MFC扩展DLL是通常实现从现有Microsoft基础类库类派生的可重用类的DLL。
MFC扩展DLL具有下列功能和要求:
1。客户端可执行文件必须是用定义的_AFXDLL编译的MFC应用程序。
2。扩展DLL也可由动态链接到MFC的规则DLL使用。
3。扩展DLL应该用定义的_AFXEXT编译。这将强制同时定义_AFXDLL,并确保从MFC头文件中拉入正确的声明。它也确保了在生成DLL时将AFX_EXT_CLASS定义为__declspec(dllexport),这在使用此宏声明扩展DLL中的类时是必要的。
4。扩展DLL不应实例化从CWinApp派生的类,而应依赖客户端应用程序(或DLL)提供此对象。
5。但扩展DLL应提供DllMain函数,并在那里执行任何必需的初始化。
扩展DLL是使用MFC动态链接库版本(也称作共享MFC版本)生成的。只有用共享MFC版本生成的MFC可执行文件(应用程序或规则DLL)才能使用扩展DLL。客户端应用程序和扩展DLL必须使用相同版本的MFCx0.dll。使用扩展DLL,可以从MFC派生新的自定义类,然后将此“扩展”版本的MFC提供给调用DLL的应用程序。
扩展DLL也可用于在应用程序和DLL之间传递MFC派生的对象。与已传递的对象关联的成员函数存在于创建对象所在的模块中。由于在使用MFC的共享DLL版本时正确导出了这些函数,因此可以在应用程序和它加载的扩展DLL之间随意传递MFC或MFC派生的对象指针。
MFC扩展DLL使用共享MFC版本的方式与应用程序使用MFC的共享DLL版本的方式相同,但有另外几点需要注意的事项:它没有CWinApp派生对象。它必须与客户端应用程序的CWinApp派生对象一起使用。这意味着客户端应用程序拥有主消息泵、空闲循环等。
它在自己的DllMain函数中调用AfxInitExtensionModule。应检查此函数的返回值。如果从AfxInitExtensionModule返回的是零值,则从DllMain函数返回0。
如果扩展DLL希望将CRuntimeClass对象或资源导出到应用程序中,它将在初始化期间创建一个CDynLinkLibrary对象。
在MFC4.0版之前,这类DLL称作AFXDLL。AFXDLL引用生成DLL时定义的_AFXDLL预处理器符号。
共享版本MFC的导入库的命名依据MFCDLL命名约定中描述的约定。VisualC++提供预生成的MFCDLL版本,以及若干可以使用并用应用程序发布的非MFCDLL。它们记录在安装到ProgramFilesMicrosoftVisualStudio文件夹的Redist.txt中。
如果要使用.def文件导出,请在头文件的开始和结尾处放置下列代码:
#undef AFX_DATA
#define AFX_DATAAFX_EXT_DATA
//<body of your header file>
#undef AFX_DATA
#define AFX_DATA
这四行确保为扩展DLL正确编译代码。省去这四行可能导致DLL不能正确地编译或链接。
如果需要与MFCDLL来回传递MFC或MFC派生对象的指针,DLL应为扩展DLL。与已传递的对象关联的成员函数存在于创建对象所在的模块中。由于在使用MFC的共享DLL版本时正确导出了这些函数,因此可以在应用程序和它加载的扩展DLL之间随意传递MFC或MFC派生的对象指针。
由于存在C++名称重整和导出问题,在同一DLL的调试版本和零售版本及用于不同平台的DLL之间,扩展DLL中的导出列表可能不同。零售版本的MFCx0.dll有大约2,000个导出入口点;调试版本的MFCx0D.dll有大约3,000个导出入口点。
内存管理
MFCx0.dll和所有加载到客户端应用程序的地址空间中的扩展DLL使用相同的内存分配器、资源加载和其他MFC“全局”状态,就好像它们在同一个应用程序中一样。这一点意义重大,因为非MFCDLL库和规则DLL正好相反,它们让每个DLL在各自的内存池之外分配。
如果扩展DLL分配内存,则该内存能够随意与任何其他应用程序分配的对象相混合。同时,如果动态链接到MFC的应用程序失败,操作系统的保护将维护共享DLL的任何其他MFC应用程序的完整性。
同样,其他“全局”MFC状态(如从中加载资源的当前可执行文件)也在客户端应用程序与MFCx0.dll本身以及所有MFC扩展DLL之间共享。
共享资源和类
导出资源的操作是通过资源列表完成的。每个应用程序均包含CDynLinkLibrary对象的单向链接表。查找资源时,大部分加载资源的标准MFC实现首先查看当前资源模块(AfxGetResourceHandle),如果未找到资源,则浏览CDynLinkLibrary对象的列表,并尝试加载请求的资源。
浏览列表的做法有一些缺点,即速度稍慢且需要管理资源ID范围。它的优点在于链接到几个扩展DLL的客户端应用程序可以使用DLL提供的任何资源,而无需指定DLL实例句柄。AfxFindResourceHandle是用于浏览资源列表以查找给定匹配的API。它采用资源的名称和类型,并从最先找到匹配项的位置返回资源句柄(或NULL)。
如果不想浏览列表,而是仅从特定的位置加载资源,请使用函数AfxGetResourceHandle和AfxSetResourceHandle保存旧句柄和设置新句柄。返回到客户端应用程序之前,请务必还原旧资源句柄。有关使用此方法显式加载菜单的示例,请参见MFC示例DLLHUSK中的Testdll2.cpp。
动态创建给定了MFC名称的MFC对象与此类似。MFC对象反序列化机制需要注册所有的CRuntimeClass对象,以便可以通过基于以前存储的内容动态创建所需类型的C++对象来重新构造。
在MFC示例DLLHUSK中,此列表的形式如下:
head -> DLLHUSK.EXE - or - DLLHUSK.EXE
| |
TESTDLL2.DLL TESTDLL2.DLL
| |
TESTDLL1.DLL TESTDLL1.DLL
| |
MFCOxxD.DLL |
| |
MFCDxxD.DLL |
| |
MFCxxD.DLL MFCxx.DLL
其中xx是版本号;例如42表示4.2版。
MFCxx.dll通常排在资源和类列表的最后。MFCxx.dll包含所有的标准MFC资源,其中包括所有标准命令ID的提示字符串。将MFCxx.dll放在列表的最后使得DLL和客户端应用程序本身不必有自己的标准MFC资源副本,而是可以依赖MFCxx.dll中的共享资源。
将所有DLL的资源名和类名合并到客户端应用程序的命名空间中,这种做法的缺点是需要小心选取ID或名称。
DLLHUSK示例通过使用多个头文件来管理共享资源命名空间。
如果MFC扩展DLL需要为每个应用程序维护额外的数据,可从CDynLinkLibrary派生一个新类并在DllMain中创建此类。运行时,DLL会检查当前应用程序的CDynLinkLibrary对象列表,以查找用于特定扩展DLL的对象。
使用 AFX_EXT_CLASS 导出和导入
扩展DLL使用 AFX_EXT_CLASS 宏导出类;链接到扩展 DLL 的可执行文件使用该宏导入类。使用 AFX_EXT_CLASS 宏,用于生成扩展 DLL 的相同头文件可以与链接到 DLL 的可执行文件一起使用。
在 DLL 的头文件中,将 AFX_EXT_CLASS 关键字添加到类的声明中,如下所示:
class AFX_EXT_CLASS CMyClass : public Cdocument{// };
当定义了预处理 _AFXDLL 和 _AFXEXT 时,该宏被 MFC 定义为 __declsp(dllexport).但当定义了 _AFXDLL 而未定义 _AFXEXT 时,该宏被定义为 __declspec(dllimport)。定义后,预处理器符号 _AFXDLL 指示共享 MFC 版本正在由目标可执行文件(DLL 或应用程序)使用。当 _AFXDLL 和 _AFXEXT 都定义了时,这指示目标可执行文件是扩展 DLL。
由于从扩展 DLL 导出时,AFX_EXT_CLASS 被定义为 __declspec(dllexport),因此可以导出整个类,而不必将该类的所有符号的修饰名放到 .DEF 文件中。此方法由 MFC 示例DLLHUSK使用。
虽然使用此方法可以避免创建 .DEF 文件和类的所有修饰名,但由于名称可以按序号导出,创建 .DEF 文件的效率更高。若要使用 .DEF 文件导出方法,请将下列代码放在头文件的开头和结尾处:
#undef AFX_DATA
#define AFX_DATA AFX_EXT_DATA
//
#undef AFX_DATA
#define AFX_DATA
警告导出内联函数时要小心,因为它们有可能导致版本冲突。内联函数扩展到应用程序代码中;因此,如果以后重写内联函数,除非重新编译应用程序本身,否则内联函数不会被更新。(通常,不用重新生成使用 DLL 函数的应用程序就可以更新 DLL 函数。)
导出类中的个别成员
有时,您可能希望导出类中的个别成员。例如,如果导出 CDialog 派生类,可能只需要导出构造函数和 DoModal 调用。可以对需要导出的个别成员使用 AFX_EXT_CLASS。
例如:
class CExampleDialog : public CDialog
{
public:
AFX_EXT_CLASS CExampleDialog();
AFX_EXT_CLASS int DoModal();
...
// rest of class definition
...
};
您不再导出类的所有成员,但由于 MFC 宏的工作方式,您可能会遇到其他问题。几个 MFC 的 Helper 宏实际声明或定义数据成员。因此,还必须从 DLL 导出这些数据成员。
例如,当生成扩展 DLL 时,DECLARE_DYNAMIC 宏的定义如下:
#define DECLARE_DYNAMIC(class_name)
protected:
static CRuntimeClass* PASCAL _GetBaseClass();
public:
static AFX_DATA CRuntimeClass class##class_name;
virtual CRuntimeClass* GetRuntimeClass() const;
以 static AFX_DATA 打头的行声明类的内部静态对象。若要正确导出该类并从客户端可执行文件访问运行时信息,必须导出此静态对象。由于静态对象是用 AFX_DATA 修饰符声明的,因此只需在生成 DLL 时将 AFX_DATA 定义为 __declspec(dllexport),并在生成客户端可执行文件时将 AFX_DATA 定义为 __declspec(dllimport)。由于已经以此方式定义了 AFX_EXT_CLASS,因此只需参考类定义,将 AFX_DATA 重定义为与 AFX_EXT_CLASS 相同。
例如:
#undef AFX_DATA
#define AFX_DATA AFX_EXT_CLASS
class CExampleView : public CView
{
DECLARE_DYNAMIC()
// ... class definition ...
};
#undef AFX_DATA
#define AFX_DATA
MFC 总是在其宏的内部定义的数据项上使用 AFX_DATA 符号,因此该技术适用于所有这类情况。例如,它适用于 DECLARE_MESSAGE_MAP。
注意如果导出整个类而非选定的类成员,静态数据成员将自动导出。