I’ve been working on a project with a diverse set of software components that must all work together and communicate over the network. There are separate Mac and Windows clients that must communicate with the same unix server. And while there’s already a well-defined protocol for their network communication and message passing, we also need to transmit a large stream of somewhat time-sensitive data.
Given that we want to reuse the same implementation for this streaming, we had the following requirements:
- It should be out-of-band with the rest of the communication.
- It must compile and integrate with POSIX C, a Windows .NET app, and a Mac Objective-C app: effectively, it must be written in C.
- It must be reasonable to share the code between our different components.
How hard could it be?
It turns out that it’s actually not terribly easy to write C that can be shared between POSIX and Windows. I encountered several issues.
The MSVC compiler doesn’t fully support modern C
There’s not much you can do about this. I had to forego some nicer C99 features, like designated initializers of structs. I ended up setting Visual Studio to compile my C sources as C++, but leaving the file extensions as
Naturally, the headers are different between Windows and POSIX. It was a reasonable compromise for me to have a
platform.h header and test for whether
It’s also important that you avoid including Windows.h, if you possibly can. Why? One word:
Cross platform networking?
Naturally, the networking APIs are entirely incompatible between Windows and POSIX. There are some lightweight libraries that you can use to get past this level, but they proved not useful for my purposes.
My solution was to create a layer, defined in
networking.h, that abstracted just on top of what we needed out of the underlying networking layer. Then we have two C files,
networking_windows.c that implement those functions using the respective APIs. It’s unfortunate, but not too bad.
If you’re trying to use
bool in C on Windows, you’re going to have a bad time. You’ll also have a nice time figuring out how to include the definitions for types like
uint32_t. My solution was to define these manually in my
platform.h when Windows is detected.
Sharing the source
Our major components are all in separate Git repositories, so naturally, we used Git submodules. Just kidding, we used Git subtrees, instead. So far, this has proved a bit painful when it comes time to pull new changes from the transport repository (or vice versa). You have to be careful to be consistent with your use of
--squash, have to
cd back to the root of your repository before you can push/pull, and also need to specify the remote repository explicitly each time.
Someday, I hope that I will devise a nice solution for this problem.
Hopefully, you won’t need to do something like this often. If you do, I hope this will be useful in setting you on the right path.