The "correct" way to do something like that is via the appropriate system API and drivers.
Generally only device drivers should talk directly to the hardware, everything else should talk to the drivers.
On a full OS like windows or linux you end up going through several layers of drivers e.g. your application uses a text entry API which then may talk to a USB keyboard driver or it may talk to a touchscreen keyboard emulator. The idea is that applications shouldn't need to talk to the hardware or even know exactly what the hardware is.
The OS normally won't let a user program talk to the hardware directly.
This both makes software more portable and also prevents all sorts of potential stability and security issues.
The only times you would talk directly to the hardware is if you're running an embedded system without an OS or you're writing device drivers yourself.
In that situation it's actually fairly easy to talk to the hardware from c. In fact c is the preferred language for things like that, you'd have to be crazy to write drivers in something like c# or java.
Be warned, firmware and device drivers like this are very different from application programming. Lots of bit manipulation operations to set and read individual settings.
Hardware is always memory mapped, all of it's controls show up as memory addresses so all you need to do (once you've bypassed any OS or CPU memory security features if they exist) is to create a pointer to those addresses and then change/read the values.
e.g.:
volatile char* lastKeyPressPointer = memoryAddressOfKeyboardInputData;
char keyPressed = *lastKeyPressPointer;
The volatile keyword tells the compiler to always check the physical memory address rather than using the last value it stored in cache. Without it you'd just keep getting the first letter pressed over and over since the compiler would optimize the actual memory read out for the second letter onwards.
Same for writes, without the volatile keyword writes would sit in the cache and not always get pushed out to the actual hardware.
As to what the correct memory addresses are and the correct values to put in them, that is system and hardware dependent and you'll need the datasheet or software manual for the parts in question. And ideally some sort of hardware experience to make sense of the documents.