Previous section   Next section

Imperfect C++ Practical Solutions for Real-Life Programming
By Matthew Wilson
Table of Contents
Chapter 9.  Dynamic Libraries


9.1. Calling Functions Explicitly

Before we delve into those three important issues, I want to talk a little more about C++ functions and dynamic linking. This is not something you'd often want to do in the real world, but working it through will inform on several issues we're going to cover, as well as add a little more to the ABI picture.

Operating environments that support dynamic linking generally support the two different-but-related mechanisms of implicit and explicit loading. Your compiler/linker and the operating environment handle implicit loading, and you can basically treat it all as automatic. Essentially, your executable has records placed within it that indicate the symbols it requires and which dynamic libraries they reside within. When the executable is loaded prior to execution all its dependencies are resolved. If any libraries are missing, or if any functions are missing from those libraries, the execution is halted.[1]

[1] Actually, some APIs allow for on-demand resolution of symbol fixups, which has the obvious pros and cons.

With explicit loading, your code issues a system call, for example, dlopen(), requesting that a given library is required. The operating system will then attempt to locate that file using its particular set of rules. For example, on Linux, it looks in the paths listed in the LD_LIBRARY_PATH environment variable, the system library cache, and the system library directories. Win32's LoadLibary() function has a considerably more convoluted algorithm, incorporating the application directory, the current directory, several system paths, and the paths listed in the PATH environment variable; it even involves the registry! [Rich1997] If the library is located and determined to be of a recognized executable format, it is then loaded into the address space of the calling process.

Once the operating system has loaded a library, you need to get access to the functions within. This is done by calling a search function, for example, dlsym(), passing the handle of the library, and a descriptor for the function you want to acquire. Such search functions generally return an opaque type, for example, void*, from which you cast to your desired function type. Clearly, it is your responsibility to know the signature of the function you intend to call.

Once you've finished with the function(s) from the library, you can tell the operating system to unload it, for example, by calling dlclose().

9.1.1 Calling C++ Functions Explicitly

The use of explicit loading of dynamic libraries is an area where C++'s piggybacking on C comes into sharp focus. When we wish to load and use a C function (from C or C++), it's really pretty simple (error-handling elided):

Listing 9.1.


// To call the function


//  char const *find_filename(char const *fileName);


// located in libpathfns.so





void print_filename(char const *fileName)


{


  char const *(*ffn)(char const *);


  void    *hLib = dlopen("libpathfns.so", RTLD_NOW);


  (void*&)(ffn) = dlsym(hLib, "find_filename");


  printf("%s => %s", path, find_filename(fileName));


  dlclose(hLib);


}



However, if find_filename() was a function with C++ linkage, we'd have to pass its mangled name to the call to dlsym(). With GCC on my Linux box, this is _Z13find_filenamePKc. Not too memorable, is it?

When you are dealing with class methods, the picture is a bit more complex. Consider the following class:



class Thing


{


public:


  void PublicMethod(char const *s);


private:


  void PrivateMethod(char const *s);


};



To load and execute PublicMethod() you would do something like the following:



Thing thing;


void  *lib = dlopen(. . .);


void  *pv = dlsym(lib, "_ZN5Thing12PublicMethodEPKc");


void  (Thing::*pubm)(char const *);


(void*&)pubm = pv;


(thing.*pubm)("Ugly ...");



This leaves much to be desired in terms of usability. Since different compilers operate different mangling schemes, we'd have a lot of conditional compilation, even if we stayed with one compiler for all link-units in our application.[2] This stuff is altogether too much of a pain to be a workable solution for anything but exceptional cases.

[2] I know of a chap who spent huge amounts of (his employer's) time and (his own) effort working on a multiplatform library that deduced the mangled names for various mangling schemes, and would, for a given platform, try several known schemes in order to try and locate a symbol. As with all such "good ideas," it had an unceremonious end for the usual reasons: no one had asked for it, no one needed it, and no one was prepared to use something so hideously complex.

9.1.2 Breaking C++ Access Control

Even though it's almost never the case that you do, let's assume for a moment that you have a good reason to call Thing's private method PrivateMethod(). Within the normal bounds of C++ you're pretty much out of luck. You can't do it via a derived class, since it's private, rather than protected. Moderately sneaky things like taking the address of it won't work either.



void (Thing::*pm)(char const*) = &Thing::PrivateMethod; // Error



However, if the implementation of Thing::PrivateMethod is in a dynamic library, you can call it via explicit loading, as in the following code compiled with Visual C++:



Thing     thing;


HINSTANCE lib = LoadLibrary(. . .);


void  *pv = dlsym(lib, " _ZN5Thing13PrivateMethodEPKc");


void (Thing::*prim)(char const *);


(void*&)prim = pv;





(thing.*prim)("Evil ...");



This is one of those techniques that you could be keelhauled for using without buy-in from someone higher up the food chain, but it can be a lifesaver if you're working with some truly truculent technology, and you have no way of recompiling its source, or insufficient masochism to do so.


      Previous section   Next section