DLL的显式链接在某些时候比隐式链接具有更大的灵活性。比如,如果在运行时发现DLL无法找到,程序可以显示一个错误信息并能继续运行。当你想为你的程序提供插件服务时,显式链接也很有用处。

显式链接到全局C/C++函数非常简单。假设你想调用DLL中的一个函数ExportedFn,你可以像这样很简单地导出它:

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++不允许将指向成员函数的指针转换成其它类型。这两个问题限制了C++类的显式链接。下面介绍两种方法来解决这个问题:①用虚函数表的方法,这也是COM使用的方法;②用GetProcAddress直接调用。我将以下面这个类为例进行讲解:

  1. class A
  2. {
  3. private:
  4. int m_nNum;
  5. public:
  6. A();
  7. A(int n);
  8. virtual ~A();
  9. void SetNum(int n);
  10. int GetNum();
  11. };

一.用虚函数表进行显式链接

这个方法是COM的基础。当我们定义一组虚函数的时候,编译器会创建一个虚函数表,将各虚函数的地址按声明的顺序放入其中。当一个类对象被创建时,它的前四个字节是一个指针,指向这个虚函数表。如果我们将A的定义修改成这样:

  1. class A
  2. {
  3. private:
  4. int m_nNum;
  5. public:
  6. A();
  7. A(int n);
  8. virtual ~A();
  9. virtual void SetNum(int n);
  10. virtual int GetNum();
  11. };

那么一个虚函数表将被编译器创建出来,其中包含三个函数的地址:析构函数,SetNum和GetNum。现在类对象要在dll中创建。既然我们要显式链接,就需要一些全局导出函数来调用operator new以创建对象。因为A有两种构造函数,所以我们定义两个函数CreateObjectofA()和CreateObjectofA1(int)并将其导出。客户可以这样来使用类对象:

 
  1. typedef A* (*PFNCreateA1)();
  2. PFNCreateA1 pfnCreateA1 =  (PFNCreateA1)GetProcAddress(hMod, TEXT("CreateObjectofA1"));
  3. A* a = (pfnCreateA1)();
  4. a->SetNum(1);
  5. _tprintf(TEXT("Value of m_nNum in a is %d\n"),a->GetNum());
  6. delete a;
   要注意的是CreateObjectofA必须使用operator new来创建对象这样客户端才可以安全地调用operator delete来销毁对象:
 
  1. extern "C" __declspec(dllexport) A* CreateObjectofA1()
  2. {
  3. return new A();
  4. }

这个方法的使用得用户可以很容易地为你的程序制作插件。它的缺点是创建对象的内存必须在dll中分配。

二.直接使用GetProcAddress进行显式链接

这个方法的关键在于将GetProcAddress函数返回的FARPROC类型转化为C++中指向成员函数的指针。幸运的是,通过C++的unio和模板机制,这个目标可以很容易地实现。我们要做的只是定义如下的函数:

  1. template<class Src , class Dest>
  2. Dest force_cast(Src src){
  3. union
  4. {
  5. Dest d;
  6. Src s;
  7. } convertor;
  8. convertor.s = Src;
  9. return convertor.d;
  10. }
   上面的函数允许我们在任何类型间进行转换,比reinterpret_cast更加有效。例如,我们定义一种指针类型:
    typedef void (A::*PSetNum)(int);
    我们可以将FARPROC类型的指针fp转化成PSetNum:
    PSetNum psn = force_cast<PSetNum>(fp);
    找到了将FARPROC转化成成员函数指针的方法以后,我们要考虑如何将C++成员函数以更加友好的名字导出。这可以通过一个.def文件来实现。
    第一步是找到待导出函数经过修饰的函数名,这可以通过查看map file或者汇编代码来实现。然后在.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

下面是调用这些成员函数的方法:

  1. typedef void (A::*PfnConstructorOfA1)();
  2. typedef void (A::*PfnConstructorOfA2)(int);
  3. typedef void (A::*PfnDestructorOfA)();
  4. typedef void (A::*PfnSetNumOfA)(int);
  5. typedef int  (A::*PfnGetNumOfA)();
  6. A* a1 = (A*)_alloca(sizeof(A));
  7. PfnConstructorOfA1 pfnConsA = force_cast<PfnConstructorOfA1>(GetProcAddress(hMod, TEXT("ConstructorOfA1")));
  8. (a1->*pfnConsA)();
  9. PfnSetNumOfA pfnSetNumA = force_cast<PfnSetNumOfA>(GetProcAddress(hMod, TEXT("SetNumOfA")));
  10. (a1->*pfnSetNumA)(1);
  11. PfnGetNumOfA pfnGetNumA = force_cast<PfnGetNumOfA>(GetProcAddress(hMod, TEXT("GetNumOfA")));
  12. _tprintf(TEXT("Value of m_nNum in a is %d\n"),(a1->*pfnGetNumA)());
  13. PfnDestructorOfA pfnDestA = force_cast<PfnDestructorOfA>(GetProcAddress(hMod, TEXT("DestructorOfA")));
  14. (a1->*pfnDestA)();

注意这里使用了alloca从栈中分配内存,你也可以使用malloc从堆中分配内存。但是不能使用C++的new操作符,因为能过new来分配内存编译器会自动插入对constructor的调用。但我们要的是显式链接,所以必须避免这种情况。随之产生的结果是我们只能显式地去调用构造函数和析构函数。

转:http://www.moon-soft.com/doc/14639.htm

http://qimo601.iteye.com/blog/1399328

DLL中类的显式链接(用虚函数进行显式链接)的更多相关文章

  1. C++ 链式继承下的虚函数列表

    目录 1.虚函数列表的位置 2.虚函数列表的内容 3.链式继承中虚函数列表的内容   注: 虚函数列表 又称为虚表, vtbl , 指向它的指针称为vptr, vs2019中称为__vfptr 操作系 ...

  2. C#基础(七)虚函数

    若一个实例方法声明前带有virtual关键字,那么这个方法就是虚方法.虚方法与非虚方法的最大不同是,虚方法的实现可以由派生类所取代,这种取代是通过方法的重写实现的(以后再讲)虚方法的特点:虚方法前不允 ...

  3. C++之多态性与虚函数

    面向对象程序设计中的多态性是指向不同的对象发送同一个消息,不同对象对应同一消息产生不同行为.在程序中消息就是调用函数,不同的行为就是指不同的实现方法,即执行不同的函数体.也可以这样说就是实现了&quo ...

  4. C#中的虚函数及继承关系

    转载:http://blog.csdn.net/suncherrydream/article/details/8423991 若一个实例方法声明前带有virtual关键字,那么这个方法就是虚方法. 虚 ...

  5. [转]C++之多态性与虚函数

    面向对象程序设计中的多态性是指向不同的对象发送同一个消息,不同对象对应同一消息产生不同行为.在程序中消息就是调用函数,不同的行为就是指不同的实现方法,即执行不同的函数体.也可以这样说就是实现了“一个接 ...

  6. c++ 一般虚函数

    类图: 代码: #include <iostream> using namespace std; class CFather //父类 { public: virtual void dis ...

  7. C++——多态性 与 虚函数

    多态性 多态性是面向对象程序设计的关键技术之一.若程序设计语言不支持多态性,不能称为面向对象的语言.利用多态性技术,可以调用同一个函数名的函数,实现完全不同的功能. 多态性(polymorphism) ...

  8. C++11显式虚函数重载

    [C++11显式虚函数重载] 在子类中给重载的虚函数加上override, 可以让编译器检察基类是否有这一虚函数.此功能适用于当基类原有的虚函数发生变化,即相当于编译期检察. 而基类,可以给函数加上f ...

  9. DLL接口的实现(虚函数)

    DLL接口的实现(虚函数) 我们在c++编程过程中往往要用到各种不同形式的程序库,这些库的发布方式有动态库和静态库.对于静态类库,设计良好的静态类库能实现功能上的隔离,无法避免类库实现必须重新编译.链 ...

随机推荐

  1. Android ActionBar相关

    1.Android 5.0 删除ActionBar下面的阴影 于Android 5.0假设你发现的ActionBar下面出现了阴影,例如,下面的设置,以消除阴影: getActionBar().set ...

  2. Linux经常使用的命令(21) - find参数具体解释

    一.使用name选项: 文件名称选项是find命令最经常使用的选项.要么单独使用该选项,要么和其它选项一起使用.  能够使用某种文件名称模式来匹配文件,记住要用引號将文件名称模式引起来.  无论当前路 ...

  3. python 获取字典值

    一.Python中的字典遍历方法: info = { 'name':'xiaoming', 'sex':'nan', 'age':20, 'id':1} info2 = { 'name':'hhh', ...

  4. OpenCV调试利器——Image Watch插件的安装和使用

    各大编译工具在调试的时候都可以实时查看变量的值,了解变量值的变动情况,在图像处理相关的程序调试中,是否也可以实时查看内存中图像变量的图形信息以及图像上指定区域或点位的数值变化情况呢? 在工业机器视觉领 ...

  5. Atitit.软件button和仪表板(13)--全文索引操作--db数据库子系统mssql2008

    Atitit.软件button和仪表板(13)--全文索引操作--db数据库子系统mssql2008 全文索引操作 4.全文索引和like语句比較 1 5.倒排索引 inverted index 1 ...

  6. wordpress如何判断手机、平板还是PC并显示对应的内容-Mobile Detect

    wordpress如何判断是手机.平板还是PC访问,并针对性的显示特定的内容?Mobile Detect 这个轻量级PHP 类库能够很好的实现这个功能.而且Mobile Detect也有wordpre ...

  7. [ 转]Node.js模块 require和 exports

    什么是模块? node.js通过实现CommonJS的Modules/1.0标准引入了模块(module)概念,模块是Node.js的基本组成部分.一个node.js文件就是一个模块,也就是说文件和模 ...

  8. zend-form笔记

    Zend-Form组件包含以下几个对象: 1.Elements:包含了name和attributes, 2.Fieldsets:继承自elements,但允许包含其他fieldset和elements ...

  9. 读BeautifulSoup官方文档之html树的搜索(2)

    除了find()和find_all(), 这里还提供了许多类似的方法我就细讲了, 参数和用法都差不多, 最后四个是next, previous是以.next/previous_element()来说的 ...

  10. WPF 元素tag属性绑定一个属性或一个对象

    <Window x:Class="CollectionBinding.CategoryDataTemp"        xmlns="http://schemas. ...