C++ DLL导出类 知识大全
在公司使用C++ 做开发,公司的大拿搭了一个C++的跨平台开发框架。在C++开发领域我还是个新手,有很多知识要学,比如Dll库的开发。
参考了很多这方面的资料,对DLL有一个基本全面的了解。有一个问题让我有点困惑,普通的导入导出C++类的方式都是使用_declspec(dllexport) /_declspec(dllimport)来导入导出类,但是在公司的开发中我们没有导入导出,而是定义了一些只有纯虚函数的抽象类,然后定义了一个工厂类,将这个工厂类注册到框架的服务中心中,使用时从服务中心拿到这个工厂类,就可以创建Dll中的其它类。对这种使用方式我不太理解,google+百度搜索了很多这方面的内容,很多blog讲到了这种使用方式,但是也没有讲清楚这样使用的原理,后来找到了一篇老外写的blog,讲得比较清楚。
使用只有纯虚函数的抽象类之所以不需要导出,是因为纯虚函数的虚表使然。下面是同老外的bkig中抽出来的一个示例。
纯虚函数类的定义如下:
[cpp] view plain copy- // The abstract interface for Xyz object.
- // No extra specifiers required.
- struct IXyz
- {
- virtual int Foo(int n) = 0;
- virtual void Release() = 0;
- };
- // Factory function that creates instances of the Xyz object.
- extern "C" XYZAPI IXyz* APIENTRY GetXyz();
使用dll的代码如下:
[cpp] view plain copy- #include "XyzLibrary.h"
- ...
- IXyz* pXyz = ::GetXyz();
- if(pXyz)
- {
- pXyz->Foo(42);
- pXyz->Release();
- pXyz = NULL;
- }
该示例中导出了一个方法来创建IXyz对象,但是并没有导出IXyz对象,IXyz类中只有纯虚函数。这是如何实现的呢?我所知道的是,需要将Dll中的类导出,导出的符号将放到导出符号表中,在链接的时候根据这些符号来定位函数的地址,这个IXyz类没有声明导出,当然类中的函数就不回生成在导出符号表中,那么怎么定位到函数的地址呢?下面这张原文中的图给出了很清晰的解释:
图中的伪代码部分解释了函数的调用过程,是通过虚表来定位函数的。因为定义的是只有纯虚函数的抽象类,这样的类编译之后会有一个纯粹的虚表,可以通过这张纯粹的虚表来进行函数调用,所以通过这种方式来使用dll的第一步是应以只带纯虚函数的抽象类,或者说接口。
更多内容,参见下面:
一、导出类的简单方式
这种方式是比较简单的,同时也是不建议采用的不合适方式。
只需要在导出类加上__declspec(dllexport),就可以实现导出类。对象空间还是在使用者的模块里,dll只提供类中的函数代码。不足的地方是:使用者需要知道整个类的实现,包括基类、类中成员对象,也就是说所有跟导出类相关的东西,使用者都要知道。通过Dependency Walker可以看到,这时候的dll导出的是跟类相关的函数:如构造函数、赋值操作符、析构函数、其它函数,这些都是使用者可能会用到的函数。
这种导出类的方式,除了导出的东西太多、使用者对类的实现依赖太多之外,还有其它问题:必须保证使用同一种编译器。导出类的本质是导出类里的函数,因为语法上直接导出了类,没有对函数的调用方式、重命名进行设置,导致了产生的dll并不通用。
部分代码(DLL头文件):
上一篇总结已经说完了。本质上来说,跟导出函数没差别。使用VS2005自动生成的代码可以省去很多力气,比起以前做练习什么都是自己动手写方便多了。要注意一下工程的设置,熟悉它们的作用可以加快编程速度。Demo代码附件:
https://files.cnblogs.com/cswuyg/%E7%BC%96%E5%86%99DLL%E6%89%80%E5%AD%A6%E6%89%80%E6%80%9D(2)(%E5%AF%BC%E5%87%BA%E7%B1%BB).rar
参考资料:
http://www.codeproject.com/KB/cpp/howto_export_cpp_classes.aspx
编写DLL所学所思(1)——导出函数
2010.8.31~2010.9.1总结
2011.9.28~30整理
烛秋
C语言Name-Mangling的规范(林锐的书有这样说过)。这样就使得,任何一个支持c语言的编译器,它编译出来的obj文件可以共享,链接成可执行文件。这是一种标准,如果DLL跟其使用者都采用这种约定,那么就可以解决函数重命名规则不一致导致的错误。
影响符号名的除了C++和C的区别、编译器的区别之外,还要考虑调用约定导致的Name Mangling。如extern “c” __stdcall的调用方式就会在原来函数名上加上写表示参数的符号,而extern “c” __cdecl则不会附加额外的符号。
dll中的函数在被调用时是以函数名或函数编号的方式被索引的。这就意味着采用某编译器的C++的Name-Mangling方式产生的dll文件可能不通用。因为它们的函数名重命名方式不同。为了使得dll可以通用些,很多时候都要使用C的Name-Mangling方式,即是对每一个导出函数声明为extern “C”,而且采用_stdcall调用约定,接着还需要对导出函数进行重命名,以便导出不加修饰的函数名。
注意到extern “C”的作用是为了解决函数符号名的问题,这对于动态链接库的制造者和动态链接库的使用者都需要遵守的规则。
动态链接库的显式装入就是通过GetProcAddress函数,依据动态链接库句柄和函数名,获取函数地址。因为GetProcAddress仅是操作系统相关,可能会操作各种各样的编译器产生的dll,它的参数里的函数名是原原本本的函数名,没有任何修饰,所以一般情况下需要确保dll’里的函数名是原始的函数名。分两步:一,如果导出函数使用了extern”C” _cdecl,那么就不需要再重命名了,这个时候dll里的名字就是原始名字;如果使用了extern”C” _stdcall,这时候dll中的函数名被修饰了,就需要重命名。二、重命名的方式有两种,要么使用*.def文件,在文件外修正,要么使用#pragma,在代码里给函数别名。
https://files.cnblogs.com/cswuyg/%E7%BC%96%E5%86%99DLL%E6%89%80%E5%AD%A6%E6%89%80%E6%80%9D.rar