Microsoft vs. Borland Portability Woes
I developed DirWalk using the Microsoft Visual C++ 4.2 compiler, but late in the project, I decided to go for Borland compilability as well (using version 5.01). Getting there was not an insignificant task, as indicated by the #if defined(__BORLANDC__) and #if defined(_MSC_VER) conditional-compilation macros scattered throughout the source code.
The Borland and Microsoft compilers differ especially in their treatment of exception-handling. Borland requires you to be using the std namespace to declare any of the standard exception types from <stdexcept>, while Microsoft does not. You end up with this in files using standard exception types:
#if defined(__BORLANDC__) using namespace std; #endif
Concerning exception specifications, Microsoft's compiler complains if you try to include an exception specification on the declaration of a non-inline constructor, even though the definition of that constructor has an exception specification. Borland's implementation handles this case more gracefully, by forcing the exception specifications to match between declaration and definition. Thus, you'll see code like this in a header file:
class X { public: #if defined(_MSC_VER) X(int x=0); #else X(int x=0) throw(); #endif };
And in the implementation file, just the one version of the definition:
X::X(int x) throw() { ... }
Strangely, if you inline the constructor, then you can use the exception specification in the same way under both products.
Multithreading support is the other big area of compiler differences I ran into on this project. The preprocessor symbols indicating a multithreaded compile are different. This is no big deal, but still it would be nice if the two compilers could agree on a common symbol. As it stands, you'll see stuff like this in DirWalk:
#if defined(__BORLANDC__) #if defined(__MT__) #if !defined(_MT) #define _MT #endif #endif #endif
Pretty, huh? It gets much worse.
Win32 has an API function CreateThread, which a program can use to create a separate thread of execution given the starting address of a function and some other data. But, in certain situations, each thread launched via CreateThread will produce small memory leaks [1] . To work around this problem, Microsoft and Borland have created their own functions to create threads. These functions both call CreateThread internally, and both clean up after the thread, but there the similarity just about ends. Here are the prototypes:
Win32:
HANDLE CreateThread( // pointer to thread security attributes LPSECURITY_ATTRIBUTES lpThreadAttributes, // initial thread stack size, in bytes DWORD dwStackSize, // pointer to thread function LPTHREAD_START_ROUTINE lpStartAddress, LPVOID lpParameter, // argument for new thread DWORD dwCreationFlags, // creation flags LPDWORD lpThreadId // pointer to returned thread identifier ;
Microsoft:
unsigned long _beginthreadex( void *security, unsigned stack_size, unsigned ( __stdcall *start_address )( void * ), void *arglist, unsigned initflag, unsigned *thrdaddr );
Borland:
unsigned long _beginthreadNT( void (_USERENTRY *start_address)(void *), unsigned stack_size, void *arglist, void *security_attrib, unsigned long create_flags, unsigned long *thread_id);
All three of these functions take essentially the same arguments, with the arguments to Borland's function having a different order for some reason. There are two principal differences between these functions. One difference is that CreateThread and _beginthreadNT take a pointer to an unsigned long for the thread ID, while _beginthreadex() takes a pointer to an unsigned int. But the most annoying difference is in the type of the function pointer that specifies the starting address of the new thread. The function passed to CreateThread must have this prototype:
DWORD WINAPI ThreadFunction(LPVOID ThreadParameter);
The function passed to _beginthreadex must have this prototype:
unsigned int WINAPI ThreadFunction(LPVOID ThreadParameter);
The function passed to _beginthreadNT() must have this prototype:
void ThreadFunction(LPVOID ThreadParameter);
So, not only must you redefine the calls to create the new thread, you must also redefine the thread function itself! (I would think that Microsoft would at least get agreement between the two functions for which it is responsible.) o
Note
[1] Specifically, if you're using the statically-linked multithreaded C runtime libraries, and if the thread calls certain C runtime functions that require a separate data area for each thread, memory leaks will occur. These functions include the errno-related functions, some time functions, strtok, tmpnam, tmpfile, and others.