My experience in programming was mixed between Linux and Windows. There are lot of standards like POSIX to standardize the api’s and there behavior across different OS and compiler environment. Here in this article I’m going to provide few tips which will be helpful for you to write OS and compiler independent code. Send me mail(yogi@yogindar.com) or add comment if you want any specific tips to be included in this section which you believe it is commonly encountered.
Few principles to be kept in mind before writing a code is
1. Reusable code
Never assume your code will be used only in Linux or Windows or for the same reason the code is written. There will be cross compiled projects which may have the same requirement. E.g:- An embedded Linux project adding a new encryption algorithm which is reused in windows based application.
2. Use standards
Use standards like ANSII C, POSIX where ever applicable as the compilers C compilation and C library will be supporting those standards by default. There will be other standards which will support more identifiers and compiler specific macros and I suggest you should be avoiding those.
3. Avoid compiler supported macros and keywords.
Most of the time you may have to compile your code based on the best compiler suited for that OS environment. You might be tempted to use those short cuts. Unless and until it is extremely important avoid those keywords and macros.
4. OS specific includes
All OS/compiler specific includes, #defines should be defined in OS specific files. This way while porting you may have to only touch this source file and not any other source files. Include platform.h in all your source code. Obviously Makefile differs.
E.g:-
Have a file named platform.h
Inside platform.h have the following code.
#ifdef WINDOWS
#include "windowsdef.h"
#endif
#ifdef LINUX
#include "linuxdef.h"
#endif
Now you will think what you will define within windowsdef.h and linuxdef.h. Next few points covers the same.
5. Segregating the includes
Identify all the #includes you need for your code. Include your OS specific #includes accordingly.
E.g:- a specific type may be defined in two different header files/path between windows and Linux.
6. Typedefining
There will be OS specific types which needs to be manually added in the other OS specific header file.
E.g:- uint16_t in Linux GCC.
I suggest a better approach for the same. Have a platform independent Typedef.
E.g:-
In windows
typedef unsigned short UINT16
In Linux
typedef uint16_t UINT16
Ones you have made the type independent start using the type independent code only.
7. Library codes
Similar to types and includes you may find differences in system and C library calls. I suggest you should add a separate platform specific library to handle the scenario. In windows have a seperate code for oswin.lib/oswin.dll where you define OS/compiler specific library calls for windows. Similarly you can do the same for Linux too with a liboslinux.a/liboslinus.so.
8. C library calls.
There will be lot of C library calls with the following behavior.
a. Same between both the OS. Until and unless you have to port to a new OS/compiler/different C library, you dont have to worry. If you encounter the same situation just add the specific prototype in #include os(newos).h and define the C library call in os(newos).lib/os(newos).so.
b. Minimal difference. May be the parameters used are swapped. Use #define in your one of the OS specific include file(oswin.h/oslinux.h) and there by call the corresponding C library call for that OS. Avoid rewriting the same C library code. If you have the code why do you want to rewrite. our time is valuable for lot of other things so never reinvent.
c. Completely different. As mentioned din point a where a new OS does not have the code understand the api parameters, return codes well and also the behaviour and rewrite the same with your OS specific library(oswin.lib/oslinux.a)
9. System calls
I suggest the same C library points applies here. So copied the same and also added a specific section more.
a. Same between both the OS. Until and unless you have to port to a new OS/compiler/different system call, you dont have to worry. If you encounter the same situation just add the specific prototype in #include os(newos).h and define the C library call in os(newos).lib/os(newos).so.
b. Minimal difference. May be the parameters used are swapped. Use #define in your one of the OS specific include file(oswin.h/oslinux.h) and there by call the corresponding C library call for that OS. Avoid rewriting the same C library system call code. If you have the code why do you want to rewrite. our time is valuable for lot of other things so never reinvent.
c. Completely different. As mentioned din point a where a new OS does not have the code understand the api parameters, return codes well and also the behaviour and rewrite the same with your OS specific library(oswin.lib/oslinux.a)
d. Very OS specific system calls. E.g:- fork(). There is no equivalent system call in windows. I suggest write wrappers and make use of the application of fork() rather than its oS specific behavior. Use it for creating a child process but with wrappers.
10. Threads, IPC and sockets
Most of them are written based on POSIX and windows and Linux will have the same implementation with same api name, different api name, different library support etc. Find out those differences and apply the above mentioned logic.
11. Wrappers/Stubs
Try using wrappers and stubs. This will help the code easy for porting and maintenance.
Wrapper is an api defined by us over the C library call(system call). Like typedef we are trying to consolidate same names for functions also there by we know what are C library calls we use and we can quickly know what are api’s to be defined by us.
Stubs is just a dummy api. Sometimes we may need those stubs because of the implementation of library calls.
E.g:- In windows socket calls are defined in a separate library and in Linux they are system calls and part of GNU C library. To use the socket libraries we need to use WSAInit() in windows for which we can define an equivalent stub in Linux and a wrapper in windows named socketinit().
12. Coding styles
I don’t have a strong opinion on using Hungarian notation or Linux kernel coding style. Use one of them but don’t mix and match.
13. Avoid using architecture specific code. One example is using asm keyword. Between 320-bit windows itself there is a difference in using assembly code inline.
14. Other common tips which is not OS specific.
a. Ensure your structures are padded correctly.
b. Take care of endianism if you are sending/receiving data to a different device/computer.
c. Avoid architecture specific code. If architecture suggests the local variables are initialized to 0 by default never rely on the same. Initialize all the local variables.
d. Use common indentation style. 1 tab = 4 spaces.
e. Avoid mix and match of new line characters. In windows it is \r\n, Linux editors it is \n and in apple editors it is \r. Use always one environment for the same. Preferably windows.
f. Use the source code management system property to take care of new lines correctly.
I believe I almost covered all the points required to write a OS/compiler independent code. Keep it as a practice and even if you have to work over a project which already has written OS/compiler dependent code try using the methodology I have given. Im sure your code will be reused by somebody with less effort if you implement the same coding methodology.