<!-- /* Font Definitions */ @font-face {font-family:宋体; panose-1:2 1 6 0 3 1 1 1 1 1; mso-font-alt:SimSun; mso-font-charset:134; mso-generic-font-family:auto; mso-font-pitch:variable; mso-font-signature:3 135135232 16 0 262145 0;} @font-face {font-family:黑体; panose-1:2 1 6 0 3 1 1 1 1 1; mso-font-alt:SimHei; mso-font-charset:134; mso-generic-font-family:modern; mso-font-format:other; mso-font-pitch:fixed; mso-font-signature:1 135135232 16 0 262144 0;} @font-face {font-family:"/@宋体"; panose-1:2 1 6 0 3 1 1 1 1 1; mso-font-charset:134; mso-generic-font-family:auto; mso-font-pitch:variable; mso-font-signature:3 135135232 16 0 262145 0;} @font-face {font-family:"/@黑体"; panose-1:2 1 6 0 3 1 1 1 1 1; mso-font-charset:134; mso-generic-font-family:auto; mso-font-pitch:variable; mso-font-signature:1 135135232 16 0 262144 0;} /* Style Definitions */ p.MsoNormal, li.MsoNormal, div.MsoNormal {mso-style-parent:""; margin:0cm; margin-bottom:.0001pt; text-align:justify; text-justify:inter-ideograph; mso-pagination:none; font-size:10.5pt; mso-bidi-font-size:12.0pt; font-family:"Times New Roman"; mso-fareast-font-family:宋体; mso-font-kerning:1.0pt;} h1 {mso-style-next:正文; margin-top:17.0pt; margin-right:0cm; margin-bottom:16.5pt; margin-left:0cm; text-align:justify; text-justify:inter-ideograph; line-height:240%; mso-pagination:lines-together; page-break-after:avoid; mso-outline-level:1; font-size:22.0pt; font-family:"Times New Roman"; mso-font-kerning:22.0pt;} h2 {mso-style-next:正文; margin-top:13.0pt; margin-right:0cm; margin-bottom:13.0pt; margin-left:0cm; text-align:justify; text-justify:inter-ideograph; line-height:173%; mso-pagination:lines-together; page-break-after:avoid; mso-outline-level:2; font-size:16.0pt; font-family:Arial; mso-fareast-font-family:黑体; mso-bidi-font-family:"Times New Roman"; mso-font-kerning:1.0pt;} h3 {mso-style-next:正文; margin-top:13.0pt; margin-right:0cm; margin-bottom:13.0pt; margin-left:0cm; text-align:justify; text-justify:inter-ideograph; line-height:173%; mso-pagination:lines-together; page-break-after:avoid; mso-outline-level:3; font-size:16.0pt; font-family:"Times New Roman"; mso-font-kerning:1.0pt;} a:link, span.MsoHyperlink {color:blue; text-decoration:underline; text-underline:single;} a:visited, span.MsoHyperlinkFollowed {color:purple; text-decoration:underline; text-underline:single;} pre {margin:0cm; margin-bottom:.0001pt; mso-pagination:widow-orphan; background:#EEEEEE; border:none; mso-border-alt:solid #999999 .75pt; padding:0cm; mso-padding-alt:7.0pt 7.0pt 7.0pt 7.0pt; font-size:9.0pt; font-family:"Courier New"; mso-fareast-font-family:黑体;} tt {mso-ascii-font-family:黑体; mso-fareast-font-family:黑体; mso-hansi-font-family:"Courier New"; mso-bidi-font-family:"Courier New";} span.codekeyword1 {mso-style-name:codekeyword1; color:blue;} /* Page Definitions */ @page {mso-page-border-surround-header:no; mso-page-border-surround-footer:no;} @page Section1 {size:612.0pt 792.0pt; margin:72.0pt 90.0pt 72.0pt 90.0pt; mso-header-margin:36.0pt; mso-footer-margin:36.0pt; mso-paper-source:0;} div.Section1 {page:Section1;} /* List Definitions */ @list l0 {mso-list-id:20203860; mso-list-type:hybrid; mso-list-template-ids:1846063014 -1082597686 67698713 67698715 67698703 67698713 67698715 67698703 67698713 67698715;} @list l0:level1 {mso-level-text:%1.; mso-level-tab-stop:18.0pt; mso-level-number-position:left; margin-left:18.0pt; text-indent:-18.0pt;} ol {margin-bottom:0cm;} ul {margin-bottom:0cm;} -->
<!-- /* Font Definitions */ @font-face {font-family:宋体; panose-1:2 1 6 0 3 1 1 1 1 1; mso-font-alt:SimSun; mso-font-charset:134; mso-generic-font-family:auto; mso-font-pitch:variable; mso-font-signature:3 135135232 16 0 262145 0;} @font-face {font-family:黑体; panose-1:2 1 6 0 3 1 1 1 1 1; mso-font-alt:SimHei; mso-font-charset:134; mso-generic-font-family:modern; mso-font-format:other; mso-font-pitch:fixed; mso-font-signature:1 135135232 16 0 262144 0;} @font-face {font-family:"/@宋体"; panose-1:2 1 6 0 3 1 1 1 1 1; mso-font-charset:134; mso-generic-font-family:auto; mso-font-pitch:variable; mso-font-signature:3 135135232 16 0 262145 0;} @font-face {font-family:"/@黑体"; panose-1:2 1 6 0 3 1 1 1 1 1; mso-font-charset:134; mso-generic-font-family:auto; mso-font-pitch:variable; mso-font-signature:1 135135232 16 0 262144 0;} /* Style Definitions */ p.MsoNormal, li.MsoNormal, div.MsoNormal {mso-style-parent:""; margin:0cm; margin-bottom:.0001pt; text-align:justify; text-justify:inter-ideograph; mso-pagination:none; font-size:10.5pt; mso-bidi-font-size:12.0pt; font-family:"Times New Roman"; mso-fareast-font-family:宋体; mso-font-kerning:1.0pt;} h1 {mso-style-next:正文; margin-top:17.0pt; margin-right:0cm; margin-bottom:16.5pt; margin-left:0cm; text-align:justify; text-justify:inter-ideograph; line-height:240%; mso-pagination:lines-together; page-break-after:avoid; mso-outline-level:1; font-size:22.0pt; font-family:"Times New Roman"; mso-font-kerning:22.0pt;} h2 {mso-style-next:正文; margin-top:13.0pt; margin-right:0cm; margin-bottom:13.0pt; margin-left:0cm; text-align:justify; text-justify:inter-ideograph; line-height:173%; mso-pagination:lines-together; page-break-after:avoid; mso-outline-level:2; font-size:16.0pt; font-family:Arial; mso-fareast-font-family:黑体; mso-bidi-font-family:"Times New Roman"; mso-font-kerning:1.0pt;} h3 {mso-style-next:正文; margin-top:13.0pt; margin-right:0cm; margin-bottom:13.0pt; margin-left:0cm; text-align:justify; text-justify:inter-ideograph; line-height:173%; mso-pagination:lines-together; page-break-after:avoid; mso-outline-level:3; font-size:16.0pt; font-family:"Times New Roman"; mso-font-kerning:1.0pt;} a:link, span.MsoHyperlink {color:blue; text-decoration:underline; text-underline:single;} a:visited, span.MsoHyperlinkFollowed {color:purple; text-decoration:underline; text-underline:single;} pre {margin:0cm; margin-bottom:.0001pt; mso-pagination:widow-orphan; background:#EEEEEE; border:none; mso-border-alt:solid #999999 .75pt; padding:0cm; mso-padding-alt:7.0pt 7.0pt 7.0pt 7.0pt; font-size:9.0pt; font-family:"Courier New"; mso-fareast-font-family:黑体;} tt {mso-ascii-font-family:黑体; mso-fareast-font-family:黑体; mso-hansi-font-family:"Courier New"; mso-bidi-font-family:"Courier New";} span.codekeyword1 {mso-style-name:codekeyword1; color:blue;} /* Page Definitions */ @page {mso-page-border-surround-header:no; mso-page-border-surround-footer:no;} @page Section1 {size:612.0pt 792.0pt; margin:72.0pt 90.0pt 72.0pt 90.0pt; mso-header-margin:36.0pt; mso-footer-margin:36.0pt; mso-paper-source:0;} div.Section1 {page:Section1;} /* List Definitions */ @list l0 {mso-list-id:20203860; mso-list-type:hybrid; mso-list-template-ids:1846063014 -1082597686 67698713 67698715 67698703 67698713 67698715 67698703 67698713 67698715;} @list l0:level1 {mso-level-text:%1.; mso-level-tab-stop:18.0pt; mso-level-number-position:left; margin-left:18.0pt; text-indent:-18.0pt;} ol {margin-bottom:0cm;} ul {margin-bottom:0cm;} -->
自己需要,翻了出来,不当之处请指教。
V. Rama Krishna
显示链接DLL中的类有时优于隐式链接。例如,若在运行期时应用程序无法找到DLL,应用程序可以给出一个错误信息而后继续运行。如果想让用户为应用程序提供插件,显示链接同样有用。在这种情况下可以显示加载DLL并且调用其中预定义的一组函数。
显示链接C/C++全局函数是很容易的。例如,假设想调用某个DLL中叫做ExportedFn的函数,可以像下面这样容易地导出函数(或者通过def文件)
extern "C" _declspec(dllexport)
void ExportedFn(int Param1, char* param2);
extern “C”连接指示符是必需的,否则C++编译器将为函数产生一个修饰名,函数将无法用”ExportedFn”这个名字导出,取而代之的是类似于”??ExportedFn@QAEX”这种形式。如果这个函数在一个叫做DLL1.dll的文件中,可执行文件客户端可以像下面这样调用这个函数:
HMODULE hMod = LoadLibrary("Dll1.dll");
typedef void (*PExportedFn)(int, char*);
PExportedFn pfnEF = (PExportedFn)GetProcAdress("ExportedFn");
pfnEF(1, "SomeString");
但如果要导出一个C++类的成员函数并显示链接它们会遇到两个问题。第一个问题是C++成员函数有修饰名(extern “C”不起作用)。第二个是C++规范不允许指向成员函数的指针被转换成其他类型(稍后我会用一个简单的方法实现这一点)。这两个问题限制了DLL中C++类的导出。在本文中我会展示一些方法来突破这些限制并显示链接DLL中的类。
这篇文章展示两种途径,另一个文章会里揭示另一种方法(delegation授权)
这三种方法分别是:
1. 用虚函数表。这是COM的做法。
2. 通过GetProcAddress直接调用。
本文以下面的类为例子
class A
{
private:
int m_nNum;
public:
A();
A(int n);
virtual ~A();
void SetNum(int n);
int GetNum();
};
两个不同的DLL中提供了两个类实例。一个客户端exe允许用户输入从加载类的DLL名,并输出结果。从这里下载范例工程。范例包含一个ExpClass.dsw工作区,其中三个工程有两个是DLL的,另一个是客户端exe的。代码展示了两种方法。
用虚函数表导出
这个方法基于COM。把成员函数声明为virtual,编译器会按照函数声明的顺序为所有的虚函数创建一个表。当创建一个该类的对象时,对象的头四个字节指向该表。如果把类A的声明改成:
class A
{
private:
int m_nNum;
public:
A();
A(int n);
virtual ~A();
virtual void SetNum(int n);
virtual int GetNum();
};
编译器创建了含有三个虚函数的表:构造函数,SetNum和GetNum(更改工程选项浏览源码的汇编,就能够实际观察到一个虚函数表被创建)
对象需要在DLL中创建。因为使用显示链接,故需要一些通过new操作符创建类对象的全局导出函数。为两个构造函数创建两个这样的函数:CreateObjectofA()和CreateObjectofA1(int),然后导出它们。最终exe文件可以这样使用对象:
typedef A* (*PFNCreateA1)();
PFNCreateA1 pfnCreateA1 =
(PFNCreateA1)GetProcAddress(hMod, TEXT("CreateObjectofA1"));
A* a = (pfnCreateA1)();
a->SetNum(1);
_tprintf(TEXT("Value of m_nNum in a is %d/n"),a->GetNum());
delete a;
有一点需要注意,就是对象是CreateObjectofA函数new出来的,exe应该把它delete掉。
extern "C" __declspec(dllexport) A* CreateObjectofA1()
{
return new A();
}
如果想让用户为应用程序提供插件,这个方法非常有用。该方法的缺点是,必须在DLL中为类申请空间。如果客户端想用其他申请内存空间的方式来创建对象,这种方法就做不到了。
下一个方法涉及直接通过GetProcAddress得到并调用函数。问题的关键是把GetProcAddress返回的FARPROC转换成指向成员变量的指针。幸运的是,通过C++模板和联合体可以轻松的做到。所需做的是把类定义成下面这个样子:
template<class Dest, class Src>
Dest force_cast(Src src)
{
union
{
Dest d;
Src s;
} convertor;
convertor.s = src;
return convertor.d;
}
上面的函数比reinterpret_cast更有效,可以转换任意类型的变量。例如,定义一个指针类型:
typedef void (A::*PSetNum)(int);
可以通过下面的方法,简单地把FARPROC类型的指针fp转换成PsetNum类型
FARPROC fp;
.
.
PSetNum psn = force_cast<PSetNum>(fp);
这是Reinterpret_cast或者C-Style转换无法实现的。
已经找到了一种把FARPROC转换成成员函数指针的方法,来看看通过友好名字导出C++的成员函数。这可以通过.def文件实现。
第一步是找到每个需要导出的函数的修饰名,这点可以使用映射文件或产生汇编列表的方法实现。函数可用下面.def文件的语法,通过友好名字导出:
EXPORTS
ConstructorOfA1 = ??0A@@QAE@XZ PRIVATE
ConstructorOfA2 = ??0A@@QAE@H@Z PRIVATE
SetNumOfA = ?SetNum@A@@UAEXH@Z PRIVATE
GetNumOfA = ?GetNum@A@@UAEHXZ PRIVATE
DestructorOfA = ??1A@@UAE@XZ PRIVATE
函数通过更友好的名字导出,下面是调用函数的方法
typedef void (A::*PfnConstructorOfA1)();
typedef void (A::*PfnConstructorOfA2)(int);
typedef void (A::*PfnDestructorOfA)();
typedef void (A::*PfnSetNumOfA)(int);
typedef int (A::*PfnGetNumOfA)();
A* a1 = (A*)_alloca(sizeof(A));
PfnConstructorOfA1 pfnConsA =
force_cast<PfnConstructorOfA1>(GetProcAddress(hMod,TEXT("ConstructorOfA1")));
(a1->*pfnConsA)();
PfnSetNumOfA pfnSetNumA =force_cast<PfnSetNumOfA>(GetProcAddress(hMod,
TEXT("SetNumOfA")));
(a1->*pfnSetNumA)(1);
PfnGetNumOfA pfnGetNumA =force_cast<PfnGetNumOfA>(GetProcAddress(hMod,
TEXT("GetNumOfA")));
_tprintf(TEXT("Value of m_nNum in a is %d/n"),(a1->*pfnGetNumA)());
PfnDestructorOfA pfnDestA =force_cast<PfnDestructorOfA>(GetProcAddress(hMod,
TEXT("DestructorOfA")));
(a1->*pfnDestA)();
值得注意的一件有趣的事情是,构造函数和析构函数都是显示调用的。这可能是类构造函数可被显示调用的唯一的方法。另一处值得注意的是,对象用alloca函数从栈上申请内存(如果想从堆上申请空间,用malloc函数)。这是因为通过new或者仅仅声明一个A类型的对象,A的构造函数会自动调用。因为A的构造函数在DLL中实现,并且不隐式地链接到DLL,所以不希望是这样。不必显示调用构造函数和析构函数,可以像下面这样在exe文件中实现构造函数:
A::A()
{
static PfnConstructorOfA1 pfnConsA1 =force_cast<PfnConstructorOfA1>
(GetProcAddress(ClassLoader<A>::s_hMod,
TEXT("ConstructorOfA1")));
(this->*pfnConsA1)();
}
A::~A()
{
static PfnDestructorOfA pfnDestA = force_cast<PfnDestructorOfA>
(GetProcAddress(ClassLoader<A>::s_hMod,
TEXT("ConstructorOfA1")));
(this->*pfnDestA)();
}
上面两个实现仅仅代表DLL中实际的函数,同时允许两个声明并允许用普通的方式使用对象A。
我会在下一篇文章中提供一种更好的委托形式。这里需要重点提到的是,如果仅仅通过指针调用,不必实现SetNum,GetNum等等这样的函数。带注释的范例说明了所有的方法。