random caveats/aspects in c/c++ cross platform development
the slash
UNIX systems have been using forward-slashes for path separation and dashes for cli utilities arguments, while microsoft chose to use back-slashes for path separation because they decided to use forward-slashes for cli utilities arguments, knowing that back-slashes are also used for character skipping on both systems, this is a good example of simple decisions that cause programmers to waste precious hours writing abstractions and fixtures for eternity, few notes to say though:
- MS compilers support using forward-slashes in header includes like:
#include <some/path/to/header.h>
most -if not all- of windows filesystem API functions can handle paths with forward-slashes, yet they always return paths with backward-slashes
c++17 std::filesystem can help with similar issues -also other 3rd party libs exist-
in java there is File.Separator which changes according to the OS -not to be confused with File.PathSeparator which hold : or ; that is used to separate paths in environment variables-
network resource paths
on windows network files are access via the UNC format that looks like \\host\path
on linux you have to mount the remote filesystem before accessing it
- you must have the necessary driver/package to communicate with the the remote filesystem service
- when mounting, you write the remote export path in a format that the driver understands
- host:/path for nfs
- //host/path for smb/cifs
- user@host:/path for sshfs
- in the end you access the target file just like any local file, probably like /mnt/mymountpoint/myfile
if your application deals with a lot of file sharing protocols then for extensibility you can create your own network resource path format that is a bit self descriptive, and then translate it according to your needs -what about nfs4.1://host/path/myfile ?-
build systems
cmake seems widely accepted, and although I personally don't like it, it appears to be the best option for today
- as you might have noticed, cmake is not a build system, it is a build system generator
- it can generate unix makefiles, VS project files, XCode project files -probably others ?-
- it has extensions like cpack that helps you package your application into a windows installer, a debian package or others -possible alternative: fpm-
makefiles can be used for projects that are not massive
- can be used to generate windows binaries on linux via mingw32 or clang (see here) -performance?, linking headache?-
- can spend some time to write a magical makefile that works on linux and cygwin environments
- MS has nmake -does anyone use it?-
some people choose to write separate build configurations for different systems by hand -going primitive is not that bad-
tons of cross-platform c/cpp build systems/generators exist today (meson, bakefile, premake, etc), take -or waste- the time to discover
networking
socket programming APIs have some differences -check this article-, so your options are:
- write your own abstraction -time?, maintenance headache?-
- use a third party library like boost or poco -do they fit with your religious beliefs?-
GUI frameworks
- GTK
- GNU project
- cross-platform -for the desktop-
- sort of "native" on linux
- has a wysiwyg editor named glade
- LGPL license
- QT
- most popular, everyone likes it
- cross-platform of course, can be even used on android
- integrated into QT's IDE -at least-
- dual licensed
- custom styles that imitates current OS
- wxWidgets
- getting older, less popular
- integrated in codeblocks IDE
- compiles to native GUI APIs, -MFC on windows, gtk on linux-
printf format specifiers
care needs to be taken when using printf -or variants- extensively in a cross platform program -again, thanks microsoft for not complying to the standards-
- on windows the printf variant you are calling redefines format specifier meaning, for example:
- in printf %s means multibyte string, %S means wide string on windows
- in wprintf %s means wide string, %S means multibyte string
- same for %c and %C
- according to c standards -also as implemented by gcc- specifiers hold their meaning regardless of the function
- %s means multibyte string, %S means widestring
- a solution is to always use %hs for char* and %ls for wchar_t*, note that %hs is not standard, it is supported by msvc and does nothing wrong in gcc -see The sad history of Unicode printf-style format specifiers in Visual C++-
- also msvc supports a set of non-standard specifiers like the %I64, which I believe should be avoided
- fmtlib is worth looking at
on-library-load hooks
on windows DllMain is called on loading and unloading of shared libs, this can be done in a cross-platform way with something like:
#ifdef __GNUC__
__attribute__((constructor))
#endif
void _onLoad(){
}
#ifdef __GNUC__
__attribute__((destructor))
#endif
void _onUnload(){
}
#ifdef _MSC_VER
#include <windows.h>
bool WINAPI DllMain(void* hinstDLL, unsigned long fdwReason, void* lpvReserved){
if(fdwReason == DLL_PROCESS_ATTACH){
_onLoad();
}else if(fdwReason == DLL_PROCESS_DETACH){
_onUnload();
}
return true;
}
#endif
possible approaches for solving platform API differences
- use the available posix functions that are implemented by windows
- use directives like #ifdef WIN32 to separate platform specific code
- write an abstraction in the form of a header file that has one source file for each OS, include the right source file in the OS specific build configuration
- use a third party library or possibly a recent STL version
- other ugly solutions I'm not sure they are possible to apply:
- use linux APIs and make cygwin dependant windows builds
- use windows APIs and make winehq dependant linux builds
random hints
the closest linux equivalent to windows file attributes is a mix of stat.st_mode and ioctl flags or probably the very recent statx
file locking is inherent and respected on windows, while on linux there are a couple of flavors but all are non-mandatory, in other words the program chooses whether to abide or to totally ignore the locking
- see this good article
some data types differ in size according to OS see here
- this can cause issues in cases like transmitting a struct in raw bytes on the network between two incompatible systems -probably why serialization exists-
- also msvc and gcc do struct alignment differently, can cause the same problem
if you think I'm missing something here, please let me know in a comment