显示链接DLL中的类

本文探讨了如何在DLL中显示链接C++类,包括利用虚函数表和通过GetProcAddress直接调用的方法。介绍了如何解决C++成员函数修饰名问题及如何转换成员函数指针。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

<!-- /* 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++规范不允许指向成员函数的指针被转换成其他类型(稍后我会用一个简单的方法实现这一点)。这两个问题限制了DLLC++类的导出。在本文中我会展示一些方法来突破这些限制并显示链接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();

};

编译器创建了含有三个虚函数的表:构造函数,SetNumGetNum(更改工程选项浏览源码的汇编,就能够实际观察到一个虚函数表被创建)

对象需要在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等等这样的函数。带注释的范例说明了所有的方法。

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值