

Brian's Rules for Writing Cross Platform 'C' Code (2008) - nkurz
http://www.ski-epic.com/source_code_essays/ten_rules_for_writing_cross_platform_c_source_code.html

======
psykotic
I developed 9 ports and maintained 13 ports of RAD's Telemetry middleware for
the game industry over a period of 3 years. My biggest takeaway from that
experience is that build management, testing and release management are the
hard parts once you get beyond the first few platforms. The only platforms
that posed even minor problems in the traditional coding sense were ones in
their infant stages (e.g. WiiU, Xbox One and PS4 years before their release)
where hardware, APIs and even compilers are full of holes, and you have to
deal with SDK updates (including deprecated APIs) and devkit refreshes.
Automate everything you can and grit your teeth for the rest of it.

Purely on the coding side of things, I would caution against extremism when
fighting code duplication between platforms. There is much to be said for
having a single C file per platform that is self contained, even if it entails
a certain level of duplication. The alternative very easily leads to an
increasingly bifurcated decision tree that tries to account for similarities
and differences between platforms (autoconf syndrome).

------
Htsthbjig
It is somewhat outdated, but yes, most of the points apply today.

Some things changed. Qt is pretty solid today. We use it as native Linux
platform. We use native Windows and Mac front ends too.

Using Qt solves lots of issues very easy, like OpenGL drawing capabilities.

So in Windows and Mac we have two versions of our own software, one with
native widgets, one with Qt ones. The Windows version using Qt is much faster
and efficient, albeit a little ugly, because of OpenGL issues(instead of
official DirectX) and font sizes or colors a little different from Windows and
so on.

~~~
joshvm
The obvious benefit for me is that Qt provides cross platform GUI creation
which is, after all, what it's designed for. Building your own frameworks is
great if you have the man power to do it once, but I think there is a strong
case for using something like Qt even if it locks you into using its data
types.

If you're just writing a utility of some sort then sure, you probably don't
need the kind of overhead that Qt dumps on you. That said, I've written very
performant code using Qt's threading and concurrency libraries and it t was
surprisingly easy. Think industrial image processing that needs to be done at
150Hz on two camera streams simultaneously.

------
mtdewcmu
I think there are some good, sound principles here; I've dealt with some to
one extent or another, and I couldn't find anything to disagree with. I worked
on a port of a large Java application to C++ on a different platform, and we
went back and forth for a while trying to choose between utf-8 and utf-16 as a
standard character encoding. We ultimately settled on utf-8, even though the
Java code had used utf-16 (because it's Java's native encoding); I'm not
surprised to read here that utf-8 is the way to go. Utf-16 is at odds with the
expectations of every C and C++ standard library function and the language
itself, which would have been a source of annoyances that would never end.

It's very, very hard to make any generalization about programming, and so I
suspect that in his confident pronouncement that cross platform should never
add more than a small amount of time to a project, there is something he's
ignoring. The application they sell is for backup, and so it would probably be
close to ideal for making cross-platform. I think he underestimates how
complex GUI code can get when dealing with requirements less trivial than a
backup application.

He also basically ignored Macintosh and ObjectiveC. I don't recall what the
state of Macintosh was at the time this piece was written, but I'm pretty sure
ObjectiveC was the standard language on Mac then as it is now.

I think there are sage principles here, though.

~~~
mikeash
UTF-16 is a sad encoding and there's essentially no reason to ever use it
except when interfacing with something that insists on it.

UCS-2 made a lot of sense back in the day when Unicode was supposed to be
16-bit and you'd just have one 16-bit "char" for each Unicode code point.
Bliss! Then Unicode breached the BMP and this didn't work anymore. UTF-16 is
just a way to let all that old code that assumes 16-bit Unicode to keep
working. You get all the disadvantages of UTF-8 (no 1:1 correspondence between
code units and code points) and none of the advantages (widespread
compatibility with non-Unicode-aware tools, mostly-reliable discrimination
between actual text and random binary data that might look like text).

One can kind of make a case for UTF-32 in some cases, but UTF-16 is almost
never the right choice unless there is literally no other.

~~~
mtdewcmu
That helps put some things in perspective. Unicode is not a well-understood
topic among programmers, I've noticed (at least in the US), and for something
so important it's surprisingly difficult to find information. The distinction
between UCS-2 and UTF-16 must have made sense at one time, but it makes no
sense anymore, and it's not necessarily easy to track down the explanation for
why there are two 16-bit formats. Thanks.

~~~
mikeash
The whole thing is pretty much incomprehensible without understanding the
history. It's a bit crazy how much historical baggage Unicode has managed to
accumulate in just a couple of decades (and most of it from just the first few
years).

It sounds like you have it figured out, but just in case: UCS-2 is the
original pure 16-bit encoding from the days when nobody could conceive of
needing more than 65536 characters for anything. UTF-16 is the hacked up
"oops, it turns out there are a lot more Chinese characters than we realized"
version that tries to maintain some compatibility with software built for
UCS-2.

Given that there was only a five-year period between the first release of
Unicode and the introduction of UTF-16, it's kind of amazing how pervasive the
16-bit encoding got. It's thoroughly baked into Java, where "char" was
supposed to be a unicode character, but ended up being a unicode character or
a piece of a UTF-16 surrogate pair. Windows APIs and Cocoa's NSString went
through the same process. JavaScript too, and the spec _still_ allows
implementations to only support UCS-2.

~~~
mtdewcmu
The c and c++ apis seem to make things worse. The right solution for charset
conversion seems to be iconv.

------
jmgao
#3 is bad (or at least incomplete) advice when you consider portability to
64-bit. What it should say is to use the C type that correctly represents the
data.

Translating DWORD to `unsigned long` is not correct! unsigned long is 32 bits
on x86_64 Linux, but 64 bits on x86_64 Windows. Using long is essentially
never correct for cross-platform code.

The right thing to do depends on what the value represents. If you want a 32
bit unsigned integer, use uint32_t. If you want something representing a
pointer or size, use the corresponding type (uintptr_t, size_t). This doesn't
just make porting easier, it adds useful hints for anyone reading your code.

~~~
foxhill
really, the issue is, if you want a fixed bitwidth data type, use the types in
unistd.h - uint64_t or uint32_t.

~~~
omilu
but fixed width data types are super freaking slow if your doing intensive
processing.

~~~
brianwski
I'm not buying it. I want to see your timings and proof. First of all, this
only applies to a very few internal loops that don't touch disk, so you can
use the "cross platform rule" for 99.999 percent of code. But if you are down
to that last inner loop that runs your 3D video game, I'll admit you will need
to have platform dependent code paths. But this describes an infinitesimally
tiny subset of the code in the world. The rest of the time, write easy to
understand cross platform less buggy code!

------
adamnemecek
'But on BOTH PLATFORMS they call the same line of code ->
BzUiUtil::PauseBackup();'

That's some funky C code you got there Brian.

~~~
mtdewcmu
I thought that, too, when I saw that example, clearly in C++, after about 100
mentions of 'C'.

I've encountered programmers that habitually refer to C++ and ObjectiveC as C.
I suspect he has that habit.

~~~
brianwski
Original author here:

No, we use 'C' and 'C++' on Windows PCs, and we use 'C' and 'C++' and
'Objective C' on the Macintosh. Objective C can call into 'C' libraries just
fine on the Macintosh. Objective C can also call into C++ just fine on the
Macintosh.

The distinctions are very important to us. :-) Objective C cannot compile on
Linux or Windows, but 'C' and 'C++' can compile on all three platforms
(Macintosh, Linux, and Windows) using the default development environments on
all three platforms (GCC on Linux, Xcode on Macintosh, Visual Studio on
Windows).

------
rumcajz
#4 is not completely true, as the "standard" ifdefs are defined by the
compiler, not the platform. Thus, they may differ when building on Windows
using MSVC and building on Windows usign MinGW.

~~~
justincormack
Kind of falls out of his using one compiler per platform. There tend to be few
differences though unless you use something less standard (like ARM's C
compiler).

~~~
brianwski
Original author here. Yeah, my goal was cross platform APPLICATIONS where the
first step is choosing your tools. I suppose if you are writing a library like
OpenSSL that you want to compile with every last flavor of every last compiler
the problem gets worse.

I stand by my guideline for applications.

Another way to think about it is if you want, use all your own custom compiler
flags, then if you can manage to get your compiler flags set by a creative
combination of BUILT IN compiler flags, that way your code will compile right
out of the box correctly on all platforms. What I dislike is that out of the
box the code compiles on zero platforms. Instead, you run a "configurator"
program that sets all the cross platform flags correctly. If at all possible,
write your code so that it self configures correctly with zero changes on all
compilers and all platforms. Please notice the "if at all possible". I think
if you are relatively careful and just use basic types this can be done
easily. Is it really that amazingly hard to find a native "int32" and "int64"
type using ifdefs on every platform/compiler?

------
kensai
This is a remarkably old article, but its truths mostly hold today. I also
think that cross-platform compatibility is a day-0 affair, you need to get to
that mentality from your first line of code. Adapting later will always be
harder and more error-prone.

------
bluedino
This post is also on BackBlaze's blog -
[http://blog.backblaze.com/2008/12/15/10-rules-for-how-to-
wri...](http://blog.backblaze.com/2008/12/15/10-rules-for-how-to-write-cross-
platform-code/)

------
ahomescu1
When I read "Platform" I thought the article would be about CPU architectures.
Still a good read though.

~~~
Danieru
I'm not sure if this is standard terminology but I like to refer to multiple
operating systems as cross-platform and multiple CPU architectures sets as
cross-architecture. It looks like the author is doing something similar.

After all the problems you encounter porting from Unix to Windows are
different from what you hit porting across architectures. With cross-platform
you're worrying over file IO functions or the lack of fork equivalent. With
cross-architecture you must ensure the code is not abusing ints for storing
pointers or assuming sizes of primitives.

~~~
jacquesm
> With cross-architecture you must ensure the code is not abusing ints for
> storing pointers or assuming sizes of primitives.

You should ensure that anyway, even when you're working on a single
architecture. Those are very bad habits to form.

~~~
voltagex_
I'm just starting C, what should I be using instead?

~~~
jacquesm
Don't make any assumptions about the architecture that your program will run
on. If you have to go into variables with a different type than the one with
which they were declared make sure you do so in a portable way.

So, for instance, if you want the lower 8 bits of an int don't declare a
pointer to a character and then lift out the byte, but do (value & 0xff)
instead.

That way if you move your program to a big endian machine it will still work.
Storing pointers in ints assumes that pointers and ints are the same size,
which is definitely not always the case.

So store pointers in a space sizeof(whatevertype*) rather than sizeof(int).

------
tylerneylon
I recently published an open source win/mac wrapper library in C. It is not
well tested, and only covers the specific subset of functionality I needed for
my current project. It may still be useful for others learning which pieces of
each os handle which common tasks - things like CoreGraphics vs GDI, file
system quirks, how to play audio, etc.

[https://github.com/tylerneylon/oswrap](https://github.com/tylerneylon/oswrap)

------
frakturfreund
The praise for OpenSSL („I can't recommend OpenSSL highly enough, […] we just
could not have done better at Backblaze.“) is kinda funny now after Heartbleed
and the massive code cleanup of the LibreSSL people …

~~~
brianwski
Original author here -> technically Heartbleed was not a bug in the OpenSSL
low level library, it was specifically an HTTPS bug (it was a bug at a higher
level than the library I'm saying I liked then and still like). Specifically
it was a bug in the "Heartbeat Extension for the Transport Layer Security"
protocol (the HTTPS level is the TLS layer, the underlying encryption
functions did not have this particular bug).

All that is a technicality, software has bugs. Heartbleed was a particularly
bad security bug. This doesn't mean you throw out the baby with the bath
water. As a society we all use HTTPS and it had a bug -> so we fix this bug
and move on as best as we can.

As far as I know, something like 99 percent of routers and OS systems such as
Linux, Macintosh, and Windows use the OpenSSL library and it has very little
to do with Heartbleed. On the other hand, it is fairly important your system
vendor stays on top of security patches and applies them.

OpenSSL is software, and therefore has bugs. But the OpenSSL encryption layer
did not contain the Heartbleed bug. Or put differently, the OpenSSL encryption
libraries that encrypt AES 128 and AES 256 had nothing to do with Heartbleed,
and those are the parts we continue to use at our company completely for free,
without paying any royalties to anybody, and they are a lot more secure than
anything we could have hand-rolled ourselves even if we took years to do it.

------
dgemm
Simply compiling and running on many different platforms (Windows, Linux,
32/64 bit, ARM, ...) has helped me find a number of truly weird issues that
would have surfaced later. Compiler warnings, diverging stdlibs, alignment...

I'm a strong believer in this approach.

~~~
brianwski
Original author here. I COMPLETELY agree. Anybody who defends their little
one-platform-world as "special" should run their code through several
compilers. It's like running a spell checker on your essay for the first time
- the spelling errors jump out at you. Different compilers seem to be
amazingly different in catching really obvious bugs. When I get something
fully working on Windows and Linux, then the Macintosh XCode points out a
problem I think to myself "WHAT THE HECK, HOW DID WINDOWS ALLOW THIS?!" It is
worth doing cross platform development simply to raise the quality of the
software you are working on, even if you never ship on the other platforms.

