2023年7月18日发(作者:)
VC++MFCDLL动态链接库编写详解虽然能⽤DLL实现的功能都可以⽤COM来替代,但DLL的优点确实不少,它更容易创建。本⽂将讨论如何利⽤VC MFC来创建不同类型的DLL,以及如何使⽤他们。⼀、DLL的不同类型 使⽤ V C++可以⽣成两种类型的DLL:MFC扩展DLL和常规DLL。常规DLL有可以分为动态连接和静态连接。Visual C++还可以⽣成WIN32 DLL,但不是这⾥讨论的主要对象。1、MFC扩展DLL每个DLL都有某种类型的接⼝:变量、指针、函数、客户程序访问的类。它们的作⽤是让客户程序使⽤DLL,MFC扩展DLL可以有C++的接⼝。也就是它可以导出C++类给客户端。导出的函数可以使⽤C++/MFC数据类型做参数或返回值,导出⼀个类时客户端能创建类对象或者派⽣这个类。同时,在DLL中也可以使⽤DLL和MFC。Visual C++使⽤的MFC类库也是保存在⼀个DLL中,MFC扩展DLL动态连接到MFC代码库的DLL,客户程序也必须要动态连接到MFC代码库的DLL。(这⾥谈到的两个DLL,⼀个是我们⾃⼰编写的DLL,⼀个装MFC类库的DLL)现在MFC代码库的DLL也存在多个版本,客户程序和扩展DLL都必须使⽤相同版本的MFC代码DLL。所以为了让MFC扩展DLL能很好的⼯作,扩展DLL和客户程序都必须动态连接到MFC代码库DLL。⽽这个DLL必须在客户程序运⾏的计算机上。2、常规DLL使⽤MFC扩展DLL的⼀个问题就是DLL仅能和MFC客户程序⼀起⼯作,如果需要⼀个使⽤更⼴泛的DLL,最好采⽤常规DLL,因为它不受MFC的某些限制。常规DLL也有缺点:它不能和客户程序发送指针或MFC派⽣类和对象的引⽤。⼀句话就是常规DLL和客户程序的接⼝不能使⽤MFC,但在DLL和客户程序的内部还是可以使⽤MFC。当在常规DLL的内部使⽤MFC代码库的DLL时,可以是动态连接/静态连接。如果是动态连接,也就是常规DLL需要的MFC代码没有构建到DLL中,这种情况有点和扩展DLL类似,在DLL运⾏的计算机上必须要MFC代码库的DLL。如果是静态连接,常规DLL⾥⾯已经包含了需要的MFC代码,这样DLL的体积将⽐较⼤,但它可以在没有MFC代码库DLL的计算机上正常运⾏。⼆、建⽴DLL 利⽤Visual C++提供的向导功能可以很容易建⽴⼀个不完成任何实质任务的DLL,这⾥就不多讲了,主要的任务是如何给DLL添加功能,以及在客户程序中利⽤这个DLL1、导出类 ⽤向导建⽴好框架后,就可以添加需要导出类的.cpp .h⽂件到DLL中来,或者⽤向导创建C++ Herder File/C++ Source File。为了能导出这个类,在类声明的时候要加“_declspec(dllexport)”,如:
class _declspec(dllexport) CMyClass{ ...//声明
}如果创建的MFC扩展DLL,可以使⽤宏:AFX_EXT_CLASS:class AFX_EXT_CLASS CMyClass{ ...//声明
}这样导出类的⽅法是最简单的,也可以采⽤.def⽂件导出,这⾥暂不详谈。2、导出变量、常量、对象
很多时候不需要导出⼀个类,可以让DLL导出⼀个变量、常量、对象,导出它们只需要进⾏简单的声明:_declspec(dllexport) int MyInt;
_declspec(dllexport) extern const COLORREF MyColor=RGB(0,0,0);
_declspec(dllexport) CRect rect(10,10,20,20);
要导出⼀个常量时必须使⽤关键字extern,否则会发⽣连接错误。注意:如果客户程序识别这个类⽽且有⾃⼰的头⽂件,则只能导出⼀个类对象。如果在DLL中创建⼀个类,客户程序不使⽤头⽂件就⽆法识别这个类。当导出⼀个对象或者变量时,载⼊DLL的每个客户程序都有⼀个⾃⼰的拷贝。也就是如果两个程序使⽤的是同⼀个DLL,⼀个应⽤程序所做的修改不会影响另⼀个应⽤程序。
我们在导出的时候只能导出DLL中的全局变量或对象,⽽不能导出局部的变量和对象,因为它们过了作⽤域也就不存在了,那样DLL就不能正常⼯作。如:MyFunction(){ _declspec(dllexport) int MyInt;
_declspec(dllexport) CMyClass object;
}3、导出函数导出函数和导出变量/对象类似,只要把_declspec(dllexport)加到函数原型开始的位置:_declspec(dllexport) int MyFunction(int);如果是常规DLL,它将和C写的程序使⽤,声明⽅式如下:extern "c" _declspec(dllexport) int MyFunction(int);实现:extern "c" _declspec(dllexport) int MyFunction(int x){ ...//操作
}如果创建的是动态连接到MFC代码库DLL的常规DLL,则必须插⼊AFX_MANAGE_STATE作为导出函数的⾸⾏,因此定义如下:extern "c" _declspec(dllexport) int MyFunction(int x){ AFX_MANAGE_STATE(AfxGetStaticModuleState());
...//操作
}有时候为了安全起见,在每个常规DLL⾥都加上,也不会有任何问题,只是在静态连接的时候这个宏⽆效⽽已。这是导出函数的⽅法,记住只有MFC扩展DLL才能让参数和返回值使⽤MFC的数据类型。4、导出指针导出指针的⽅式如下:_declspec(dllexport) int *pint;_declspec(dllexport) CMyClass object = new CMyClass;如果声明的时候同时初始化了指针,就需要找到合适的地⽅类释放指针。在扩展DLL中有个函数DllMain()。(注意函数名中的两个l要是⼩写字母),可以在这个函数中处理指针:# include "MyClass.h"_declspec(dllexport) CMyClass *pobject = new CMyClass;DllMain(HINSTANCE hInstance,DWORD dwReason,LPVOID lpReserved){ if(dwReason == DLL_PROCESS_ATTACH)
{
.....//
}
else if(dwReason == DLL_PROCESS_DETACH)
{
delete pobject;
}
}常规DLL有⼀个从CWinApp派⽣的类对象处理DLL的开和关,可以使⽤类向导添加InitInstance/ExitInstance函数。int CMyDllApp::ExitInstance(){ delete pobject;
return CWinApp::ExitInstance();
}三、在客户程序中使⽤DLL 编译⼀个DLL时将创建两个⽂件.dll⽂件和.lib⽂件。⾸先将这两个⽂件复制到客户程序项⽬的⽂件夹⾥,这⾥需要注意DLL和客户程序的版本问题,尽量使⽤相同的版本,都使⽤RELEASE或者都是DEBUG版本。
接着就需要在客户程序中设置LIB⽂件,打开Project Settings--- >Link--->Object/library Modules中输⼊LIB的⽂件名和路径。如:Debug/。除了DLL和LIB⽂件外,客户程序需要针对导出类、函数、对象和变量的头⽂件,现在进⾏导⼊添加的关键字就是:_declspec(dllimport),如:_declspec(dllimport),如:_declspec(dllimport) int MyFunction(int);_declspec(dllimport) int MyInt;_declspec(dllimport) CMyClass object;extern "C" _declspec(dllimport) int MyFunction(int);在有的时候为了导⼊类,要把相应类的头⽂件添加到客户程序中,不同的是要修改类声明的标志:class _declspec(dllimport) CMyClass,如果创建的是扩展DLL,两个位置都是:class AFX_EXT_CLASS CMyClass。
使⽤DLL的⼀个⽐较严重的问题就是编译器之间的兼容性问题。不同的编译器对c++函数在⼆进制级别的实现⽅式是不同的。所以对基于C++的DLL,如果编译器不同就有很⿇烦的。如果创建的是MFC扩展DLL,就不会存在问题,因为它只能被动态连接到MFC的客户应⽤程序。这⾥不是本⽂讨论的重点。⼀、重新编译问题我们先来看⼀个在实际中可能遇到的问题:
⽐如现在建⽴好了⼀个DLL导出了CMyClass类,客户也能正常使⽤这个DLL,假设CMyClass对象的⼤⼩为30字节。如果我们需要修改DLL中的CMyClass类,让它有相同的函数和成员变量,但是给增加了⼀个私有的成员变量int类型,现在CMyClass对象的⼤⼩就是34字节了。当直接把这个新的DLL给客户使⽤替换掉原来30字节⼤⼩的DLL,客户应⽤程序期望的是30字节⼤⼩的对象,⽽现在却变成了⼀个34字节⼤⼩的对象,糟糕,客户程序出错了。类似的问题,如果不是导出CMyClass类,⽽在导出的函数中使⽤了CMyClass,改变对象的⼤⼩仍然会有问题的。这个时候修改这个问题的唯⼀办法就是替换客户程序中的CMyClass的头⽂件,全部重新编译整个应⽤程序,让客户程序使⽤⼤⼩为34字节的对象。这就是⼀个严重的问题,有的时候如果没有客户程序的源代码,那么我们就不能使⽤这个新的DLL了。⼆、解决⽅法
为了能避免重新编译客户程序,这⾥介绍两个⽅法:(1)使⽤接⼝类。(2)使⽤创建和销毁类的静态函数。1、使⽤接⼝类 接⼝类的也就是创建第⼆个类,它作为要导出类的接⼝,所以在导出类改变时,也不需要重新编译客户程序,因为接⼝类没有发⽣变化。 假设导出的CMyClass类有两个函数FunctionA FunctionB。现在创建⼀个接⼝类CMyInterface,下⾯就是在DLL中的CMyInterface类的头⽂件的代码:# include "MyClass.h"class _declspec(dllexport) CMyInterface{ CMyClass *pmyclass; CMyInterface(); ~CMyInterface();public: int FunctionA(int); int FunctionB(int);};⽽在客户程序中的头⽂件稍不同,不需要INCLUDE语句,因为客户程序没有它的拷贝。相反,使⽤⼀个CMyClass的向前声明,即使没有头⽂件也能编译:class _declspec(dllexport) CMyInterface{ class CMyClass;//向前声明 CMyClass *pmyclass; CMyInterface(); ~CMyInterface();public: int FunctionA(int); int FunctionB(int);};在DLL中的CMyInterface的实现如下:CMyInterface::CMyInterface(){ pmyclass = new CMyClass();}CMyInterface::~CMyInterface(){ delete pmyclass;}int CMyInterface::FunctionA(){ return pmyclass->FunctionA();}int CMyInterface::FunctionB(){ return pmyclass->FunctionB();
}.....对导出类CMyClass的每个成员函数,CMyInterface类都提供⾃⼰的对应的函数。客户程序与CMyClass没有联系,这样任意改CMyClass也不会有问题,因为CMyInterface类的⼤⼩没有发⽣变化。即使为了能访问CMyClass中的新增变量⽽给CMyInterface类加了函数也不会有问题的。 但是这种⽅法也存在明显的问题,对导出类的每个函数和成员变量都要对应实现,有的时候这个接⼝类会很庞⼤。同时增加了客户程序调⽤所需要的时间。增加了程序的开销。2、使⽤静态函数 还可以使⽤静态函数来创建和销毁类对象。创建⼀个导出类的时候,增加两个静态的公有函数CreateMe()/DestroyMe(),头⽂件如下:class _declspec(dllexport) CMyClass{ CMyClass();
~CMyClass();
public:
static CMyClass *CreateMe();
static void DestroyMe(CMyClass *ptr);
};实现函数就是:CMyClass * CMyClass::CMyClass(){ return new CMyClass;
}void CMyClass::DestroyMe(CMyClass *ptr){ delete ptr;
}然后象其他类⼀样导出CMyClass类,这个时候在客户程序中使⽤这个类的⽅法稍有不同了。如若想创建⼀个CMyClass对象,就应该是:CMyClass x;CMyClass *ptr = CMyClass::CreateMe();在使⽤完后删除:CMyClass::DestroyMe(ptr);-----------------------------------------------结束-------------------------------------------------
发布者:admin,转转请注明出处:http://www.yc00.com/web/1689609697a270337.html
评论列表(0条)