article

A Portable DLL/SO Solution

Email
Submitted on: 1/1/2015 12:26:00 PM
By: Gushing Wound (from psc cd)  
Level: Intermediate
User Rating: By 11 Users
Compatibility: C++ (general), Microsoft Visual C++, Borland C++, UNIX C++
Views: 3064
 
     The following section is a survey and overview of the means to provide portable ‘plug-in’ capabilities for applications. ‘Plug-In’s are compiled libraries from which symbols (functions, variables, objects) can be imported during run-time.

 
				

DLL's and SO's: Portable Solutions

The following section is a survey and overview of the means to provide ‘plug-in’ capabilities for applications. ‘Plug-In’s are compiled libraries from which symbols (functions, variables, objects) can be imported during run-time. On the Windows NT platform, these files are referred to as "dynamically linked libraries" and carry the extension ".dll". On the SGI machines (IRIX/UNIX), these files are referred to as "shared objects" and carry the extension ".so". The functionality and interface to these objects are very similar on both platforms. This section will overview both platforms’ implementation, highlighting similarities and differences between the two formats, and demonstrating preprocessor macros which will allow source files to compile correctly on both platforms. Only an overview is given as the actual mechanism each platform uses to implement this functionality is beyond the scope of this section. The usage and creation of these files will be described from the environment of Microsoft Visual Studio for Windows NT and a command line environment for SGI. This overview information is provided only to meet the requirements of the RFS simulator, and is not an in-depth discussion of the complete capabilities of DLL and SO libraries.

Overview

A dynamic link library (DLL) and a shared object (SO) provide the functionality needed to provided ‘plug-in’ capabilities to an application on the NT and UNIX platforms respectively. Implementation on these platforms is very similar, often differing by only a function name or a include file. The process to compile a linked library on both platforms is as follows

Creating, Linking and Compiling the DLL or SO:

  • Write the code for the DLL or SO. Identify the functions or variables that are to be available for the calling process.
  • Compile the source code into an object file.
  • Link that object file into either a DLL or SO.

Accessing the DLL or SO from a Calling Process:

  • Load the DLL or SO
  • Get a pointer to the exported function or variable
  • Utilize the exported function or variable
  • Close the library

Creating, Linking, and Compiling the DLL or SO

Source Files

All Unix object files are candidates for inclusion into a shared object library. No special export statements need to added to code to indicate exportable symbols, since all symbols are available to an interrogating process (the process which loads the SO/DLL).

In Windows NT, however, only the specified symbols will be exported (i.e., available to an interrogating process). Exportable objects are indicated by the including the keyword ‘__declspec(dllexport)’. The following examples demonstrate how to export variables and functions.

__declspec( dllexport ) void MyCFunc(); /* exporting function MyCFunc */

__declspec (dllexport) int MyVariable; /* exporting variable MyVariable */

Linking Objects into DLL/SO

Both DLL and SO files are linked from compiled object files. Under Visual Studio 6.0, a "Win32 DLL Project" can be created. All objects created from the source files in the project will be linked into the target DLL. Under Unix, the linking of object code into a shared library can be accomplished using the ‘-shared’ option of the linker executable ‘ld’. For example, the following command line can be used:

ld -64 -shared greetings.o -o greetings.so

This command (from the man pages) will compile a greetings.so shared object library from the greetings.o object file. The option ‘-64’ indicates 64 bit code, and affects the search path for the library (see below).

Accessing the DLL or SO from a Calling Process

System Header and Library Files

To use the shared objects in Unix, the include directive ‘#include <dlfcn.h>’ must be used. Under Windows NT, the include directive ‘#include <windows.h>’ must be used.

Loading the DLL/SO

In Unix, loading the SO file can be accomplished from the function dlopen(). The function prototype is

void* dlopen( const char *pathname, int mode )

The argument pathname is either the absolute or relative (from the current directory) path and filename of the .SO file to load. The argument mode is either the symbol RTLD_LAZY or RTLD_NOW. RTLD_LAZY will locate symbols in the file given by pathname as they are referenced, while RTLD_NOW will locate all symbols before returning. The function dlopen() will return a pointer to the handle to the opened library, or NULL if there is an error.

Under Windows, the function to load a library is given by

HINSTANCE LoadLibrary( LPCTSTR lpLibFileName );

In this case, the filename of executable module is pointed to by the argument lpLibFileName. This function returns a handle to the DLL (of type HISTANCE), or NULL if there is an error.

Search Paths for Dynamic/Shared Libraries

Under Unix, the shared object will be searched for in the following places.

  1. In the directory specified by the pathname argument to dlopen() if it is not a simple file name (i.e. it contains a character). In this case, the exact file is the only placed searched; steps two through four below are ignored.
  2. In any path specified via the -rpath argument to ld(1) when the executable was statically linked.
  3. In any directory specified by the environment variable LD_LIBRARY_PATH. If LD_LIBRARY_PATH is not set, 64-bit programs will also examine the variable LD_LIBRARY64_PATH, and new 32-bit ABI programs will examine the variable LD_LIBRARYN32_PATH to determine if an ABI-specific path has been specified. All three of these variables will be ignored if the process is running setuid or setgid.
  4. The default search paths will be used. These are /usr/lib:/lib for 32-bit programs, /usr/lib64:/lib64 for 64-bit programs, and /usr/lib32:/lib32 for new 32-bit ABI programs.

 

Under Windows, the shared object will be searched for in the following places.

  1. The directory from which the application loaded.
  2. The current directory.
  3. Windows 95 and Windows 98: The Windows system directory. Use theGetSystemDirectory function to get the path of this directory.
  4. Windows NT: The 32-bit Windows system directory. Use the GetSystemDirectory function to get the path of this directory. The name of this directory is SYSTEM32.
  5. Windows NT: The 16-bit Windows system directory. There is no function that obtains the path of this directory, but it is searched. The name of this directory is SYSTEM.
  6. The Windows directory. Use theGetWindowsDirectory function to get the path of this directory.
  7. The directories that are listed in the PATH environment variable.

Run-Time Access of Symbols from the DLL/SO

Under Unix, symbols can be referenced from a SO once the library is loaded using dlopen(). The function dlsym() will return a pointer to a symbol in the library.

void* dlsym( void* handle, const char *name);

The handle argument is the handle to the library returned by dlopen(). The name argument is a string containing the name of the symbol. The function returns a pointer to the symbol if it is found, and NULL if not or if there is an error.

Under Windows, the functions can be accessed with a call to GetProcAddress().

FARPROC GetProcAddress( HMODULE hModule, LPCSTR lpProcName);

The argument hModule is the handle to the module returned from LoadLibrary(). The argument lpProcName is the string containing the name of the function. This procedure returns the function pointer to the procedure is successful, else it returns NULL.

For example, suppose the function getProductOf() is defined in a Unix .so file, and is exported in a Windows .dll file. The prototype of the function is given by

float getProductOf( float number1, float number2 );

A typedef to the function pointer of this function is given by

typedef float (*LPFunctionType)(float, float);

This, of course, is the same for both platforms. Once the DLL/SO is loaded as described in the previous section, access to the function looks like (m_libraryHandle is the handle returned by dlopen in Unix or LoadLibrary in Windows)

LPFunctionType functionptr;

// UNIX

functionptr = (LPFunctionType)dlsym(m_libraryHandle, " getProductOf");

// NT

functionptr = (LPFunctionType) GetProcAddress(m_libraryHandle, "getProductOf");

Closing the DLL/SO

Closing the library is accomplished in Unix using the function dlclose, and in Windows using the function FreeLibrary. Note that these function return either a 0 or a non-zero value, but Windows returns 0 if there is an error. Unix returns 0 if successful.

In Unix, the library is closed with a call to dlclose.

int dlclose( void *handle );

The argument handle is the handle to the opened SO file (the handle returned by dlopen). This function returns 0 if successful, a non-zero value if not successful.

In Windows NT, the library is closed using the function Free Library.

BOOL FreeLibrary( HMODULE hLibModule );

The argument hLibModule is the handle to the loaded DLL library module. This function returns a non-zero value if the library closes successfully, and a 0 if there is an error.

MultiPlatform Example:

This example is a modification of the man pages of dlopen() and has not been compiled for testing. Note that this code is for example purposes, and is not robust (error-handling routines have been omitted, etc).

dltry.c:
------------------

#ifdef FOR_UNIX

#include <dlfcn.h>

#define GetFunctionFromModule dlsym

#define CloseModule dlclose

typedef (void*) LpHandleType;

#else

#include <windows.h>

#define GetFunctionFromModule GetProcAddress

#define CloseModule FreeLibrary

typedef HINSTANCE LpHandleType;

#endif

typedef int (*xamplefuncptr)(int);

int main()

{

LpHandleType handle;

int i;

xamplefuncptr fptr;

/* open the library- machine specific */

#ifdef FOR_UNIX

handle = dlopen("./greetings.so", RTLD_LAZY);

#else

handle = LoadLibrary("./greetings.dll");

#endif

/* get function is same thanks to macros */

fptr = (xamplefuncptr) GetFunctionFromModule(handle, "greetings");

 

i = (*fptr)(3);

/* close the module */

/* note, CloseModule returns 0 if error on NT, if success on UNIX */

CloseModule( handle );

return 0;

}

greetings.c:
----------------------

#include <stdio.h>

#ifdef FOR_UNIX

int greetings(int num_greetings)

#else

#include <windows.h>

int __declspec(dllexport) greetings(int num_greetings)

#endif

{

int i;

for (i=0; i < num_greetings; i++)

printf ("hello world0);

return 1;

}

Command Line- UNIX:
----------------------

% cc -32 -c -DFOR_UNIX dltry.c greetings.c

% ld -32 -shared greetings.o -o greetings.so

% cc -32 dltry.o

% setenv LD_LIBRARY_PATH .

% a.out

hello world

hello world

hello world

 


Other 1 submission(s) by this author

 


Report Bad Submission
Use this form to tell us if this entry should be deleted (i.e contains no code, is a virus, etc.).
This submission should be removed because:

Your Vote

What do you think of this article (in the Intermediate category)?
(The article with your highest vote will win this month's coding contest!)
Excellent  Good  Average  Below Average  Poor (See voting log ...)
 

Other User Comments


 There are no comments on this submission.
 

Add Your Feedback
Your feedback will be posted below and an email sent to the author. Please remember that the author was kind enough to share this with you, so any criticisms must be stated politely, or they will be deleted. (For feedback not related to this particular article, please click here instead.)
 

To post feedback, first please login.