一、介绍
如何使用
dlopen API
动态地加载
C++
函数和类,是
Unix C++
程序员经常碰到的问题。
事实上,情况偶尔有些复杂,需要一些解释。这正是写这篇
mini HOWTO
的缘由。
理解这篇文档的前提是对
C/C++
语言中
dlopen API
有基本的了解。
这篇
HOWTO
的维护链接是
:
http://www.isotton.com/howtos/C++-dlopen-mini-HOWTO/
二、问题所在
有时你想在运行时加载一个库(并使用其中的函数),这在你为你的程序写一些插件或模
块架构的时候经常发生。
在
C
语言中,加载一个库轻而易举(调用
dlopen
、
dlsym
和
dlclose
就够了),但对
C++
来说,情况稍微复杂。
动态加载一个
C++
库的困难一部分是因为
C++
的
name mangling
(译者注:也有人把它翻译为
“
名字毁坏
”
,我觉得还是不翻译好),
另一部分是因为
dlopen API
是用
C
语言实现的,因而没有提供一个合适的方式来装载类。
在解释如何装载
C++
库之前,最好再详细了解一下
name mangling
。
我推荐您了解一下它,即使您对它不感兴趣。因为这有助于您理解问题是如何产生的,如
何才能解决它们。
1. Name Mangling
在每个
C++
程序(或库、目标文件)中,
所有非静态(
non-static
)函数在二进制文件中都是以
“
符号(
symbol
)
”
形式出现的。
这些符号都是唯一的字符串,从而把各个函数在程序、库、目标文件中区分开来。
在
C
中,符号名正是函数名:
strcpy
函数的符号名就是
“strcpy”
,等等。
这可能是因为两个非静态函数的名字一定各不相同的缘故。
而
C++
允许重载(不同的函数有相同的名字但不同的参数),
并且有很多
C
所没有的特性
──
比如类、成员函数、异常说明
──
几乎不可能直接用函数名
作符号名。
为了解决这个问题,
C++
采用了所谓的
name mangling
。它把函数名和一些信息(如参数
数量和大小)杂糅在一起,
改造成奇形怪状,只有编译器才懂的符号名。
例如,被
mangle
后的
foo
可能看起来像
foo@4%6^
,或者,符号名里头甚至不包括
“foo”
。
其中一个问题是,
C++
标准(目前是
[ISO14882]
)并没有定义名字必须如何被
mangle
,
所以每个编译器都按自己的方式来进行
name mangling
。
有些编译器甚至在不同版本间更换
mangling
算法(尤其是
g++ 2.x
和
3.x
)。
即使您搞清楚了您的编译器到底怎么进行
mangling
的,从而可以用
dlsym
调用函数了,
但可能仅仅限于您手头的这个编译器而已,而无法在下一版编译器下工作。
三、类
使用
dlopen API
的另一个问题是,它只支持加载函数。
但在
C++
中,您可能要用到库中的一个类,而这需要创建该类的一个实例,这不容易做到。
四、解决方案
1. extern "C"
C++
有个特定的关键字用来声明采用
C binding
的函数:
extern "C"
。
用
extern "C"
声明的函数将使用函数名作符号名,就像
C
函数一样。
因此,只有非成员函数才能被声明为
extern "C"
,并且不能被重载。
尽管限制多多,
extern "C"
函数还是非常有用,因为它们可以象
C
函数一样被
dlopen
动态
加载。
冠以
extern "C"
限定符后,并不意味着函数中无法使用
C++
代码了,
相反,它仍然是一个完全的
C++
函数,可以使用任何
C++
特性和各种类型的参数。