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:
- 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.
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 |
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 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);
#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:
- Directory where the executable is located (for example, notepad.exe in Windows)
- Current working directory (That is, the directory from which notepad.exe is launched.)
- Windows system directory (typically C:\Windows\System32)
- Windows directory (typically C:\Windows)
- 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 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.
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 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.
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.
Learn
-
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
-
Safari bookstore:
Visit this e-reference library to find specific technical resources.
-
developerWorks technical events and webcasts:
Stay current with developerWorks technical events and webcasts.
-
Podcasts: Tune in and
catch up with IBM technical experts.
Get products and technologies
-
IBM trial software:
Build your next development project with software for download directly from
developerWorks.
Discuss
- Participate in the
developerWorks blogs
and get involved in the developerWorks community.
- Participate in the AIX and UNIX forums:
- AIX—technical forum
- AIX 6 Open Beta
- AIX for Developers Forum
- Cluster Systems Management
- IBM Support Assistant
- Performance Tools—technical
- Virtualization—technical
- More AIX and UNIX forums
Rahul Kardam is a senior software developer specializing in complex C++-based electronic design automation tools such as simulators for hardware designs. He has programming experience in both Windows and UNIX platforms. Rahul enjoys tinkering with open source software, which he uses as a framework for designing robust, scalable code for the design automation tools he works on.
Comments (Undergoing maintenance)





