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

  • 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

Leave a Reply