problem
sometimes you want to load a library (and use the function) at runtime ), this often happens when you write some plug-ins or module architectures for your Program .
in C language, it is easy to load a library (it is enough to call dlopen, dlsym, and dlclose), but the situation is a little complicated for C ++. Part of the difficulty of loading a C ++ library dynamically is that C ++'s name mangling (Translator's note: Some people translate it into "name destruction", which I think is still not good at translation ), another part is that the dlopen API is implemented in C language, so it does not provide an appropriate method to load classes.
before explaining how to load the C ++ library, you 'd better learn more about name mangling. I recommend that you know about it, even if you are not interested in it. This helps you understand how problems arise and how they can be solved.
Name mangling
In each c ++ Program (or library or target file), all non-static (non-static) functions appear in the binary file in the form of a symbol. These symbols are unique strings that separate functions from each other in the program, library, and target file.
In C, the symbolic name is the function name: the symbolic name of the strcpy function is "strcpy", and so on. This may be because the names of the two non-static functions must be different.
C ++ allows overloading (different functions have the same name but different parameters ), and there are many features that C does not have-such as classes, member functions, and exception descriptions-it is almost impossible to directly use function names as symbol names. To solve this problem, C ++ adopts the so-called name mangling. It blends the function name and some information (such as the number and size of parameters) into a strange symbol name that only the compiler can understand. For example, the foo after mangle may look like Foo @ 4% 6 ^, or the symbol name does not even include "foo ".
One of the problems is that the C ++ standard (currently []) does not define how a name must be mangle, so each compiler uses its own method to perform name mangling. Some compilers even change mangling between different versions.Algorithm(Especially g ++ 2. x and 3.x ). Even if you have figured out how your compiler performs mangling, you can use dlsym to call the function, but it may be limited to your current compiler, but cannot work in the next version of the compiler.
Class
Another problem with using the dlopen API is that it only supports loading functions. However, in C ++, you may need to use a class in the library, and this requires creating an instance of this class, which is not easy to do.
Solution
Extern "C"
C ++ has a specific keyword used to declare the function using C binding: extern "C ". The function declared with extern "C" uses the function name as the symbol name, just like the C function. Therefore, only non-member functions can be declared as extern "c" and cannot be overloaded. Despite the many limitations, extern "C" functions are very useful because they can be dynamically loaded by dlopen like C functions. Given the extern "C" qualifier does not mean that C ++ cannot be used in functions.CodeOn the contrary, it is still a complete C ++ function that can use any c ++ features and various types of parameters.
Load Functions
In C ++, functions are loaded using dlsym, just like in C. However, this function must be declared with the extern "C" qualifier to prevent its symbolic name from being mangle.
Example 1. Load a function
Code:
--------------------------------------------------------------------------------
//----------
// Main. cpp:
//----------
# Include <iostream>
# Include <dlfcn. h>
Int main (){
Using STD: cout;
Using STD: cerr;
Cout <"C ++ dlopen demo \ n ";
// open the library
cout <"Opening hello. so... \ n ";
void * handle = dlopen (". /Hello. so ", rtld_lazy);
If (! Handle) {
cerr <"cannot open Library:" return 1;
}< br>
// load the symbol
cout <"loading symbol hello... \ n ";
typedef void (* hello_t) ();
// reset errors
dlerror ();
hello_t Hello = (hello_t) dlsym (handle, "hello ");
const char * dlsym_error = dlerror ();
If (dlsym_error) {
cerr <"cannot load symbol 'hello ': " '\ n';
dlclose (handle);
return 1;
}< br>
// use it to do the calculation
cout <"calling hello... \ n ";
Hello ();
// close the library
cout <" Closing library... \ n ";
dlclose (handle);
}
//----------
// Hello. cpp:
//----------
# Include <iostream>
Extern "C" Void Hello (){
STD: cout <"hello" <'\ n ';
}
--------------------------------------------------------------------------------
In hello. CPP, the hello function is defined as extern "C ". It is called by dlsym in Main. cpp. The function must be limited by extern "C"; otherwise, the symbol name is unknown.
Warning:
Extern "C" can be declared in two ways: the inline format used in the preceding example extern "C ", still use the extern "C" {...} in curly brackets "{...} this. The first inline form Declaration has two meanings: the external link (extern linkage) and the C language link (Language linkage), while the second method only affects the language link.
The following two statements are equivalent:
Code:
--------------------------------------------------------------------------------
Extern "C" int Foo;
Extern "C" Void bar ();
--------------------------------------------------------------------------------
And
Code:
--------------------------------------------------------------------------------
Extern "C "{
Extern int Foo;
Extern void bar ();
}
--------------------------------------------------------------------------------
For functions, the declaration of the extern and non-extern functions is no different, but the variables are different. If you declare variables, remember:
Code:
--------------------------------------------------------------------------------
Extern "C" int Foo;
--------------------------------------------------------------------------------
And
Code:
--------------------------------------------------------------------------------
Extern "C "{
Int Foo;
}
--------------------------------------------------------------------------------
It is a different thing (Translator's note: in short, the former is a declaration, while the latter is not only a declaration, but also a definition ).
For further explanations, refer to [7.5], 7th, especially section; or refer to [str2000], 9.2.4. Before you use the extern variable to search for victory, read the documents listed in the "Others" section.
Load class
Loading a class is a bit difficult, because we need an instance of the class, not just a function pointer. We cannot use new to create a class instance, because the class is not defined in the executable file, and (sometimes) We don't even know its name.
Solution: Exploitation of polymorphism! In the executable file, we define an interface base class with virtual member functions, and define a derived implementation class in the module. Generally, the interface class is abstract (if a class contains virtual functions, it is abstract ).
Dynamic Loading classes are often used to implement plug-ins, which means that a clearly defined interface must be provided-we will define an interface class and a derived implementation class.
Next, in the module, we will define two additional helper functions, which are known as "class factory functions )". One function creates a class instance and returns its pointer. The other function is used to destroy the pointer. Both functions are limited by extern "C.
To use the classes in the module, we use dlsym to load the two functions as in Example 1 with the hello function, and then we can create and destroy instances as needed.
Example 2. Load a class
We use a general polygon class as the interface, and inherit its triangle class (Translator's note: Positive triangle class) as the implementation.
Code:
--------------------------------------------------------------------------------
//----------
// Main. cpp:
//----------
# Include "polygon. HPP"
# Include <iostream>
# Include <dlfcn. h>
Int main (){
Using STD: cout;
Using STD: cerr;
// Load the triangle Library
Void * triangle = dlopen ("./triangle. So", rtld_lazy );
If (! Triangle ){
Cerr <"cannot load Library:" <dlerror () <'\ n ';
Return 1;
}
// reset errors
dlerror ();
// load the symbols
create_t * create_triangle = (create_t *) dlsym (triangle, "Create");
const char * dlsym_error = dlerror ();
If (dlsym_error) {
cerr <"cannot load symbol create: " return 1;
}< br>
destroy_t * destroy_triangle = (destroy_t *) dlsym (triangle, "Destroy");
dlsym_error = dlerror ();
If (dlsym_error) {
cerr <"cannot load symbol destroy: " return 1;
}
// Create an instance of the class
Polygon * poly = create_triangle ();
// Use the class
Poly-> set_side_length (7 );
Cout <"the area is:" <poly-> area () <'\ n ';
// Destroy the class
Destroy_triangle (poly );
// Unload the triangle Library
Dlclose (triangle );
}
//----------
// Polygon. HPP:
//----------
# Ifndef polygon_hpp
# Define polygon_hpp
Class polygon {
Protected:
Double side_length _;
Public:
Polygon ()
: Side_length _ (0 ){}
Virtual ~ Polygon (){}
Void set_side_length (double side_length ){
Side_length _ = side_length;
}
Virtual double area () const = 0;
};
// The types of the class factories
Typedef polygon * create_t ();
Typedef void destroy_t (polygon *);
# Endif
//----------
// Triangle. cpp:
//----------
# Include "polygon. HPP"
# Include <cmath>
Class triangle: Public polygon {
Public:
Virtual double area () const {
Return side_length _ * SQRT (3)/2;
}
};
// The class factories
Extern "C" polygon * Create (){
Return new triangle;
}
Extern "C" Void destroy (polygon * P ){
Delete P;
}
--------------------------------------------------------------------------------
Note the following when loading a class:
◆ You must provide both a creation function and a destruction function in a module or shared library, and cannot use Delete to destroy instances within the execution file, the destroy function that can only pass the instance pointer to the module for processing. This is because in C ++, the new operator can be overloaded; this can easily lead to mismatched calls of New-delete, resulting in inexplicable memory leaks and segment errors. This is also true when linking modules and executable files with different standard libraries.
◆ The destructor of the interface class must be virtual in all circumstances ). Even if the error may be extremely small, it is almost worrying, but it is still not worth taking the risk. The extra overhead is negligible. If the base class does not require destructor, define an empty (but virtual) destructor. Otherwise, you will encounter problems sooner or later. I promise you. You can learn more about this issue in section 20th of the comp. Lang. c ++ FAQ (http://www.parashift.com/c++-faq-lite.
Source code
You can download all the code packages included in this document: http://www.isotton.com/howtos/C++-dl...xamples.tar.gz