• 0

C++ to C# (converting libraries)


Question

Heya, 
This is my first post and I hope I don't break any rules (which I did read :)). Anyway, here is my problem:
 
I have a project which I would really like to do in C# to expand my knowledge with this specific programming language. However, the libraries for this project are written in C++.
 
I won't ask for someone to convert the whole thing, because that's just very unfair (and breaks the rules I think :p). So I will just ask for the following the be explained in plain English! 
 
(In C++)
What does:
#ifndef
#include
#define
#void
...all mean or represent?
 
Thanks very much!
Swampy
 
Here is the code in full (in C++) just incase anyone would like to read it:

http://pastebin.com/nUQ7npTR

 

(This was initially made by Adafruit, the help support team said that they were ok with converting their code as long as it was not for commercial use (which it's not, it's a home project! :D).

 

Thanks again!

 

Link to comment
https://www.neowin.net/forum/topic/1259864-c-to-c-converting-libraries/
Share on other sites

23 answers to this question

Recommended Posts

  • 0

The keywords starting with # are called "compiler directives". #ifndef means "if not defined" #define is used to define a constant. #include includes a (header) file. Those aren't exactly part of the C++ language spec and you don't see them very often unless you're working with device driver libraries and such (which this looks like).

 

I don't see a #void in this code but void as in C++ void type. In OOP languages like C++ and C# methods or functions have a return type (int, string, etc) or void if the function or method doesn't return any value. 

 

// Returns a value (of type Int)

public int Add(int a, int b)

{

  return a + b;

}

 

// Doesn't return a value (ie. void)

public void Sleep()

{

  Thread.Sleep(1000);

}

 

I'm probably not the best person to explain all this though...

  • 0
  On 11/06/2015 at 18:23, Obry said:

The keywords starting with # are called "compiler directives" and not really part of the C++ language specification. #ifndef means "if not defined" #define instructs the compiler to define a constant. Those aren't exactly part of the C++ language spec and you don't see them very often unless you're working with device driver libraries and such (which this looks like).

 

#ifndef -> "if not defined"

#define -> defines a project constant

#include -> include a (header) file

 

I don't see a #void in this code but void as in C++ void type. In OOP languages like C++ and C# methods or functions have a return type (int, string, etc) or void if the function or method doesn't return any value. 

 

Ex:

 

public int add(int a, int b)

{

  return a + b;

}

 

public void doNothing()

{

 

}

Oh alrighy thanks!

So is there a function or some kind of operator for, if not defined for C#?

  • 0

Yes it does - #define, #ifndef, #if #endif, etc. It does not have #include directive though. You won't see much of that in C# (is what I should've said in my first post).

 

In C# you generally include your external libraries as part of your project and then use:

 

using Vendor.Library.SomeNamespace;

using Vendor.Library.SomeNamespace.SubNamespace;

 

(instead of #include)

 

That being said, you should be able to include C++ libraries (compiled into .dll) in C# and use them that way without having to re-write them.

 

Check this out ( searching on google will give you a lot more 

  • 0
  On 11/06/2015 at 18:39, Obry said:

Yes it does - #define, #ifndef, #if #endif, etc. It does not have #include directive though. You won't see much of that in C# (is what I should've said in my first post).

 

In C# you generally include your external libraries as part of your project and then use:

 

using Vendor.Library.SomeNamespace;

using Vendor.Library.SomeNamespace.SubNamespace;

 

(instead of #include)

 

That being said, you should be able to include C++ libraries (compiled into .dll) in C# and use them that way without having to re-write them.

 

Check this out ( searching on google will give you a lot more 

If you would like to see the whole provided files: https://github.com/adafruit/Adafruit-PWM-Servo-Driver-Library

 

To my knowledge I would be doing

using .... (and so on) when it comes to this. I thought using was combine file properties. (I don't think that makes much sense). This libary was giving properties to the addresses for this device.

  • 0

void is the return type of the function, in this case 

void write8(uint8_t addr, uint8_t d);

is a function called write8 that returns void, that is it returns nothing.

 

The #include, #define, etc. are compiler pre-processor directives. They are instructions to the compiler to do before it does anything else.

 

#include will include whatever file is specified. #if is a conditional. So the following code 

#if ARDUINO >= 100
 #include "Arduino.h"
#else
 #include "WProgram.h"
#endif

mean if ARDUINO is defined to be of greater-than-or-equal to 100 it will include the file Arduino.h otherwise it will include WProgram.h

 

The .h files are header files, in this case they are not standard library header files but ones provided as part of that adafruit library. You will come across two types of #include directives generally. Ones with the filename enclosed in double quotes are custom header files and their location is relative to the source file. Ones enclosed between angle brackets are standard library headers such as iostream.

 

#define is an old C way of defining a constant. So the following code 

#define PCA9685_SUBADR1 0x2

Will declare a constant called PCA9685_SUBADR1 with the value of 0x2

 

What the compiler does in this case is everywhere it sees PCA9685_SUBADR1 in your source file it will do a simple search and replace of the value 0x2. 

 

And lastly the following code

#ifndef _ADAFRUIT_PWMServoDriver_H

means If Not Defined so if _ADAFRUIT_PWMServoDriver_H is not already defined then the following #define will define it. If it is already defined then the #define on the following line is skipped over so as to not try and define it again.

 

Hope that helps?

  • 0
  On 11/06/2015 at 19:45, kozukumi said:

void is the return type of the function, in this case 

void write8(uint8_t addr, uint8_t d);

is a function called write8 that returns void, that is it returns nothing.

 

The #include, #define, etc. are compiler pre-processor directives. They are instructions to the compiler to do before it does anything else.

 

#include will include whatever file is specified. #if is a conditional. So the following code 

#if ARDUINO >= 100
 #include "Arduino.h"
#else
 #include "WProgram.h"
#endif

mean if ARDUINO is defined to be of greater-than-or-equal to 100 it will include the file Arduino.h otherwise it will include WProgram.h

 

The .h files are header files, in this case they are not standard library header files but ones provided as part of that adafruit library. You will come across two types of #include directives generally. Ones with the filename enclosed in double quotes are custom header files and their location is relative to the source file. Ones enclosed between angle brackets are standard library headers such as iostream.

 

#define is an old C way of defining a constant. So the following code 

#define PCA9685_SUBADR1 0x2

Will declare a constant called PCA9685_SUBADR1 with the value of 0x2

 

What the compiler does in this case is everywhere it sees PCA9685_SUBADR1 in your source file it will do a simple search and replace of the value 0x2. 

 

And lastly the following code

#ifndef _ADAFRUIT_PWMServoDriver_H

means If Not Defined so if _ADAFRUIT_PWMServoDriver_H is not already defined then the following #define will define it. If it is already defined then the #define on the following line is skipped over so as to not try and define it again.

 

Hope that helps?

 

Very much so!

I don't suppose you could show me how it would look in C#?

(I'm referring to: #if ARDUINO >= 100

#include "Arduino.h"

#else

#include "WProgram.h"

#endif)

 

and so would the #define PCA9685_SUBADR1 0x2 be written as        const string PCA9685_SUBADR1 = "0x2";?

  • 0
  On 11/06/2015 at 20:21, SwampyPk said:

Very much so!

I don't suppose you could show me how it would look in C#?

(I'm referring to: #if ARDUINO >= 100

#include "Arduino.h"

#else

#include "WProgram.h"

#endif)

 

and so would the #define PCA9685_SUBADR1 0x2 be written as        const string PCA9685_SUBADR1 = "0x2";?

 

Sorry but I don't know enough C# to know how you would or if it is even possible to use the C++ headers in your C# app. 

  • 0

#if and #define don't work quite the same way in C# as they do in C++.

#define in C++ can be used by itself with just an identifier, (#define DEBUG) or it can be assigned a value, (#define constValue 1) or it can be used as a macro that is replaced with code at compile time (#define INC_VALUE(val) val++).

C#'s define directive only can be used as in the first case. It's essentially a Boolean flag where it exists or it doesn't.

C# also doesn't have "ifndef" or "ifdef". You just use #if and the NOT operator. (#if DEBUG or #if !DEBUG).

Includes in C# are made using the "using" keyword rather than the include preprocessor command, so you would use "using Arduino;" at the top of your source file. This would require you have something that is in that namespace, of course.

You can call c++ library functions from C#, it's just a bit more complicated. The library either needs to be COM+ or you have to create platform invoke functions for the library.

  • 0

All of the answers so far are micro focused on a literal reply to the question and ignores the context where the person asking the question by the very nature of the question might actually be asking the wrong question - yeah parse that.

 

Google tells me that Swampy is probably looking for something like this:

 

https://github.com/raspberry-sharp/raspberry-sharp-io

 

He wants to use C# to talk to some hardware via industry standard I2C protocol.

 

Pca9685 is sample code for his device:

 

https://github.com/raspberry-sharp/raspberry-sharp-io/tree/master/Raspberry.IO.Components/Controllers/Pca9685

  • 0
  On 11/06/2015 at 20:50, Eric said:

You can call c++ library functions from C#, it's just a bit more complicated. The library either needs to be COM+ or you have to create platform invoke functions for the library.

 

That option would asume he is running the C# code on Windows. If so, he has some larger issues to deal with such as writing a device driver or using the parallel port that may not exist etc.

  • 0

I have noticed, and has been pointed out that I did lack some context. Well here it is :D.

 

I currently have a Raspberry Pi 2 in my possession, this RasPi is currently connected to a 'Adafruit 16-channel Servo hat'. Amongst many of it's features, for this problem, my only concern is for the 3 servos I have connected to servo-Output ports (if you care what ports: 1,3 and 5).

 

As I have said before, I wanted to expand my knowledge in C# and decided to try and program it in that language, however, I noticed their libraries were written in C++ (also python, but I'm not too bothered about that). 

 

I had two possible solutions (which I think were my only two):

1). Try and rewrite them into C#

2). Try and see if there was any possibility to use the C++ libraries in my C# program to use the servos.

 

Thanks very much everyone!

Swampy

 

PS: I am using the I2C protocol to communicate between the RasPi and the servo hat. 

  • 0
  On 11/06/2015 at 21:02, DevTech said:

All of the answers so far are micro focused on a literal reply to the question and ignores the context where the person asking the question by the very nature of the question might actually be asking the wrong question - yeah parse that.

 

Google tells me that Swampy is probably looking for something like this:

 

https://github.com/raspberry-sharp/raspberry-sharp-io

 

He wants to use C# to talk to some hardware via industry standard I2C protocol.

 

Pca9685 is sample code for his device:

 

https://github.com/raspberry-sharp/raspberry-sharp-io/tree/master/Raspberry.IO.Components/Controllers/Pca9685

 

 

I think the second link is the C++ to C# converted version. Am I wrong?

 

C++ version:

http://pastebin.com/nUQ7npTR

 

C# version:

https://github.com/raspberry-sharp/raspberry-sharp-io/blob/master/Raspberry.IO.Components/Controllers/Pca9685/Pca9685Connection.cs

  • 0
  On 11/06/2015 at 22:18, SwampyPk said:

I have noticed, and has been pointed out that I did lack some context. Well here it is :D.

 

I currently have a Raspberry Pi 2 in my possession, this RasPi is currently connected to a 'Adafruit 16-channel Servo hat'. Amongst many of it's features, for this problem, my only concern is for the 3 servos I have connected to servo-Output ports (if you care what ports: 1,3 and 5).

 

As I have said before, I wanted to expand my knowledge in C# and decided to try and program it in that language, however, I noticed their libraries were written in C++ (also python, but I'm not too bothered about that). 

 

I had two possible solutions (which I think were my only two):

1). Try and rewrite them into C#

2). Try and see if there was any possibility to use the C++ libraries in my C# program to use the servos.

 

Thanks very much everyone!

Swampy

 

PS: I am using the I2C protocol to communicate between the RasPi and the servo hat. 

 

 

Well you gave 1/2 the context. You are using Raspberry Pi 2 hardware.

 

Next, which O/S - there are quite a few which run on that board.

 

Since you want to use C#, Windows 10 is a natural fit:

 

http://www.hanselman.com/blog/SettingUpWindows10ForIoTOnYourRaspberryPi2.aspx

 

Or the default Linux where C# is implemented in the Mono project.

  • 0
  On 11/06/2015 at 22:25, SwampyPk said:

I think the second link is the C++ to C# converted version. Am I wrong?

 

C++ version:

http://pastebin.com/nUQ7npTR

 

C# version:

https://github.com/raspberry-sharp/raspberry-sharp-io/blob/master/Raspberry.IO.Components/Controllers/Pca9685/Pca9685Connection.cs

 

A very quick look at both suggests to me that the Raspberry Sharp project is not a "conversion" (http://www.raspberry-sharp.org/)

 

Note that in your fruit code, the main I2C comm is in some library named "Wire" which is not in that project

 

There is so little code in that adafruit sample that "conversion" is not really a useful pattern. Just understand what is doing and implement it in C# or perhaps just ignore the adafruit code and start with the Raspberry Sharp project which has a bunch of useful C# code that at a quick glance appears to be just fine as an example of C# programming.

 

Also, that Githib code is the first thing I found on a quick search so I could plop in an example, so there may be lots of other stuff on GitHub - search with "Raspberry" maybe and other source code places on the internet

  • 0
  On 11/06/2015 at 22:51, DevTech said:

Well you gave 1/2 the context. You are using Raspberry Pi 2 hardware.

 

Next, which O/S - there are quite a few which run on that board.

 

Since you want to use C#, Windows 10 is a natural fit:

 

http://www.hanselman.com/blog/SettingUpWindows10ForIoTOnYourRaspberryPi2.aspx

 

Or the default Linux where C# is implemented in the Mono project.

I am using the Raspian O/S. However, I will probably change it to windows 10.

 

Will do that tomorrow :D

  On 11/06/2015 at 23:00, DevTech said:

A very quick look at both suggests to me that the Raspberry Sharp project is not a "conversion" (http://www.raspberry-sharp.org/)

 

Note that in your fruit code, the main I2C comm is in some library named "Wire" which is not in that project

 

There is so little code in that adafruit sample that "conversion" is not really a useful pattern. Just understand what is doing and implement it in C# or perhaps just ignore the adafruit code and start with the Raspberry Sharp project which has a bunch of useful C# code that at a quick glance appears to be just fine as an example of C# programming.

 

Also, that Githib code is the first thing I found on a quick search so I could plop in an example, so there may be lots of other stuff on GitHub - search with "Raspberry" maybe and other source code places on the internet

 

The thing is, I spoke to the Adafruit help developers and we couldn't find a good version in C#. :/

 

I will keep researching :).

  • 0
  On 11/06/2015 at 23:56, SwampyPk said:

 

The thing is, I spoke to the Adafruit help developers and we couldn't find a good version in C#. :/

 

 

 

Again, there just is not a lot of code there. The Raspberry Sharp project has two GitHub projects, one general libe and an I/O lib

 

So you could grab bits n pieces of each to implement I2C and then use the device specific bits from Raspberry Sharp and Adafruit to add your servo board specifics. Don't try for a direct conversion since the Adafruit code is crap from a C# point of view.

 

Start with the smallest thing that could possibly work such as a repeating  I2C bitstream to a pin. I would have to imagine that various people have cobbled up I2C monitor software for a PC that you can use like an oscilliscope to see if your code makes proper I2C. Your turn to google and let me know if my guess is correct...

  • 0
  On 12/06/2015 at 00:37, DevTech said:

Again, there just is not a lot of code there. The Raspberry Sharp project has two GitHub projects, one general libe and an I/O lib

 

So you could grab bits n pieces of each to implement I2C and then use the device specific bits from Raspberry Sharp and Adafruit to add your servo board specifics. Don't try for a direct conversion since the Adafruit code is crap from a C# point of view.

 

Start with the smallest thing that could possibly work such as a repeating  I2C bitstream to a pin. I would have to imagine that various people have cobbled up I2C monitor software for a PC that you can use like an oscilliscope to see if your code makes proper I2C. Your turn to google and let me know if my guess is correct...

 

Good plan :D I'll report back soon.

  • 0

Is there an Arduino C# library that you are already using or wanted to use?

Converting the library (I found the source on Github) is very simple, but it's interfacing with Arduino's Wire library so that would also need to either be implemented if there isn't an existing project for it.

This topic is now closed to further replies.
  • Posts

    • It’s encouraging that Intel is still competing, the best they can, in the gpu space.
    • Windows 11 KB5062233/ KB5060843/ KB5062197/ KB5061090 setup, recovery updates released by Sayan Sen This week Microsoft released non-security preview updates for Windows 11 22H2 and 23H2 under KB5060826 as well as for 24H2 under KB5060829. The company also published dynamic updates alongside them. Dynamic updates bring improvements to the Windows Recovery in the form of Windows Recovery Environment (WinRE) updates, also called Safe OS updates, as well as to the Setup binaries in the form of Setup updates. These Dynamic Update packages are meant to be applied to existing Windows images prior to their deployment. These packages include fixes to Setup.exe binaries, SafeOS updates for Windows Recovery Environment, and more. Dynamic Updates also help preserve Language Pack (LP) and Features on Demand (FODs) content during the upgrade process. VBScript, for example, is currently an FOD on Windows 11 24H2. Both setup and recovery updates were released. The changelogs are given below. First up we have the Setup updates: Up next, we have the recovery updates: Microsoft notes that the Recovery updates will be downloaded and installed automatically via the Windows Update channel. The Setup updates, however, need to be downloaded and installed manually. You can avail them on Microsoft's Update Catalog website: KB5062233, KB5060843, KB5062197, and KB5061090.
    • Gaming mouse review: Keychron Lemokey G2 8K Wireless, high performance, light in weight by Robbie Khan Available in the UK and USA, the G2 is a wireless gaming mouse by Lemokey, the customised gaming peripheral arm of Keychron, a brand many readers will know well. I've checked out Keychron keyboards recently, models such as the top-tier Q6 Max, and the bargain priced B6 Pro. I never got the chance to check out the G1, but the G2 seems to put itself firmly in-between competing 8K gaming mice out there whilst coming in at a decent sum under £100/$100. The other benefit is that it uses Keychron Launcher, the web-based software that has been a staple part of Keychron keyboards for a long time now. I rate the launcher highly and have rarely had a problem with it, so it is great to see a robust piece of software being used on an affordable gaming mouse that's not only built well, but performs well, too. As it's a high performance gaming mouse with a flagship PixArt sensor, I will be comparing the G2 directly against my personal mouse, the similarly sized Pulsar Feinmann F01. Both mice are priced wildly differently, so it was interesting to compared several factors between them to see if one bests the other where it matters most, or if it just comes down to individual preferences. Let's check out the specs... Keychron Lemokey G2 SKU G2-A1 Sensor PixArt 3950 IPS 750 MCU Realtek 8762G Connectivity Bluetooth 5.1 / 2.4 GHz / Wired (type-C cable) Polling Rate Up to 8000 Hz (2.4 GHz / wired mode), 125 Hz (Bluetooth mode) Motion Sync Yes Angle Snapping Yes Acceleration 50g Cable Detachable Type-C to C Cable + Type-A to Type-C adapter Dongle Micro USB-A dongle Main switches Huano Micro Switch Switch lifespan 80 Million Clicks Battery 300 mAh Battery life 60 Hours (Under 1000Hz) 35 Hours (1000-4000Hz) 20 Hours (4000-8000Hz) Skates Teflon / PTFE Lift Off Distance 0.7 / 1.0 / 2.0 mm Material (Body and Grip) ABS Dimensions 118mm / 62.6mm / 38.2mm Tracks on glass (min. 4 mm thickness) Yes (max 1000Hz report rate) Weight 52 ± 3 g Colours Black / White Price £73.99 / $69.99 Fit and finish Made of all ABS, it certainly doesn't feel anywhere near as nice as the F01, but then again it's priced much lower, so no complaints there. To me it feels more like a previous generation Endgame/Zowie mouse, before they started to use the more grippy textured finish. It's a fine finish but how long that lasts only time will tell. With the Endgame mice I always found that the side buttons would go shiny first, and I suspect the exact same to apply here with the G2, since the size and shape of them is similar, as is the material quality. Speaking of side buttons, my personal preference is flat and large buttons, the long and pointed ones like those on the G2 shown above and GAMIAC PX71 at the bottom always felt a bit awkward to me for naturally resting a thumb on, whereas the large surface area of the ones found on the Feinmann and others like it feel more ergonomic and comfortable for long usage sessions. Pulsar Feinmann F01 (ergo shape) Lemokey G2 (ambidextrous shape) Under my 19cm hand, the G2 feels comfortable and stable for both claw and palm grip styles, though my usual style is a hybrid approach. I like to pinch a mouse with a thumb and ring finger, then articulate forward or backward movement with them to adapt to different play-styles. Both mice have a sloping back hump that works brilliantly for this and i had no problem getting comfortable with the G2 here. The underside of the G2 hides a cool feature I wish more more mice makers adopted, can you spot what it is in the photo below? That little flap at the bottom to store the USB dongle, it's convenient, especially for gamers who travel with their mice or laptop gamers. The skates applied are PTFE and cover three zones on the underside, sadly I forgot to take a photo of them before swapping them out to my preferred skates, the Wallhack Pro UWMP yellow dots. I found no problem with glide resistance on the stock skates, though find dots glide nicer overall and these yellow dots slide around like butter. Lemokey states 52g for the weight, give or take 3g, so my scales measure up perfectly here. It's not as lightweight as the Feinmann which currently is 45g, but it's still considered super-light and honestly, under the hand, this weight difference on dot skates is hard to tell apart. Gripping the G2 hard on both sides shows no obvious issues with flexing, so the internal construction is also up to scratch and exactly what i would expect in this price range. The differences start to be noticed when you are using the G2 as a combination mouse, both in gaming and desktop use. The wheel is thinner than the one on the Feinmann and other gaming mice with fat wheels. I much prefer a nice wide wheel, the diameter is also much larger as can be seen with the overhead photo above. The button tops also have a distinct difference in feel when actuating the switches. there is more of a hollow feel to the G2's tops, as well as slightly more travel post-click. the Huano switches feel and sound excellent, though, but I think the ABS contact surface on the outside could have been slightly more distinct in feel. Here is a demo of how the main switches sound, you can also hear the switch tops clap if you pay attention: Likewise the side buttons have extra play after the switches actuate, something the Feinmann and others in the higher price category don't tend to have. Features & software Aside from the usual features that all gaming mice support these days, such as Motion Sync, lift off distance and Macro, the big feature with the G2 is that is uses Keychron Launcher, the web-browser based software. All changes are stored directly onto the on-board memory, here is what the sections within it look like: I found no bugs with the implementation here, and unlike other Keychron wireless devices I have used with Launcher in the past, the G2 connects and can be customised in it over both wired and wireless. Just be aware that the firmware update option when connected via wireless will only check the dongle's firmware. To check the G2's firmware, a USB cable will need to be connected. Performance Whether on the desktop in Windows or in games, the cursor and motion performance is excellent, though this is to be expected from all modern gaming mice, regardless of price. The higher priced mice tend to have better use of high quality materials, and implementation of software and physical features. I'm currently pacing through nine games, all mixed genre, and in each of them I had no troubles quickly getting comfortable with the G2. If you're used to an Endgame XM2we sized mouse, then this will be just as familiar to you. The convenience of the DPI button at the top of the mouse instead of underside makes instant switching simpler, although the button can be remapped to do something else for those who don't care about DPI toggling. Here is a demo of the G2 playing Doom: The Dark Ages: The sensor tracking performance at 8K was perfect, aside from the few moments my physical movement on the mousepad slowed down causing some drops in the Razer mouse tracking measurement graph below: A consistent 7900-8000Hz polling was observed, though keep in mind that using such high polling rates will impact CPU performance whilst gaming. How much this affects framerates will boil down to the CPU you have. On my i7 12700KF there was no change whilst gaming, but during the measurement above I did observe 13% CPU package utilisation. In practice, though, I saw no performance difference playing at 8K versus 2K or even 1K. Battery life will drastically be impacted at 8K polling rates for obvious reasons, and whilst many prefer 4K, there is a growing trend to just stick to 2K which feels the safe middle ground of great battery performance, whilst still being suitably responsive on paper for even the most fast-paced of gaming sessions. Conclusion The Lemokey G2 has proven to be a rather excellent mouse, not just for gaming, but general use, too. Though being excellent doesn't give it any special status, as there are equally excellent mice out there that cost the same, less and even more. A buying decision will come down to individual needs, do you value the use of a browser-based software tool like Keychron Launcher? If so, then this is right up your street. Do you want something lightweight but also supports the gaming features and feel in the hand as more expensive mice? Maybe the G2 will satisfy. It's not totally perfect, nothing ever is, the thumb buttons could have been a bit wider, and the switch caps have more travel after clicking than what I am comfortable with, but otherwise this is a great mouse with features that rival the competition. The sensors on gaming mice these days isn't a key selling factor any more either, since even entry level gaming mice are capable of precision tracking and speed that was only possible on the top-tier models of the past, like many things in tech now, the point of diminishing returns has been reached, and brands have to work harder at giving us consumers a unique selling point to attract interest. I believe the G2 has at least one USP (Keychron Launcher), it also helps that it's a very comfortable mouse to use for all-day sessions.
    • Delays can happen any time and names changed, but the joke was still there to be made.
  • Recent Achievements

    • Week One Done
      Alexander 001 earned a badge
      Week One Done
    • Week One Done
      icecreamconesleeves earned a badge
      Week One Done
    • One Year In
      PAC0 earned a badge
      One Year In
    • One Month Later
      PAC0 earned a badge
      One Month Later
    • One Year In
      Shahmir Shoaib earned a badge
      One Year In
  • Popular Contributors

    1. 1
      +primortal
      564
    2. 2
      +FloatingFatMan
      187
    3. 3
      ATLien_0
      185
    4. 4
      Skyfrog
      113
    5. 5
      Xenon
      109
  • Tell a friend

    Love Neowin? Tell a friend!