You are on page 1of 9

Windows to UNIX porting, Part 1: Porting C/C++

sources
Demystifying the process of porting a C/C++-based project from
Windows to UNIX

Rahul Kumar Kardam September 18, 2007

Software programs are often made to run on systems that are completely different from the
system in which the program is coded or developed. This process of adapting software across
systems is known as porting. You might need to port software for any one of several reasons.
Perhaps your end users want to use the software in a new environment, such as a different
version of UNIX®, or perhaps your developers are integrating their own code into the software
to optimize it for your organization's platform.

View more content in this series

Porting from Windows to a UNIX environment


Most Microsoft® Windows®-based projects are built using Microsoft Visual Studio®, which has
a sophisticated integrated development environment (IDE) that automates almost the entire
build process for the developer. In addition, Windows developers use Windows platform-specific
application program interfaces (APIs), headers, and language extensions. Most UNIX®-like
systems, such as SunOS, OpenBSD, and IRIX, don't support an IDE or any Windows-specific
headers or extensions, thereby making porting a time-consuming activity. To make matters
more complicated, legacy Windows-based code was meant to be run on 16-bit or 32-bit x86
architecture. UNIX-based environments are often 64-bit, and most UNIX vendors don't support the
x86 instruction set. This article, the first in a two-part series, demystifies the process of porting a
typical Visual C++ project in a Windows operating system to a g++ environment in SunOS while
addressing the aforementioned issues in some detail.

C/C++ project types in Visual Studio


You can use a Visual C++ project to create one of three variants (single or multi-threaded) of a
project:

© Copyright IBM Corporation 2007 Trademarks


Windows to UNIX porting, Part 1: Porting C/C++ sources Page 1 of 9
developerWorks® ibm.com/developerWorks/

• Dynamic-link library (DLL or .dll)


• Static library (LIB or .lib)
• Executable (.exe)

For more complex variants, use a Visual Studio .NET solution—this solution makes it possible
to create and manage multiple projects. The next couple of sections in this document focus on
porting dynamic and static library project variants from Windows to UNIX.

Porting a DLL to a UNIX environment


The UNIX equivalent of a .dll file in Windows is a shared object (.so) file. However, the process of
creating a .so file is rather different from that of creating a .dll file. Consider the example in Listing
1, where you try to create a small .dll file that has a single function, printHello, which is called
from the main routine in the main.cpp file.

Listing 1. File hello.h containing the declaration for the printHello routine
#ifdef BUILDING_DLL
#define PRINT_API __declspec(dllexport)
#else
#define PRINT_API __declspec(dllimport)
#endif

extern "C" PRINT_API void printHello();

Listing 2 provides the source code for hello.cpp.

Listing 2. File hello.cpp


#include <iostream>
#include "hello.h"

void printHello
{
std::cout << "hello Windows/UNIX users\n";
}

extern "C" PRINT_API void printHello();

If you use the Microsoft 32-bit C/C++ standard compiler for 80x86 platforms (cl), the following
command creates the hello.dll file:
cl /LD hello.cpp /DBUILDING_DLL

/LD instructs cl to create a .dll file. (It can be instructed to create other formats such as .exe
or .obj.) /DBUILDING_DLL defines the PRINT_API macro for this particular building process so that
the printHello symbol is exported from this DLL.

Listing 3 contains the main.cpp main source file, which uses the printHello routine. The assumption
here is that hello.h, hello.cpp, and main.cpp are all in the same folder.

Windows to UNIX porting, Part 1: Porting C/C++ sources Page 2 of 9


ibm.com/developerWorks/ developerWorks®

Listing 3. Main sources using the printHello routine


#include "hello.h"

int main ( )
{
printHello();
return 0;
}

To compile and link the main code, use the following command line:
cl main.cpp hello.lib

A quick inspection of the sources and generated output reveals two important facts. First, the
Windows-specific syntax, __declspec(dllexport), is needed to export any functions, variables, or
classes from a DLL. Likewise, the Windows-specific syntax, __declspec(dllimport), is needed to
import any functions, variables, or classes from a DLL. And second, the compilation generates two
files: printHello.dll and printHello.lib. PrintHello.lib is used to link the main sources, and the UNIX
headers for shared objects don't need the declspec syntax. The output of a successful compilation
is a single .so file that gets linked with the main sources.

To create a shared library in UNIX platforms using g++, compile all source files as relocatable
shared objects by passing the -fPIC flag to g++. PIC stands for position independent code.
A shared library is potentially mapped to a new memory address every time it gets loaded.
Therefore, it makes sense to generate the addresses of all variables and functions inside the
library in a way that can be easily computed relative to the start address that the library is loaded
to. This code is generated by the -fPIC option and makes the code relocatable. The -o option is
used to specify the name of an output file, and the -shared option builds a shared library in which
unresolved references are allowed. To create the hello.so file, you must modify the header, as
shown in Listing 4 below.

Listing 4. Modified header for hello.h with UNIX-specific changes


#if defined (__GNUC__) && defined(__unix__)
#define PRINT_API __attribute__ ((__visibility__("default")))
#elif defined (WIN32)
#ifdef BUILDING_DLL
#define PRINT_API __declspec(dllexport)
#else
#define PRINT_API __declspec(dllimport)
#endif

extern "C" PRINT_API void printHello();

And here's the g++ command for linking the shared library hello.so:
g++ -fPIC -shared hello.cpp -o hello.so

To create the main executable, compile the sources:


g++ -o main main.cpp hello.so

Windows to UNIX porting, Part 1: Porting C/C++ sources Page 3 of 9


developerWorks® ibm.com/developerWorks/

Symbol hiding in g++


There are two typical ways to export symbols from a Windows-based DLL. The first method is to
use __declspec(dllexport) only on select elements (for example, classes, global variables, or
global functions) that are exported from the DLL. The second method is to use a module-definition
(.def) file. .def files have their own syntax and contain the symbols that need to be exported from
the DLL.

The default behavior of the g++ linker is to export all the symbols from a .so file. This might not
be desirable, and it makes linking multiple DLLs a time-consuming task. To selectively export
symbols from a shared library, use the g++ attribute mechanism. For example, consider that the
user sources have two methods, 'void print1();' and ' int print2(char*);', and the user
needs to export print2 only. Listing 5 encloses a means of achieving this in both Windows and
UNIX.

Listing 5. Hiding symbols in g++


#ifdef _MSC_VER // Visual Studio specific macro
#ifdef BUILDING_DLL
#define DLLEXPORT __declspec(dllexport)
#else
#define DLLEXPORT __declspec(dllimport)
#endif
#define DLLLOCAL
#else
#define DLLEXPORT __attribute__ ((visibility("default")))
#define DLLLOCAL __attribute__ ((visibility("hidden")))
#endif

extern "C" DLLLOCAL void print1(); // print1 hidden


extern "C" DLLEXPORT int print2(char*); // print2 exported

Using __attribute__ ((visibility("hidden"))) prevents symbol exporting from a DLL. The


latest versions of g++ (4.0.0 and higher) also provide the -fvisibility switch, which you can
use to selectively export symbols from a shared library. Using g++ with -fvisibility=hidden in a
command line suspends exporting of all symbols from a shared library, except for those that have
been declared with __attribute__ ((visibility("default"))). This is a neat way of telling g++
that every declaration that is not explicitly marked with a visibility attribute has a hidden visibility.
Using dlsym to extract a hidden symbol returns NULL.

Overview of the attribute mechanism in g++


Much like the Visual Studio environment, which provides a lot of additional syntax on top of C/
C++, g++ supports many non-standard extensions to the language. One of these, the attribute
mechanism in g++, is handy for porting purposes. The previous example discussed symbol hiding.
Yet another use of attributes is to set function types, such as cdecl, stdcall, and fastcall, to
Visual C++. Part 2 of this series discusses the attribute mechanism in greater detail.

Explicit DLL or shared object loading in a UNIX environment


In Windows systems, it is quite common to for a .dll file to be explicitly loaded by a Windows
program. For example, consider a sophisticated Windows-based editor that has printing

Windows to UNIX porting, Part 1: Porting C/C++ sources Page 4 of 9


ibm.com/developerWorks/ developerWorks®

capabilities. Such an editor would dynamically load the DLL for the printer driver the first time
a user makes the corresponding request. Windows-based developers use the Visual Studio-
provided APIs, such as LoadLibrary, to explicitly load a DLL, GetProcAddress to query for a
symbol from the DLL, and FreeLibrary to unload an explicitly loaded DLL. The UNIX equivalents
for the same functions are the dlopen, dlsym, and dlclose routines. Further, in Windows, there's
a special DllMain method that is invoked the first time the DLL is loaded onto memory. UNIX-like
systems have a corresponding method called _init.

Consider a variant of the previous example. Listing 6 is the loadlib.h header file, which is used in
the sources that call the main method.

Listing 6. Header file loadlib.h


#ifndef __LOADLIB_H
#define __LOADLIB_H

#ifdef UNIX
#include <dlfcn.h>
#endif

#include <iostream>
using namespace std;

typedef void* (*funcPtr)();

#ifdef UNIX
# define IMPORT_DIRECTIVE __attribute__((__visibility__("default")))
# define CALL
#else
# define IMPORT_DIRECTIVE __declspec(dllimport)
# define CALL __stdcall
#endif

extern "C" {
IMPORT_DIRECTIVE void* CALL LoadLibraryA(const char* sLibName);
IMPORT_DIRECTIVE funcPtr CALL GetProcAddress(
void* hModule, const char* lpProcName);
IMPORT_DIRECTIVE bool CALL FreeLibrary(void* hLib);
}

#endif

The main method now explicitly loads the printHello.dll file and invokes the print method for the
same, as shown in Listing 7.

Listing 7. Main file Loadlib.cpp


#include "loadlib.h"

int main(int argc, char* argv[])


{
#ifndef UNIX
char* fileName = "hello.dll";
void* libraryHandle = LoadLibraryA(fileName);
if (libraryHandle == NULL)
cout << "dll not found" << endl;
else // make a call to "printHello" from the hello.dll
(GetProcAddress(libraryHandle, "printHello"))();
FreeLibrary(libraryHandle);

Windows to UNIX porting, Part 1: Porting C/C++ sources Page 5 of 9


developerWorks® ibm.com/developerWorks/

#else // unix
void (*voidfnc)();
char* fileName = "hello.so";
void* libraryHandle = dlopen(fileName, RTLD_LAZY);
if (libraryHandle == NULL)
cout << "shared object not found" << endl;
else // make a call to "printHello" from the hello.so
{
voidfnc = (void (*)())dlsym(libraryHandle, "printHello");
(*voidfnc)();
}
dlclose(libraryHandle);
#endif

return 0;
}

DLL search path in Windows and UNIX environments


In Windows operating systems, a DLL is searched in the following order:

1. Directory where the executable is located (for example, notepad.exe in Windows)


2. Current working directory (That is, the directory from which notepad.exe is launched.)
3. Windows system directory (typically C:\Windows\System32)
4. Windows directory (typically C:\Windows)
5. Directories listed as part of the PATH environment variable

In UNIX-like systems, such as Solaris, the LD_LIBRARY_PATH environment variable specifies


the shared library search order. The path to a new shared library needs to be appended to the
LD_LIBRARY_PATH variable. The search order for HP-UX involves directories listed as part of
LD_LIBRARY_PATH followed by those in SHLIB_PATH. For IBM AIX® operating systems, it's the
LIBPATH variable that determines the shared library search order.

Porting a static library from Windows to UNIX


The object code of static libraries, as opposed to dynamic link libraries, is linked when the
application compiles and, as such, becomes a part of the application. Static libraries in UNIX
systems follow a naming convention, where lib is prefixed and .a is suffixed to the library name.
For example, the Windows user.lib file would typically be named libuser.a in a UNIX system. The
operating system-provided commands ar and ranlib are used to create static libraries. Listing
8 illustrates how to create a static library, libuser.a, from the user_sqrt1.cpp and user_log1.cpp
source files.

Listing 8. Creating a static library in a UNIX environment


g++ -o user_sqrt1.o -c user_sqrt1.cpp
g++ -o user_log1.o -c user_log1.cpp
ar rc libuser.a user_sqrt1.o user_log1.o
ranlib libuser.a

The ar tool creates a static library, libuser.a, and puts copies of the user_sqrt1.o and user_log1.o
object files in it. If there is an existing library file, the object files are added to it. If the object
files being used are newer than those inside the library, the older ones are replaced. The r flag

Windows to UNIX porting, Part 1: Porting C/C++ sources Page 6 of 9


ibm.com/developerWorks/ developerWorks®

replaces older object files in the library with newer versions of the same object files. If it doesn't
exist yet, the c option creates the library.

After a new archive is created or an existing one is modified, an index of archive contents needs to
be created and stored as part of the archive. The index lists each symbol, defined by a member of
an archive, that is a relocatable object file. The index speeds up linking with the static library and
allows routines in the library to be called, irrespective of their actual placement inside the library.
Note that the GNU ranlib is an extension of the ar tool, and invoking ar with an s argument, [ar -
s], has the same effect as invoking ranlib.

Precompiled headers
C/C++-based applications in Visual C++ often use precompiled headers. Precompiled headers
are a performance feature that certain compilers, such as cl in Visual Studio, provide to help
speed up compilation. Complex applications often make use of header (.h or .hpp) files, which are
sections of code that are meant to be included as part of one or more source files. The header files
are modified only rarely during the scope of a project. Thus, to speed up compilation, these files
can be converted into an intermediate form that is easier for the compiler to understand so that
subsequent compilations are faster. This intermediate form is called precompiled header files or
PCH in the Visual Studio environment.

Consider the example involving hello.cpp in Listings 1 and 2 earlier in this article. The inclusion of
iostream and the definition of the EXPORT_API macro can be considered code-invariant parts of the
file throughout the scope of the project. Thus, they are good candidates for inclusion in a header
file. Listing 9 shows what the code looks like with the relevant changes.

Listing 9. Contents of precomp.h


#ifndef __PRECOMP_H
#define __PRECOMP_H

#include <iostream>

# if defined (__GNUC__) && defined(__unix__)


# define EXPORT_API __attribute__((__visibility__("default")))
# elif defined WIN32
# define EXPORT_API __declspec(dllexport)
# endif

Listing 10 shows the source code of the DLL with the relevant changes.

Listing 10. Contents of new hello.cpp file


#include "precomp.h"
#pragma hdrstop

extern "C" EXPORT_API void printHello()


{
std::cout << "hello Windows/UNIX users" << std::endl;
}

As the name suggests, a precompiled header file contains object code in a compiled form that is
included before the header stop point. This point in the source file is usually marked by a lexeme

Windows to UNIX porting, Part 1: Porting C/C++ sources Page 7 of 9


developerWorks® ibm.com/developerWorks/

that is not consumed as a token by the preprocessor, meaning one that is not a preprocessor
directive. Alternatively, this header stop point can also be specified as #pragma hdrstop, if it is
encountered in the sources before a valid non-preprocessor language keyword in the source text.

In a Solaris build, a precompiled header file is searched for when #include is in the compilation.
As it searches for the included file, the compiler looks for a precompiled header in each directory
just before it looks for the include file in that directory. The name searched for is the name
specified in the #include with .gch appended. If the precompiled header file can't be used, it is
ignored.

Here is the command line for achieving precompiled header facility in Windows:
cl /Yc precomp.h hello.cpp /DWIN32 /LD

/Yc tells the cl compiler to generate the precompiled header from precomp.h. The same
functionality is achieved in Solaris using the following lines:
g++ precomp.h
g++ -fPIC -G hello.cpp -o hello.so

The first command creates the precompiled header precomp.h.gch. The rest of the procedure for
generating the shared object is the same as described earlier in the article.

Note: Support for precompiled headers in g++ is available for versions 3.4 and above.

Conclusion
Porting across two completely divergent systems, such ase Windows and UNIX, is never an easy
task and, as such, it requires a lot of tweaking and patience. This article explained the essentials
of porting the most basic project types from a Visual Studio environment to a g++/Solaris-based
one. The second and concluding article in this series discusses the multitude of compiler options
available in the Visual Studio environment and their g++ equivalents, the g++ attribute mechanism,
some of the problems porting from a 32-bit (typically Windows) to a 64-bit (UNIX) environment,
and multithreading.

Windows to UNIX porting, Part 1: Porting C/C++ sources Page 8 of 9


ibm.com/developerWorks/ developerWorks®

Related topics
• Microsoft Developer Network: This site provides documentation of dynamic-link library
functions.
• Making C++ Loadable Modules Work: Frank Pilhofer discusses this topic in detail.
• Building And Using Static And Shared 'C' Libraries: Check out this tutorial on the Little Unix
Programmers Group (LUPG)'s Little site.
• KAI C++ User's Guide: Refer to this guide for a detailed description of precompiled headers.
• GCC online documentation: This site provides manuals for the latest releases of GCC/G++.
• AIX and UNIX: The AIX and UNIX developerWorks zone provides a wealth of information
relating to all aspects of AIX systems administration and expanding your UNIX skills.
• New to AIX and UNIX?: Visit the "New to AIX and UNIX" page to learn more about AIX and
UNIX.
• AIX Wiki: A collaborative environment for technical information related to AIX.
• Search the AIX and UNIX library by topic:
• System administration
• Application development
• Performance
• Porting
• Security
• Tips
• Tools and utilities
• Java™ technology
• Linux®
• Open source
• IBM trial software: Build your next development project with software for download directly
from developerWorks.

© Copyright IBM Corporation 2007


(www.ibm.com/legal/copytrade.shtml)
Trademarks
(www.ibm.com/developerworks/ibm/trademarks/)

Windows to UNIX porting, Part 1: Porting C/C++ sources Page 9 of 9

You might also like