I'm trying to write a program to use a static library of a C++ code into another C++ code. The first C++ code is hello.cpp
:
#include <iostream>
#include <string.h>
using namespace std;
extern "C" void say_hello(const char* name) {
cout << "Hello " << name << "!\n";
}
int main(){
return 0;
}
The I made a static library from this code, hello.a
, using this command:
g++ -o hello.a -static -fPIC hello.cpp -ldl
Here's the second C++ code to use the library, say_hello.cpp
:
#include <iostream>
#include <string>
#include <dlfcn.h>
using namespace std;
int main(){
void* handle = dlopen("./hello.a", RTLD_LAZY);
cout<<handle<<"\n";
if (!handle) {
cerr<<"Cannot open library: "<<dlerror()<<'\n';
return 1;
}
typedef void (*hello_t)();
dlerror(); // reset errors
hello_t say_hello = (hello_t) dlsym(handle, "say_hello");
const char *dlsym_error = dlerror();
if (dlsym_error) {
cerr<<"Cannot load symbol 'say_hello': "<<dlsym_error<<'\n';
dlclose(handle);
return 1;
}
say_hello("World");
dlclose(handle);
return 0;
}
Then I compiled say_hello.cpp
using:
g++ -W -ldl say_hello.cpp -o say_hello
and ran ./say_hello
in the command line. I expected to get Hello World!
as output, but I got this instead:
0x8ea4020
Hello ▒▒▒▒!
What is the problem? Is there any trick to make compatibility for method's argument like what we use in ctypes
or what?
If it helps I use a lenny.
EDIT 1:
I have changed the code and used a dynamic library, 'hello.so', which I've created using this command:
g++ -o hello.so -shared -fPIC hello.cpp -ldl
The 6th line of the code changed to:
void* handle = dlopen("./hello.so", RTLD_LAZY);
When I tried to compile say_hello.cpp
, I got this error:
say_hello.cpp: In function ‘int main()’:
say_hello.cpp:21: error: too many arguments to function
I also tried to compile it using this line:
g++ -Wall -rdynamic say_hello.cpp -ldl -o say_hello
But same error raised. So I removed the argument "World"
and the it has been compiled with no error; but when I run the executable, I get the same output like I have mentioned before.
EDIT 2:
Based on @Basile Starynkevitch 's suggestions, I changed my say_hello.cpp
code to this:
#include <iostream>
#include <string>
#include <dlfcn.h>
using namespace std;
int main(){
void* handle = dlopen("./hello.so", RTLD_LAZY);
cout<<handle<<"\n";
if (!handle) {
cerr<<"Cannot open library: "<<dlerror()<<'\n';
return 1;
}
typedef void hello_sig(const char *);
void* hello_ad = dlsym(handle, "say_hello");
if (!hello_ad){
cerr<<"dlsym failed:"<<dlerror()<<endl;
return 1;
}
hello_sig* fun = reinterpret_cast<hello_sig*>(hello_ad);
fun("from main");
fun = NULL;
hello_ad = NULL;
dlclose(handle);
return 0;
}
Before that, I used below line to make a .so
file:
g++ -Wall -fPIC -g -shared hello.cpp -o hello.so
Then I compiled say_hello.cpp
wth this command:
g++ -Wall -rdynamic -g say_hello.cc -ldl -o say_hello
And then ran it using ./say_hello
. Now everything is going right. Thanks to @Basile Starynkevitch for being patient about my problem.
Functions never have null addresses, so
dlsym
on a function name (or actually on any name defined in C++ or C) cannot beNULL
without failing:And dlopen(3) is documented to dynamically load only dynamic libraries (not static ones!). This implies shared objects (
*.so
) in ELF format. Read Drepper's paper How To Use Shared LibrariesI believe you might have found a bug in
dlopen
(see also its POSIX dlopen specification); it should fail for a static libraryhello.a
; it is always used on position independent shared libraries (likehello.so
).You should
dlopen
only position independent code shared objects compiled withor if you have several C++ source files:
you could remove the
-O
optimization flag or add-g
for debugging or replace it with-O2
if you want.and this works extremely well: my MELT project (a domain specific language to extend GCC) is using this a lot (generating C++ code, forking a compilation like above on the fly, then
dlopen
-ing the resulting shared object). And my manydl.c example demonstrates that you candlopen
a big lot of (different) shared objects on Linux (typically millions, and hundred of thousands at least). Actually the limitation is the address space.BTW, you should not
dlopen
something having amain
function, sincemain
is by definition defined in the main program calling (perhaps indirectly)dlopen
.Also, order of arguments to
g++
matters a lot; you should compile the main program withThe
-rdynamic
flag is required to let the loaded plugin (hello.so
) call functions from inside yoursay_hello
program.For debugging purposes always pass
-Wall -g
tog++
above.BTW, you could in principle
dlopen
a shared object which don't have PIC (i.e. was not compiled with-fPIC
); but it is much better todlopen
some PIC shared object.Read also the Program Library HowTo and the C++ dlopen mini-howto (because of name mangling).
example
File
helloshared.cc
(my tiny plugin source code in C++) isand I am compiling it with:
The main program is in file
mainhello.cc
:which I compile with
Then I am running
./mainhello
with the expected output:Please notice that the signature
hello_sig_t
inmainhello.cc
should be compatible (homomorphic, i.e. the same as) with the functionsay_hello
of thehelloshared.cc
plugin, otherwise it is undefined behavior (and you probably would have aSIGSEGV
crash).