上一节中,我们介绍了插件作为动态库的加载,其中我们注意到
函数:
void* dlsym(void* handle,const char* symbol)
返回的是【symbol对应的地址】。
因此,在我们开发的插件中,SetupPlugin和RemovePlugin函数需要添加extern “C” :
extern "C" Plugin* SetupPlugin()
{
return new MyPlugin();
}
extern "C" Plugin* RemovePlugin(Plugin *plugin)
{
delete plugin;
}
为什么呢?
这就得提一提C++的Name Mangling了:
在每个C++程序(或库、目标文件)中,所有非静态(non-static)函数在二进制文件中都是以“符号(symbol)”形式出现的。这些符号都是唯一的字符串,从而把各个函数在程序、库、目标文件中区分开来。
在C中,符号名就是函数名:foo函数的符号名就是“foo”。因为两个非静态函数的名字一定不相同,因此这是可行的。
在C++中,由于允许重载(不同的函数有相同的名字但不同的参数),并且有很多C所没有的特性──比如类、成员函数等等,所以几乎不可能直接用函数名作符号名。为解决这个问题,C++采用了所谓的name mangling——它把函数名和一些信息(如参数数量和大小)杂糅在一起,改造成奇形怪状,只有编译器才懂的符号名。例如,被mangle后的foo可能看起来像foo@4%6^,或者,符号名里头甚至不包括“foo”。而且,C++标准并没有定义名字必须如何被mangle,所以每个编译器都按自己的方式来进行name mangling。有些编译器甚至在不同版本间更换mangling算法(尤其是g++ 2.x和3.x)。即使你搞清楚了你的编译器到底怎么进行mangling的,从而可以用dlsym调用函数了,但这是与编译器相关的,可移植性不强。
解决方案
使用extern “C”
C++有个特定的关键字用来声明采用C binding的函数:extern “C” 。 用 extern “C”声明的函数将使用函数名作符号名,就像C函数一样。
当然,只有非成员函数才能被声明为extern “C”,并且不能被重载。
这样子,它们就可以像C函数一样被dlopen动态加载了。
加上extern “C”限定符后,并不意味着函数中无法使用C++代码了,事实上,它仍然是一个C++函数,可以使用任何C++特性。