(转)如何从一个DLL中导出一个C++类
1个回答
展开全部
自从Windows的开端阶段动态链接库(DLL)就是Windows平台的一个构成项目组。动态链接库容许在一个自力的模块中封装一系列的功能函数然后以一个显式的C函数列表供给外部应用者应用。在上个世纪80年代,当Windows DLLs面世时,对于广大开辟者而言只有C说话是切实可行的开辟手段。所以, Windows DLLs很天然地以C函数和数据的情势向外部露出功能。从本质来说,一个DLL可以由任何说话实现,然则为了使DLL用于其它的说话和景象之下,一个DLL接口必须撤退猬缩到最低请求的母体——C说话。应用C接口并不主动意味一个开辟者应当应当放弃面向对象的开辟体式格式。甚至C接口也能用于真正的面向对象编程,尽管它有可能被认为是一种单调乏味的实现体式格式。很显然世界上应用人数排第二的编程说话是C++,但它却不得不被DLL所诱惑。然而,和C说话相反,在调用者和被调用者之间的二进制接口被很好的定义并被广泛接管,然则在C++的世界里却没有可识此外应用法度二进制接口。实际上,由一个C++编译器产生的二进制代码并不克不及被其它C++编译器兼容。再者,在同一个编译器但不合版本的二进制代码也是互不兼容的。所有这些导致从一个DLL中一个C++类的确就是一个冒险。这篇文章就是演示几种从一个DLL模块中导出C++类的办法。源码演示了导出虚构的Xyz对象的不合技能。Xyz对象很是简单,只有一个函数:Foo。下面是Xyz对象的图解:Xyzint Foo(int)Xyz对象在一个DLL里实现,这个DLL能作为一个分布式体系供局限很广的客户端应用。一个用户能以下面三种体式格式调用Xyz的功能:应用一个规矩的C++类应用一个抽象的C++接口源码(译注:文章附带的源码)包含两个:XyzLibrary– 一个DLLXyzutable– 一个Win32 应用"XyzLibrary.dll"的把握台法度XyzLibrary应用下列便利的宏导出它的代码:#if defined(XYZLIBRARY_EXPORT) // inside DLL# define XYZAPI __declspec(dllexport)#else // outside DLL# define XYZAPI __declspec(dllimport)#endif // XYZLIBRARY_EXPORTXYZLIBRARY_EXPORT标识符仅仅在XyzLibrary定义,是以在XYZAPI宏在DLL生成时被扩大为__declspec(dllexport)而在客户法度生成时被扩大为__declspec(dllimport)。C说话体式格式 经典的C说话体式格式进行面向对象编程的一种体式格式就是应用晦涩的指针,比如句柄。一个用户可以或许应用一个函数创建一个对象。实际上这个函数返回的是这个对象的一个句柄。接着用户可以或许调用这个对象相干的各类操纵函数只要这个函数可以或许接管这个句柄作为它的一个参数。一个很好的例子就是在Win32窗口相干的API中句柄的习惯是应用一个HWND句柄来代表一个窗口。虚构的Xyz对象经由过程下面如许一种体式格式导出一个C接口:typedef tagXYZHANDLE {} * XYZHANDLE;// 创建一个Xyz对象实例的函数XYZAPI XYZHANDLE APIENTRY GetXyz(VOID);// 调用Xyz.Foo函数XYZAPI INT APIENTRY XyzFoo(XYZHANDLE handle, INT n);// 开释Xyz实例和占用的资料XYZAPI VOID APIENTRY XyzRelease(XYZHANDLE handle);// APIENTRY is defined as __stdcall in WinDef.h header.下面是一个客户端调用的C代码:#include "XyzLibrary.h"/* 创建Xyz实例*/XYZHANDLE hXyz = GetXyz();if(hXyz) /* 调用 Xyz.Foo函数*/ XyzFoo(hXyz, 42); /*析构 Xyz实例并开释已取得的资料. */ XyzRelease(hXyz); hXyz = NULL;应用这种体式格式,一个DLL必须供给显式的对象构建和删除函数。 对于所有的导出函数记住它们调用和谈是首要的。对于很多初学者来说忘怀添加调用和谈是很是广泛的错误。只要客户端的调用和谈和DLL的调用和谈匹配,一切都能运行。然则,一旦客户端改变了它的调用和谈,开辟者将会产生一个难以察觉的直到运行时才产生的错误。XyzLibrary应用一个APIENTRY宏,这个宏在"WinDef.h"这个头文件里被定义为__stdcall。异常安然性 在DLL局限内不容许产生C++异常。在一段时候内,C说话不辨认C++的异常,并且不克不及正确处理惩罚它们。假如一个对象的办法须要呈报一个错误,这时一个返回码须要用到。l 一个DLL能被最广泛的合适的开辟者所应用。几乎每一种现代编程说话都支撑纯C函数的互用性。l 一个DLL的C运行时库和它的客户端是互相自力的。因为资料的获取和开释完全产生在DLL模块的内部,所以一个客户端不受一个DLL的C运行时库选择的影响。l 获取正确对象的合适的办法的义务落在DLL的应用者的肩上。比如鄙人面的代码片段,编译器不克不及捕获到此中产生的错误:l 获取正确对象的合适的办法的义务落在DLL的应用者的肩上。比如鄙人面的代码片段,编译器不克不及捕获到此中产生的错误:/* void* GetSomeOtherObject(void)是此外处所定义的一个函数 */XYZHANDLE h = GetSomeOtherObject();/* 啊! 错误: 在错误的对象实例上调用Xyz.Foo函数*/XyzFoo(h, 42); l 显式请求创建和摧毁一个对象的实例。此中希罕烦人的是对象实例的删除。客户端必须极细心地在一个函数的退出点调用XyzRelease函数。假如开辟者忘怀调用XyzRelease函数,那时资料就会泄漏,因为编译器不克不及跟踪一个对象实例的生命周期。那些支撑析构函数或垃圾收集器的说话经由过程在C接口上作一层封装有助于降落这个题目产生的概率。l 假如一个对象的函数返回或接管其它对象作为参数,那时DLL作者也就不得不为这些对象供给一个正确的C接口。假如退回到最大限度的复用,也就是C说话,那么只有以字节创建的类型(如int, double, char*等等)可以作为返回类型和函数参数 C++天然的体式格式:导出一个类在Windows平台上几乎每一个现代的编译器都支撑从一个DLL中导出一个类。导出一个类和导出一个C函数很是类似。用那种办法一个开辟者被请求做就是在类名之前应用__declspec(dllexport/dllimport)关键字来指定假如全部类都须要被导出,或者在指定的函数声明前指定假如只是特定的类函数须要被导出。这儿有一个代码片段:// 全部CXyz类被导出,包含它的函数和成员class XYZAPI CXyzpublic: int Foo(int n);// 只有 CXyz::Foo函数被导出class CXyzpublic: XYZAPI int Foo(int n); 在导出全部类或者它们的办法没有须要显式指定一个调用和谈。按照预设,C++编译器应用__thiscall作为类成员函数的调用和谈。然而,因为不合的编译器具有不合的定名润饰法例,导出的C++类只能用于同一类型的同一版本的编译器。这儿有一个MS Visual C++编译器的定名润饰法例的应用实例: 重视这里润饰名是如何不合于C++本来的名字。下面是屏幕截图显示的是经由过程应用Dependency Walker 对象对同一个DLL的润饰名进行破译获得的: 只有MS Visual C++编译器能应用这个DLL.DLL和客户端代码只有在同一版本的MS Visual C++编译器才干确保在调用者和被调用者润饰名匹配。这儿有一个客户端代码应用Xyz对象的例子:#include "XyzLibrary.h"// 客户端应用Xyz对象作为一个规矩C++类.CXyz xyz;xyz.Foo(42);正如你所看到的,导出的C++类的用法和其它任何C++类的用法几乎是一样的。没什么特此外。首要事项:应用一个导出C++类的DLL和应用一个静态库没有什么不合。所有应用于有C++代码编译出来的静态库的规矩完全实用于导出C++类的DLL。所见即所得一个细心的读者必定已经重视到Dependency Walker对象显示了额外的导出成员,那就是CXyz& CXyz::operator =(const CXyz&)赋值操纵符。在工作你所看到的恰是C++的收入(译注:我估计这是原文作者滑稽的说法,意思是你没有定义一个=赋值操纵符,而编译器帮你主动定义一个,不是收入是什么?)。按照C++标准,每一个类有四个指定的成员函数:默认机关函数拷贝机关函数赋值操纵符 (operator =)假如类的作者没有声明同时没有供给这些成员的实现,那么C++编译器会声明它们,并产生一个隐式的默认的实现。在CXyz类,编译器断定它的默认机关函数,拷贝机关函数和析构函数都毫无意义,经过优化后把它们打消掉了。而赋值运算符在优化之后还存活并从DLL中导出。首要事项:应用__declspec(dllexport)来指定类导出来告诉编译器来测验测验导出任何和类相干的器材。它包含所有类的数据成员,所有类的成员函数(或者显式声明,或者由编译器隐式生成),所有类的基类和所有它们的成员。推敲:class Baseclass Data// MS Visual C++ compiler 会发出C4275 warning ,因为没有导出基类class __declspec(dllexport) Derived : public Baseprivate: Data m_data; // C4251 warning,因为没有导出数据成员. 在上方的代码片段,编译器会警告你没有导出基类和类的数据成员。所以,为了成功导出一个类,一个开辟者被请求导出所有相干基类和所有类的已定义的数据成员。这个滚雪球般的导出请求是一个重大毛病。这也是为什么,比如,导出派生自STL模板类或者应用STL模板类对象作为数据成员是很是艰苦和令人生厌的。比如一个STL容器比如std::map实例可能请求导出数十个额外的内部类。异常安然性一个导出的C++类可能会在没有任何错误产生的景象下抛出异常。因为一个DLL和它的客户端应用同一版本的同一类型的编译器的事实,C++异常将在超出DLL的局限进行捕获和抛出如同DLL没有分界线一样。记住,应用一个带有导出C++代码和应用带有雷同代码的静态库是完全一样的。长处l 一个导出的C++类和其它任何C++类的用法是一样的l 客户端能毫不艰苦地捕获在DLL产生的异常l 当在一个DLL模块内有一些小的代码批改时,其它模块也不消从头生成。这对于有着很多错杂难懂代码的大是很是有效的。l 在一个大中遵守营业逻辑分成不合的DLL实现可以被认为真正的模块划分的第一步。总的来说,它是使达到模块化值得去做的事毛病l 从一个DLL中导出C++类在它的对象和应用者须要对峙慎密的接洽。DLL应当被视作一个带有推敲到代码依附的静态库。l 客户端代码和DLL都必须和同一版本的CRT(译注:C运行时库)动态连接在一路。为了可以或许在模块之间批改CRT资料的记载,这一步是必须的。假如一个客户端和DLL连接到不合版本的CRT,或者静态连接到CRT,那么在一个CRT实例申请的资料有可能在另一个CRT实例中开释。它将破坏CRT实例的内涵状况并妄图操纵外部资料,并很可能导致运行失败。l 客户端代码和DLL必须在异常处理惩罚和产生杀青一致,同时在编译器的异常设置也必须一致l 导出C++类请求同时导出这个类的所有相干的器材,包含:所有它的基类、所有类定义的用到的数据成员等等。C++成熟的办法:应用抽象接口一个C++抽象接口(比如一个拥有纯虚函数和没稀有据成员的C++类)设法做到分身其美:对对象而言自力于编译器的规矩的接口以及便利的面向对象体式格式的函数调用。为达到这些请求去做的就是供给一个接口声明的头文件,同时实现一个能返回最新创建的对象实例的工厂函数。只有这个工厂函数须要应用__declspec(dllexport/dllimport)指定。接口不须要任何额外的指定。// Xyz object的抽象接口// 不请求作额外的指定struct IXyz virtual int Foo(int n) = 0; virtual void Release() = 0;// 创建Xyz对象实例的工厂函数extern "C" XYZAPI IXyz* APIENTRY GetXyz();在上方的代码片段中,工厂函数GetXyz被声明为extern XYZAPI。如许做是为了防止函数名被润饰(译注:如上方提到的导出一个C++类,其成员函数名导出后会被润饰)。如许,这个函数在外部发挥解析为一个规矩的C函数,并且很轻易被和C兼容的编译器所辨认。这就是当应用一个抽象接口时客户端代码看起来和下面一样:#include "XyzLibrary.h"IXyz* pXyz = ::GetXyz();if(pXyz) pXyz->Foo(42); pXyz->Release(); pXyz = NULL;C++不消为接口供给一个特定的标识表记标帜以便其它编程说话应用(比如C#或Java)。但这并不料味C++不克不及声明和实现接口。设计一个C++的接口的一般办法是去声明一个没有任何数据成员的抽象类。如许,派生类可以持续这个接口并实现这个接口,但这个实现对客户端是不成见的。接口的客户端不消知道和存眷接口是如何实现的。它只需知道函数是可用的和它们做什么。内部机制在这种办法背后的思惟是很是简单的。一个由纯虚函数构成的成员很少的类只不过是一个虚函数表——一个函数指针数组。在DLL局限内这个函数指针数组被它的作者填充当何他认为必须的器材。如许这个指针数组在DLL外部应用就是调用接口的实际上的实现。下面是IXyz接口的用法申明图表。 上方的图表演示了IXyz接口被DLL和EXE模块二者都用到。在DLL模块内部,XyzImpl类派生自IXyz接口并实现它的办法。在EXE的函数调用引用DLL模块经过一个虚表的实际实现。这种DLL为什么能和其它的编译器一路运行简短的申明是:因为COM技巧和其它的编译器一路运行。如今作一个具体申明,实际上,在模块之间应用一个成员很少的虚基类作为接口正确来说是COM对外露出了一个COM接口。如我们所知的虚表的概念,能很正确地添加COM标准的标识表记标帜。这不是一个偶合。C++说话,作为一个至少跨越了十年的主流开辟说话,已经广泛地应用在COM编程。因为C++生成地支撑面向对象的特点。微软将它作为财产COM开辟的重量级的对象是毫不新鲜的。作为COM技巧的所有者,微软已经确保COM的二进制标准和它们拥有的在Visual C++编译器实现的C++对象模型能以最小的本钱实现匹配。难怪其它的编译器厂商都和微软采取雷同的体式格式实现虚表的布局。毕竟 成果,每小我都想支撑COM技巧,并做到和微软已存在的解决办法兼容。假设某个C++编译器不克不及有效支撑COM,那么它注定会被Windows市场合扔掉。这就是为什么时至本日,经由过程一个抽象接口从一个DLL导出一个C++类能和Windows平台上过得去的编译器能靠得住地运行在一路。应用一个智能指针为了确保正确的资料开释,一个虚接口供给了一个额外的函数来清除对象实例。手动调用这个函数令人腻烦并轻易导致错误产生。我们都知道这个错误在C世界里这是一个很广泛的错误,因为在那儿开辟者不得不记得开释显式函数调用获取的资料。这就是为什么典范的C++代码借助于智能指针应用RAII(资料获取即初始化)的习惯。Xyzutable供给了一个例子,应用了AutoClosePtr模板。AutoClosePtr模板是一个最简单的智能指针,这个智能指针调用了一个类祛除一个实例的主观办法来庖代操纵符。这儿有一段演示带有IXyz接口的一个智能指针的用法的代码片段:#include "XyzLibrary.h"#include "AutoClosePtr.h"typedef AutoClosePtr IXyzPtr;IXyzPtr ptrXyz(::GetXyz());if(ptrXyz) ptrXyz->Foo(42);// 不须要调用ptrXyz->Release(). 智能指针将在析构函数里主动调用这个函数不管如何,应用智能指针将确保Xyz对象能正本地恰当资料。因为一个错误或者一个内部异常的产生,函数会过早地退出,然则C++说话包管所有局部对象的析构函数能在函数退出之前被调用。异常安然性和COM接口一样不再容许因为任何内部异常的产生而导致资料泄漏,抽象类接口不会让任何内部异常冲破DLL局限。函数调用将会应用一个返回码来明白指导产生的错误。对于特定的编译器,C++异常的处理惩罚都是特定的,不克不及够分享。所以,在这个意义上,一个抽象类接口发挥解析得实足像一个C函数。长处:l 一个导出的C++类可以或许经由过程一个抽象接口,被用于任何C++编译器l 一个DLL的C运行库和DLL的客户端是互相自力的。因为资料的初始化和开释都完全产生在DLL内部,所以客户端不受DLL的C运行库选择的影响。l 真正的模块分别能高度完美实现。成果模块可以从头设计和从头生成而不受的残剩模块的影响。l 若是须要,一个DLL模块能很便利地转化为真正的COM模块。毛病:l 一个显式的函数调用须要创建一个新的对象实例并删除它。尽管一个智能指针能免除开辟者之后的调用l 一个抽象接口函数不克不及返回或者接管一个规矩的C++对象作为一个参数。它只能以内置类型(如int、double、char*等)或者另一个虚接口作为参数类型。它和COM接口有着雷同的限制。STL模板类是如何做的C++标准模板库的容器(如vector,list或map)和其它模板并没有设计为DLL模块(以抽象类接口体式格式)。有关DLL的C++标准是没有的因为DLL是一种平台特定技巧。C++标准不须要呈如今没有效到C++说话的其它平台上。当前,微软的Visual C++编译器可以或许导出和导入开辟者显式以__declspec(dllexport/dllimport)关键字标识的STL类实例。编译器会发出几个令人憎恶的警告,然则还能运行。然而,你必须记住,导出STL模板实例和导出规矩C++类是完全一样的,有着一样的限制。所以,在那方面STL是没什么特此外。总结这篇文章评论辩论了几种从一个DLL模块中导出一个C++对象的不合办法。对每种办法的长处和毛病的具体论说也已给出。下面是得出的几个结论:l 以一个完全的C函数导出一个对象有着最广泛的开辟景象和开辟说话的兼容性。然而,为了应用现代编程范式一个DLL应用者被请求应用过期的C技能对C接口作一层额外的封装。l 导出一个规矩的C++类和以C++代码供给一个零丁的静态库没什么差别。用法很是简单和熟悉,然而DLL和客户端有着很是慎密的连接。DLL和它的客户端必须应用雷同版本和雷同类型的编译器。l 定义一个无数据成员的抽象类并在DLL内部实现是导出C++对象的最好办法。到今朝为止,这种办法在DLL和它的客户端供给了一个清楚的,明白界定的面向对象接口。如许一种DLL能在Windows平台上被任何现代C++编译器所应用。接口和智能指针一路连络应用的用法几乎和一个导出的C++类的用法一样便利。
推荐律师服务:
若未解决您的问题,请您详细描述您的问题,通过百度律临进行免费专业咨询