Crossplatforming methods in C

Whether you're a newbie or an experienced programmer, any questions, help, or just talk of any language will be welcomed here.

Moderator: Coders of Rage

Crossplatforming methods in C

Postby metallicsoul92 on Tue Nov 17, 2015 8:46 pm

By methods i mean ideologies, not code xD

I have been writing a General crossplatform library in C so that users can download the library, and make objects like threads out of the box. Basically abstracting 3 platform specific code into one, but i dont quite know what the better approach on doing so is. One one hand, i can write functions under if __linux__ or if __WIN32 || __64 , and then create a global function, could figure it out on runtime (Which technically it wont , it usually identifies itself by compile time) and do something like :
[code2=] #if __linux__
#define createThread(args) createThreadL(args)
#endif
#if __WIN32 || __WIN64
#define createThread(args) createThreadW(args)
#endif
[/code2]

or i can go about it another way, end each file with the operating system , like threading_linux.c threading_windows.c etc, and write a makefile which decides which ones i build. Also are there any other ideas/Tips on creating crossplatform code in C?
metallicsoul92
Chaos Rift Newbie
Chaos Rift Newbie
 
Posts: 7
Joined: Sun Mar 08, 2015 12:31 am

Re: Crossplatforming methods in C

Postby dandymcgee on Wed Nov 18, 2015 5:41 pm

Of the two approaches each has pros and cons. The first can make for messy code that is hard to read unless you're careful about formatting. The second makes it hard to compare what any given function does on different platforms (you have to open multiple files to find each copy of the method). Also, the second might force you to duplicate a lot of code that would otherwise be shared.

I think it depends on how different each OS implementation really is. Are you replacing a few lines, or basically the entire method. If a few lines, use the #ifdef approach. If you're basically #ifdef'ing the entire function, it's probably cleaner to just create multiple files and #ifdef the #include, regardless of whether or not you're using the Makefile approach.

I can't think of any additional alternatives off-hand.
Falco Girgis wrote:It is imperative that I can broadcast my narcissistic commit strings to the Twitter! Tweet Tweet, bitches! :twisted:
User avatar
dandymcgee
ES Beta Backer
ES Beta Backer
 
Posts: 4911
Joined: Tue Apr 29, 2008 4:24 pm
Location: New Hampshire

Re: Crossplatforming methods in C

Postby metallicsoul92 on Wed Nov 18, 2015 8:56 pm

dandymcgee wrote:I think it depends on how different each OS implementation really is. Are you replacing a few lines, or basically the entire method. If a few lines, use the #ifdef approach. If you're basically #ifdef'ing the entire function, it's probably cleaner to just create multiple files and #ifdef the #include, regardless of whether or not you're using the Makefile approach.
I can't think of any additional alternatives off-hand.


Well with Threads, they are alot different between linux pthread and windows threads.pthreads can be made with even just 2 parameters , (pthread, NULL, thead function, NULL), or as shown up to 4. If im not mistaken , windows has extra info inside the threads such as stack size, and each parameter has its own type for its data. Im trying to figure how i could make it into one function, but the makefile way with say createThread_linux() and createThread_Windows() and a
#ifdef __linux__
#define createThread(params) createThread_linux(params)
#endif
#ifdef __WIN32 || __WIN64
#define createThread(params) createThread_windows(params)
#endif
metallicsoul92
Chaos Rift Newbie
Chaos Rift Newbie
 
Posts: 7
Joined: Sun Mar 08, 2015 12:31 am

Re: Crossplatforming methods in C

Postby Falco Girgis on Tue Feb 09, 2016 2:07 pm

I definitely DO NOT recommend having a clusterfuck of preprocessor conditional compilation statements like that all over your codebase for supporting multiple platforms. That's what the very first revision of libGyro looked like, and it got really gold really fast. That's a messy, totally disorganized way of handling things.

You are much better off separating things into different files in the long-run. There are a few shortcomings with just doing this naively that can all be addressed with a more advanced structure.

1) Duplicate code. You should be able to reuse ALL common code by only abstracting away the bare minimum, truly "platform-specific" code. Take the libGyro video subsystem as an example. Common code resides in "gyro_video_common.c" which then calls private functions defined within "gyro_video_x86.c," gyro_video_arm.c," "gyro_video_sh4.c" depending on the platform. Now the platform-specific C files have ONLY functions that are 100% platform-specific and they all share the same method signature so common code remains completely oblivious to the platform-specifics. This is link-time polymorphism.

2) Inability to Inline/define custom datatypes. In the simple .c file abstraction method, you lose the ability to inline functions (since they must be defined in header files), and you lose the ability to define platform-specific datatypes. By also extending this kind of abstraction to give each platform also a "gyro_video_*.h" file, you can still make custom structs, #defines, and even inline functions, so there is no loss of functionality or performance.

The next obvious question should be "so how the hell do I know which header file to include if I'm not using #ifdefs!?!" Good question, grasshopper. I suggest having something like a "platforms.h" with a single set of #ifdefs where each platform gets its own #defines for header files. So for libGyro you would see something like:
Code: Select all
#if GYRO_PLATFORM == GYRO_PLATFORM_PC
    #define GYRO_VIDEO_INCLUDE "pc/gyro_video_pc.h"
    #define GYRO_MATH_INCLUDE "pc/gyro_math_pc.h"
#elif GYRO_PLATFORM == GYRO_PLATFORM_DC
    #define GYRO_VIDEO_INCLUDE "dc/gyro_video_pc.h"
    #define GYRO_MATH_INCLUDE "dc/gyro_math_pc.h"
#endif

Now gyro_video_common.h/.c is simply including the correct file by:
Code: Select all
#include "gyro_platform.h"
#include GYRO_VIDEO_INCLUDE

and this is all transparent to client code (which should only ever be including the common header file, which is including the platform-specific one internally). Basically you're achieving link-time polymorphism with the .c files and compile-time polymorphism with the .h files. In the long-run, abstracting platform-specific code into respective .h/.c files is far cleaner and scales MUCH better than having a clusterfuck shitshow of preprocessor conditionals all over the place in random .h and .c files.

Now if you have a problem with one platform in particular or are only interested in looking at a subset of the codebase, it's all in one place. Also, assuming you want to add a new platform like Android, iPhone, Wii U, or a toaster, you can easily see exactly which functions need to be implemented to get the codebase on another platform.
"So what happens if the Elysian Shadows Kickstarter fails?"
Image
User avatar
Falco Girgis
Elysian Shadows Team
Elysian Shadows Team
 
Posts: 10693
Joined: Thu May 20, 2004 3:04 pm
Location: Studio Vorbis, AL


Return to Programming Discussion

Who is online

Users browsing this forum: No registered users and 0 guests