此说明介绍如何使用 MFCxx.DLL
MFC 应用程序和 MFC 扩展 DLL 的共享动态链接库( MFCxxD.DLL
其中 xx 是 MFC 版本号)。 有关常规 MFC DLL 的详细信息,请参阅 将 MFC 用作 DLL 的一部分。
此技术说明涵盖 DLL 的三个方面。 最后两个用户适用于更高级的用户:
如果有兴趣使用 MFC 生成可与非 MFC 应用程序(称为 常规 MFC DLL)一起使用的 DLL,请参阅 技术说明 11。
MFCxx.DLL支持概述:术语和文件
常规 MFC DLL:使用常规 MFC DLL 使用某些 MFC 类生成独立 DLL。 应用/DLL 边界的接口是“C”接口,客户端应用程序不必是 MFC 应用程序。
常规 MFC DLL 是 MFC 1.0 中支持的 DLL 版本。 技术 说明 11 和 MFC 高级概念示例 DLLScreenCap
介绍了它们。
注释
从 Visual C++ 版本 4.0 开始, 术语 USRDLL 已过时,并且已替换为静态链接到 MFC 的常规 MFC DLL。 还可以生成动态链接到 MFC 的常规 MFC DLL。
MFC 3.0(及更高版本)支持具有所有新功能(包括 OLE 和数据库类)的常规 MFC DLL。
AFXDLL:也称为 MFC 库的共享版本。 它是 MFC 2.0 中添加的新 DLL 支持。 MFC 库本身位于许多 DLL 中(如下所述)。 客户端应用程序或 DLL 动态链接它所需的 DLL。 跨应用程序/DLL 边界的接口是C++/MFC 类接口。 客户端应用程序必须是 MFC 应用程序。 此 DLL 支持所有 MFC 3.0 功能(异常:数据库类不支持 UNICODE)。
注释
从 Visual C++ 版本 4.0 开始,这种类型的 DLL 称为“扩展 DLL”。
此说明将用于 MFCxx.DLL
引用整个 MFC DLL 集,其中包括:
调试:
MFCxxD.DLL
(组合)和MFCSxxD.LIB
(静态)。发布:
MFCxx.DLL
(组合)和MFCSxx.LIB
(静态)。Unicode 调试:
MFCxxUD.DLL
(组合)和MFCSxxD.LIB
(静态)。Unicode 版本:
MFCxxU.DLL
(组合)和MFCSxxU.LIB
(静态)。
注释
库 MFCSxx[U][D].LIB
与 MFC 共享 DLL 结合使用。 这些库包含必须静态链接到应用程序或 DLL 的代码。
应用程序链接到相应的导入库:
调试:
MFCxxD.LIB
发布:
MFCxx.LIB
Unicode 调试:
MFCxxUD.LIB
Unicode 版本:
MFCxxU.LIB
MFC 扩展 DLL 是扩展MFCxx.DLL
的 DLL(或其他 MFC 共享 DLL)。 在这里,MFC 组件体系结构将启动。 如果从 MFC 类派生一个有用的类,或生成另一个类似于 MFC 的工具包,则可以将其放在 DLL 中。 DLL 使用 MFCxx.DLL
的最终客户端应用程序也一样。 MFC 扩展 DLL 允许可重用叶类、可重用基类以及可重用视图和文档类。
优点和缺点
为何应使用 MFC 的共享版本
使用共享库可能会导致较小的应用程序。 (使用大多数 MFC 库的最小应用程序小于 10K)。
MFC 的共享版本支持 MFC 扩展 DLL 和常规 MFC DLL。
生成使用共享 MFC 库的应用程序比静态链接的 MFC 应用程序更快。 这是因为不需要链接 MFC 本身。 在链接器必须压缩调试信息的生成中
DEBUG
尤其如此。 当应用程序链接到已包含调试信息的 DLL 时,压缩的调试信息更少。
为何不应使用 MFC 的共享版本:
- 寄送使用共享库的应用程序需要你随程序一起寄送
MFCxx.DLL
其他库。MFCxx.DLL
可以像许多 DLL 一样自由重新分发,但仍必须在安装程序中安装 DLL。 此外,还必须交付程序本身和 MFC DLL 使用的其他可再发行库。
如何编写 MFC 扩展 DLL
MFC 扩展 DLL 是一个 DLL,其中包含用于扩展 MFC 类功能的类和函数。 MFC 扩展 DLL 使用共享 MFC DLL 的方式与应用程序使用共享 MFC DLL 的方式相同,还有一些其他注意事项:
生成过程类似于生成使用共享 MFC 库的应用程序,其中包含一些额外的编译器和链接器选项。
MFC 扩展 DLL 没有
CWinApp
派生类。MFC 扩展 DLL 必须提供特殊
DllMain
。 AppWizard 提供DllMain
可以修改的函数。如果 MFC 扩展 DLL 将类型或资源导出
CRuntimeClass
到应用程序,则 MFC 扩展 DLL 通常提供初始化例程来创建一个CDynLinkLibrary
。 如果每个应用程序数据必须由 MFC 扩展 DLL 维护,可以使用派生类CDynLinkLibrary
。
下面更详细地介绍了这些注意事项。 另请参阅 MFC 高级概念示例 DLLHUSK
。 它演示如何:
使用共享库生成应用程序。 (
DLLHUSK.EXE
是动态链接到 MFC 库和其他 DLL 的 MFC 应用程序。生成 MFC 扩展 DLL。 (它显示了生成 MFC 扩展 DLL 时使用的特殊标志。
_AFXEXT
)生成 MFC 扩展 DLL 的两个示例。 其中一个显示了 MFC 扩展 DLL 的基本结构(TESTDLL1),另一个显示导出整个类接口(TESTDLL2)。
客户端应用程序和任何 MFC 扩展 DLL 都必须使用相同的版本 MFCxx.DLL
。 遵循 MFC DLL 的约定,并提供 MFC 扩展 DLL 的调试和发布版本/release
。 这种做法允许客户端程序生成其应用程序的调试和发布版本,并将其与所有 DLL 的相应调试或发布版本链接。
注释
由于C++名称混乱和导出问题,因此 MFC 扩展 DLL 中的导出列表在不同平台的调试和发布版本之间可能有所不同。 该版本 MFCxx.DLL
有大约 2000 个导出的入口点;调试 MFCxxD.DLL
有大约 3000 个导出的入口点。
内存管理快速说明
此技术说明即将结束的标题为“内存管理”的部分介绍了 MFC 共享版本的实现 MFCxx.DLL
。 此处介绍了实现 MFC 扩展 DLL 所需的信息。
MFCxx.DLL
加载到客户端应用程序的地址空间中的所有 MFC 扩展 DLL 都将使用相同的内存分配器、资源加载和其他 MFC“全局”状态,就像它们位于同一应用程序中一样。 这一点很重要,因为非 MFC DLL 库和静态链接到 MFC 的常规 MFC DLL 完全相反:每个 DLL 都分配出自己的内存池。
如果 MFC 扩展 DLL 分配内存,则该内存可与任何其他应用程序分配的对象自由混合。 此外,如果使用共享 MFC 库的应用程序崩溃,作系统将维护共享 DLL 的任何其他 MFC 应用程序的完整性。
同样,其他“全局”MFC 状态(如要从中加载资源的当前可执行文件)也会在客户端应用程序、所有 MFC 扩展 DLL 和 MFCxx.DLL
自身之间共享。
生成 MFC 扩展 DLL
可以使用 AppWizard 创建 MFC 扩展 DLL 项目,并自动生成相应的编译器和链接器设置。 它还生成一个 DllMain
可以修改的函数。
如果要将现有项目转换为 MFC 扩展 DLL,请从使用 MFC 的共享版本生成的标准设置开始。 然后进行以下更改:
添加到
/D_AFXEXT
编译器标志。 在“项目属性”对话框中,选择 C/C++>Preprocessor 类别。 添加到_AFXEXT
“定义宏”字段,用分号分隔每个项。/Gy
删除编译器开关。 在“项目属性”对话框中,选择 “C/C++>Code 生成 ”类别。 请确保未启用 “启用 Function-Level 链接 ”属性。 通过此设置可以更轻松地导出类,因为链接器不会删除未引用的函数。 如果原始项目生成了静态链接到 MFC 的常规 MFC DLL,请将/MT
(或) 编译器选项更改为/MD
(或/MTd
/MDd
) 。使用 LINK 选项生成导出库
/DLL
。 创建新目标并将 Win32 Dynamic-Link 库指定为目标类型时,此选项将设置。
更改头文件
MFC 扩展 DLL 的一般目标是将一些常见功能导出到一个或多个可以使用该功能的应用程序。 实质上,DLL 导出类和全局函数供客户端应用程序使用。
若要确保每个成员函数都相应地标记为导入或导出,请使用特殊声明 __declspec(dllexport)
和 __declspec(dllimport)
。 当客户端应用程序使用类时,你希望将它们声明为 __declspec(dllimport)
。 生成 MFC 扩展 DLL 本身时,函数应声明为 __declspec(dllexport)
。 生成的 DLL 还必须导出函数,以便客户端程序可以在加载时绑定到它们。
若要导出整个类,请在 AFX_EXT_CLASS
类定义中使用。 框架将此宏 __declspec(dllexport)
定义为何时 _AFXDLL
和 _AFXEXT
定义宏,但将其定义为 __declspec(dllimport)
未定义时 _AFXEXT
。
_AFXEXT
仅在生成 MFC 扩展 DLL 时定义。 例如:
class AFX_EXT_CLASS CExampleExport : public CObject
{ /* ... class definition ... */ };
不导出整个类
有时,你可能只想导出类的单个必需成员。 例如,如果导出 CDialog
派生类,则可能需要导出构造函数和 DoModal
调用。 可以使用 DLL 的 DEF 文件导出这些成员,但也可以在需要导出的各个成员上以完全相同的方式使用 AFX_EXT_CLASS
。
例如:
class CExampleDialog : public CDialog
{
public:
AFX_EXT_CLASS CExampleDialog();
AFX_EXT_CLASS int DoModal();
// rest of class definition
// ...
};
执行此作时,可能会遇到其他问题,因为不会导出该类的所有成员。 问题在于 MFC 宏的工作方式。 MFC 的一些帮助程序宏实际上声明或定义数据成员。 DLL 也需要导出这些数据成员。
例如,生成 MFC 扩展 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
声明类中的静态对象的行。 若要正确导出此类并从客户端 EXE 访问运行时信息,需要导出此静态对象。 由于静态对象是使用修饰符AFX_DATA
声明的,因此只需在生成 DLL 时定义AFX_DATA
__declspec(dllexport)
。 将其 __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。
注释
如果要导出整个类,而不是导出类的所选成员,则会自动导出静态数据成员。
可以使用同一技术为使用DECLARE_SERIAL和IMPLEMENT_SERIAL宏的类自动导出 CArchive
提取运算符。 使用以下代码将类声明(位于头文件中)括号来导出存档运算符:
#undef AFX_API
#define AFX_API AFX_EXT_CLASS
/* your class declarations here */
#undef AFX_API
#define AFX_API
_AFXEXT 的限制
只要没有多层 MFC 扩展 DLL,就可以对 MFC 扩展 DLL 使用 _AFXEXT
预处理器符号。 如果 MFC 扩展 DLL 调用或派生自你自己的 MFC 扩展 DLL 中的类,然后从 MFC 类派生,则必须使用你自己的预处理器符号以避免歧义。
问题是,在 Win32 中,必须将任何数据显式声明为 __declspec(dllexport)
从 DLL 导出数据,并从 __declspec(dllimport)
DLL 导入数据。 定义 _AFXEXT
时,MFC 标头可确保 AFX_EXT_CLASS
正确定义。
如果有多个层,则一个符号(如 AFX_EXT_CLASS
不足):MFC 扩展 DLL 可以导出其自己的类,也可以从另一个 MFC 扩展 DLL 导入其他类。 若要解决此问题,请使用一个特殊的预处理器符号,指示要生成 DLL 本身,而不是使用 DLL。 例如,假设有两个 MFC 扩展 DLL, A.DLL
以及 B.DLL
。 它们分别导出 A.H
一些类,以及 B.H
分别导出一些类。
B.DLL
使用来自 A.DLL
. 的类 头文件类似于以下形式:
/* A.H */
#ifdef A_IMPL
#define CLASS_DECL_A __declspec(dllexport)
#else
#define CLASS_DECL_A __declspec(dllimport)
#endif
class CLASS_DECL_A CExampleA : public CObject
{ /* ... class definition ... */ };
/* B.H */
#ifdef B_IMPL
#define CLASS_DECL_B __declspec(dllexport)
#else
#define CLASS_DECL_B __declspec(dllimport)
#endif
class CLASS_DECL_B CExampleB : public CExampleA
{ /* ... class definition ... */ };
生成时 A.DLL
,使用 /DA_IMPL
生成时 B.DLL
生成,生成时使用 /DB_IMPL
。 通过使用每个 DLL 的单独符号导出 CExampleB
,并在 CExampleA
生成 B.DLL
时导入。
CExampleA
在生成 A.DLL
和导入时由某些其他客户端使用 B.DLL
时导出。
使用内置 AFX_EXT_CLASS
和 _AFXEXT
预处理器符号时,无法完成这种类型的分层。 上述技术采用与 MFC 相同的方式解决了此问题。 MFC 在生成其 OLE、数据库和网络 MFC 扩展 DLL 时使用此技术。
仍然不导出整个类
同样,在未导出整个类时,必须特别小心。 确保正确导出 MFC 宏创建的必要数据项。 可以通过重新定义 AFX_DATA
特定类的宏来执行此作。 每当未导出整个类时,重新定义它。
例如:
// A.H
#ifdef A_IMPL
#define CLASS_DECL_A _declspec(dllexport)
#else
#define CLASS_DECL_A _declspec(dllimport)
#endif
#undef AFX_DATA
#define AFX_DATA CLASS_DECL_A
class CExampleA : public CObject
{
DECLARE_DYNAMIC()
CLASS_DECL_A int SomeFunction();
// class definition
// ...
};
#undef AFX_DATA
#define AFX_DATA
DllMain
下面是应在 MFC 扩展 DLL 的主源文件中放置的代码。 它应该在标准包括之后。 使用 AppWizard 为 MFC 扩展 DLL 创建初学者文件时,它将为你提供一个 DllMain
。
#include "afxdllx.h"
static AFX_EXTENSION_MODULE extensionDLL;
extern "C" int APIENTRY
DllMain(HINSTANCE hInstance, DWORD dwReason, LPVOID)
{
if (dwReason == DLL_PROCESS_ATTACH)
{
// MFC extension DLL one-time initialization
if (!AfxInitExtensionModule(
extensionDLL, hInstance))
return 0;
// TODO: perform other initialization tasks here
}
else if (dwReason == DLL_PROCESS_DETACH)
{
// MFC extension DLL per-process termination
AfxTermExtensionModule(extensionDLL);
// TODO: perform other cleanup tasks here
}
return 1; // ok
}
用于捕获模块的运行时类(CRuntimeClass
结构)及其对象工厂(COleObjectFactory
对象)的调用AfxInitExtensionModule
,以便在创建对象时CDynLinkLibrary
使用。 (可选)调用, AfxTermExtensionModule
以允许 MFC 清除 MFC 扩展 DLL 时,每个进程分离(在进程退出时发生,或者当调用卸载 FreeLibrary
DLL 时)从 MFC 扩展 DLL 中清除 MFC 扩展 DLL。 由于大多数 MFC 扩展 DLL 不会动态加载(通常,它们通过导入库链接),因此通常不需要调用 AfxTermExtensionModule
。
如果应用程序动态加载并释放 MFC 扩展 DLL,请确保调用 AfxTermExtensionModule
如上所示。 此外,如果应用程序使用多个线程,或者如果应用程序动态加载 MFC 扩展 DLL,请务必使用AfxLoadLibrary
和 (而不是 Win32 函数LoadLibrary
FreeLibrary
)。AfxFreeLibrary
使用 AfxLoadLibrary
和 AfxFreeLibrary
确保加载和卸载 MFC 扩展 DLL 时执行的启动和关闭代码不会损坏全局 MFC 状态。
头文件 AFXDLLX.H
包含 MFC 扩展 DLL 中使用的结构的特殊定义,例如 AFX_EXTENSION_MODULE
定义和 CDynLinkLibrary
。
全局 扩展DLL 必须声明为如下所示。 与 16 位版本的 MFC 不同,在此期间可以分配内存和调用 MFC 函数,因为 MFCxx.DLL
调用时 DllMain
完全初始化了内存。
共享资源和类
简单的 MFC 扩展 DLL 只需要将一些低带宽函数导出到客户端应用程序,而无需再执行其他作。 更多用户界面密集型 DLL 可能需要将资源和C++类导出到客户端应用程序。
导出资源通过资源列表来进行。 在每个应用程序中都是一个单项链接的对象 CDynLinkLibrary
列表。 查找资源时,加载资源的大多数标准 MFC 实现将首先查看当前资源模块(AfxGetResourceHandle
),如果未找到,则查看尝试加载请求的资源的对象列表 CDynLinkLibrary
。
给定C++类名的情况下动态创建C++对象的过程类似。 MFC 对象反序列化机制需要注册所有 CRuntimeClass
对象,以便它可以根据之前存储的对象动态创建所需类型的C++对象来重新构造。
如果希望客户端应用程序在 MFC 扩展 DLL DECLARE_SERIAL
中使用类,则需要导出类,使其对客户端应用程序可见。 它还通过浏览 CDynLinkLibrary
列表来完成。
在 MFC 高级概念示例中 DLLHUSK
,列表如下所示:
head -> DLLHUSK.EXE - or - DLLHUSK.EXE
| |
TESTDLL2.DLL TESTDLL2.DLL
| |
TESTDLL1.DLL TESTDLL1.DLL
| |
| |
MFC90D.DLL MFC90.DLL
该 MFCxx.DLL
条目通常位于资源和类列表中。
MFCxx.DLL
包括所有标准 MFC 资源,包括所有标准命令 ID 的提示字符串。 将它放在列表的末尾可让 DLL 和客户端应用程序本身都依赖于共享资源 MFCxx.DLL
,而不是有自己的副本。
将所有 DLL 的资源和类名称合并到客户端应用程序的名称空间中具有缺点,即必须注意选择的 ID 或名称。 可以通过不将资源或 CDynLinkLibrary
对象导出到客户端应用程序来禁用此功能。 此示例 DLLHUSK
使用多个头文件管理共享资源名称空间。 有关使用共享资源文件的更多提示,请参阅 技术说明 35 。
初始化 DLL
如上所述,通常需要创建一个 CDynLinkLibrary
对象来将资源和类导出到客户端应用程序。 需要提供导出的入口点来初始化 DLL。 最少的是,它是一个 void
例程,不采用任何参数,不返回任何参数,但它可以是你喜欢的任何内容。
如果要使用 DLL 的每个客户端应用程序都必须调用此初始化例程(如果使用此方法)。 还可以在DllMain
调用AfxInitExtensionModule
后立即分配此CDynLinkLibrary
对象。
初始化例程必须在当前应用程序的堆中创建对象 CDynLinkLibrary
,并连接到 MFC 扩展 DLL 信息。 可以通过定义如下所示的函数来执行此作:
extern "C" extern void WINAPI InitXxxDLL()
{
new CDynLinkLibrary(extensionDLL);
}
本示例中的例程名称 InitXxxDLL 可以是所需的任何内容。 它不需要 extern "C"
,但它使导出列表更易于维护。
注释
如果从常规 MFC DLL 使用 MFC 扩展 DLL,则必须导出此初始化函数。 在使用任何 MFC 扩展 DLL 类或资源之前,必须从常规 MFC DLL 调用此函数。
导出条目
导出类的简单方法是使用__declspec(dllimport)
__declspec(dllexport)
和导出每个类和全局函数。 这要容易得多,但比命名 DEF 文件中每个入口点的效率要低得多,如下所示。 这是因为你对导出的函数的控制较少。 而且,不能按序号导出函数。 TESTDLL1和TESTDLL2使用此方法导出其条目。
更有效的方法是通过在 DEF 文件中命名每个条目来导出每个条目。 此方法由 MFCxx.DLL
. 由于我们要从 DLL 有选择性地导出,因此必须决定要导出的特定接口。 这很困难,因为必须以 DEF 文件中条目的形式指定链接器的完整名称。 请勿导出任何C++类,除非你确实需要具有符号链接。
如果以前尝试使用 DEF 文件导出C++类,可能需要开发一个工具来自动生成此列表。 可以使用两个阶段链接过程完成此作。 在没有导出的情况下链接 DLL 一次,并允许链接器生成 MAP 文件。 MAP 文件包含应导出的函数列表。 通过重新排列,可以使用它为 DEF 文件生成 EXPORT 条目。 导出列表 MFCxx.DLL
和 OLE 和数据库 MFC 扩展 DLL(以数千为单位)是使用此类过程生成的(尽管它不是完全自动的,并且需要一段时间每隔一次进行一次手动优化)。
CWinApp 与 CDynLinkLibrary
MFC 扩展 DLL 没有 CWinApp
其自己的派生对象。 相反,它必须与客户端应用程序的派生对象一起使用 CWinApp
。 这意味着客户端应用程序拥有主消息泵、空闲循环等。
如果 MFC 扩展 DLL 需要为每个应用程序维护额外的数据,则可以从 CDynLinkLibrary
上述例程中派生并创建一 InitXxxDLL
个新类。 运行时,DLL 可以检查当前应用程序的对象列表,以查找该特定 MFC 扩展 DLL 的对象列表 CDynLinkLibrary
。
在 DLL 实现中使用资源
如上所述,默认资源加载将遍视具有请求资源的第一个 EXE 或 DLL 的对象列表 CDynLinkLibrary
。 无论资源位于何处,所有 MFC API 和所有内部代码都用于 AfxFindResourceHandle
遍查资源列表以查找任何资源。
如果只想从特定位置加载资源,请使用 API AfxGetResourceHandle
并 AfxSetResourceHandle
保存旧句柄并设置新句柄。 在返回到客户端应用程序之前,请务必还原旧资源句柄。 示例TESTDLL2使用此方法显式加载菜单。
浏览列表有一些缺点:它稍微慢一些,并且需要管理资源 ID 范围。 其优点是链接到多个 MFC 扩展 DLL 的客户端应用程序可以使用任何 DLL 提供的资源,而无需指定 DLL 实例句柄。
AfxFindResourceHandle
是用于遍历资源列表以查找给定匹配项的 API。 它采用资源的名称和类型,并返回资源句柄,其中首次查找资源或 NULL。
编写使用 DLL 版本的应用程序
应用程序要求
使用 MFC 共享版本的应用程序必须遵循一些基本规则:
它必须有一个
CWinApp
对象,并遵循消息泵的标准规则。它必须使用一组必需的编译器标志进行编译(请参阅下文)。
它必须与 MFCxx 导入库链接。 通过设置所需的编译器标志,MFC 标头在链接时确定应用程序应与之链接的库。
若要运行可执行文件,
MFCxx.DLL
必须位于路径或 Windows 系统目录中。
使用开发环境进行构建
如果对大多数标准默认值使用内部生成文件,可以轻松更改项目以生成 DLL 版本。
以下步骤假定你有一个正常运行的 MFC 应用程序( NAFXCWD.LIB
用于调试)和 NAFXCW.LIB
(对于发布),并且你想要将其转换为使用 MFC 库的共享版本。 运行 Visual Studio 环境并具有内部项目文件。
- 在“ 项目 ”菜单上,选择“ 属性”。 在“项目默认值”下的“常规”页中,将Microsoft基础类设置为在共享 DLL(MFCxx(d).dll中使用 MFC)。
使用 NMAKE 进行生成
如果使用的是编译器的外部生成文件功能,或者直接使用 NMAKE,则必须编辑生成文件以支持所需的编译器和链接器选项。
所需的编译器标志:
/D_AFXDLL /MD
/D_AFXDLL
标准 MFC 标头需要 _AFXDLL
定义符号。
-
/MD
应用程序必须使用 C 运行时库的 DLL 版本。
所有其他编译器标志都遵循 MFC 默认值(例如, _DEBUG
用于调试)。
编辑库的链接器列表。 更改为NAFXCWD.LIB
和更改为 NAFXCW.LIB
MFCxx.LIB
。MFCxxD.LIB
将 LIBC.LIB
替换为 MSVCRT.LIB
。 与任何其他 MFC 库一样,请务必 MFCxxD.LIB
放置在任何 C 运行时库 之前 。
(可选)添加到 /D_AFXDLL
发布和调试资源编译器选项(实际使用 /R
的资源编译选项)。 此选项通过共享 MFC DLL 中存在的资源来缩小最终可执行文件。
进行这些更改后,需要完全重新生成。
生成示例
大多数 MFC 示例程序都可以从 Visual C++或命令行中的共享 NMAKE 兼容 MAKEFILE 生成。
若要转换上述任何示例 MFCxx.DLL
,可以将 MAK 文件加载到 Visual C++,并设置项目选项,如前所述。 如果使用 NMAKE 生成,则可以在 NMAKE 命令行上指定 AFXDLL=1
,并使用共享 MFC 库生成示例。
MFC 高级概念示例 DLLHUSK 是使用 MFC 的 DLL 版本生成的。 此示例不仅演示了如何生成链接 MFCxx.DLL
的应用程序,还演示了 MFC DLL 打包选项的其他功能,如本技术说明后面介绍的 MFC 扩展 DLL。
打包说明
DLL(MFCxx.DLL
和 MFCxxU.DLL
)的发布版本可以自由再发行。 DLL 的调试版本不可自由再发行,只能在应用程序开发期间使用。
调试 DLL 随调试信息一起提供。 通过使用 Visual C++ 调试器,可以跟踪应用程序和 DLL 的执行。 发布 DLL (MFCxx.DLL
和 MFCxxU.DLL
) 不包含调试信息。
如果自定义或重新生成 DLL,则应将其称为“MFCxx”以外的内容。 MFC SRC 文件 MFCDLL.MAK
描述生成选项,并包含用于重命名 DLL 的逻辑。 重命名文件是必需的,因为这些 DLL 可能由许多 MFC 应用程序共享。 让自定义版本的 MFC DLL 替换系统上安装的 MFC DLL 可能会中断使用共享 MFC DLL 的另一个 MFC 应用程序。
不建议重新生成 MFC DLL。
如何实现MFCxx.DLL
以下部分介绍了如何实现 MFC DLL(MFCxx.DLL
和 MFCxxD.DLL
)。 如果只想将 MFC DLL 与应用程序一起使用,则了解此处的详细信息也并不重要。 此处的详细信息对于了解如何编写 MFC 扩展 DLL 并不重要,但理解此实现可能有助于编写自己的 DLL。
实现概述
MFC DLL 实际上是 MFC 扩展 DLL 的一个特殊情况,如上所述。 它具有大量类的导出。 在 MFC DLL 中,我们做了一些其他作,使其比常规 MFC 扩展 DLL 更特别。
Win32 执行大部分工作
MFC 的 16 位版本需要多种特殊技术,包括堆栈段上的每应用数据、大约 80x86 程序集代码创建的特殊段、每个进程异常上下文和其他技术。 Win32 直接支持 DLL 中的每进程数据,这是大部分时间所需的数据。 在大多数情况下 MFCxx.DLL
,只 NAFXCW.LIB
打包在 DLL 中。 如果你查看 MFC 源代码,你会发现几个 #ifdef _AFXDLL
情况,因为不需要进行很多特殊情况。 专门在 Windows 3.1 上处理 Win32(也称为 Win32s)的特殊情况。 Win32s 不支持直接按进程 DLL 数据。 MFC DLL 必须使用线程本地存储 (TLS) Win32 API 来获取处理本地数据。
对库源、其他文件的影响
版本 _AFXDLL
对普通 MFC 类库源和标头的影响相对较小。 主标头包含AFXWIN.H
一个特殊的版本文件(AFXV_DLL.H
)和一个附加的头文件(AFXDLL_.H
)。 标头 AFXDLL_.H
包括 CDynLinkLibrary
应用程序和 MFC 扩展 DLL 的 _AFXDLL
类和其他实现详细信息。 此 AFXDLLX.H
标头用于生成 MFC 扩展 DLL(有关详细信息,请参阅上文)。
MFC SRC 中 MFC 库的常规源在 #ifdef 下 _AFXDLL
有一些额外的条件代码。 另一个源文件 (DLLINIT.CPP
) 包含额外的 DLL 初始化代码,以及 MFC 共享版本的其他粘附。
为了生成 MFC 的共享版本,提供了其他文件。 (请参阅下文,了解有关如何生成 DLL 的详细信息。
两个 DEF 文件用于导出用于调试 (
MFCxxD.DEF
) 和发布 (MFCxx.DEF
) 版本的 DLL 的 MFC DLL 入口点。RC 文件 (
MFCDLL.RC
) 包含 DLL 的所有标准 MFC 资源和VERSIONINFO
资源。提供 CLW 文件 (
MFCDLL.CLW
) 以允许使用 ClassWizard 浏览 MFC 类。 此功能不特定于 MFC 的 DLL 版本。
内存管理
使用 MFCxx.DLL
由共享 C 运行时 DLL 提供的 MSVCRTxx.DLL
通用内存分配器的应用程序。 应用程序、任何 MFC 扩展 DLL 以及 MFC DLL 都使用此共享内存分配器。 通过使用共享 DLL 进行内存分配,MFC DLL 可以分配稍后由应用程序释放的内存,反之亦然。 由于应用程序和 DLL 都必须使用相同的分配器,因此不应重写C++全局 operator new
或 operator delete
。 相同的规则适用于 C 运行时内存分配例程的其余部分(例如malloc
、realloc
free
和其他)。
序号和类__declspec(dllexport)和 DLL 命名
我们不会使用 class
__declspec(dllexport)
C++编译器的功能。 相反,类库源(MFCxx.DEF
和 MFCxxD.DEF
)中包含了导出列表。 仅导出一组选择的入口点(函数和数据)。 不会导出其他符号,例如 MFC 专用实现函数或类。 所有导出都是按序号执行的,而无需在居民或非常驻名称表中使用字符串名称。
使用 class
__declspec(dllexport)
可能是生成较小 DLL 的可行替代方法,但在大型 DLL(如 MFC)中,默认导出机制具有效率和容量限制。
这一切都意味着,我们可以在版本中打包大量的功能,该版本 MFCxx.DLL
只有大约 800 KB,而不会影响大量执行或加载速度。
MFCxx.DLL
如果未使用此方法,则大小为 100 KB。 利用此方法,可以在 DEF 文件末尾添加其他入口点。 它允许简单的版本控制,而不会损害按序号导出的速度和大小效率。 MFC 类库中的主要版本修订将更改库名称。 也就是说, MFC30.DLL
是包含 MFC 类库版本 3.0 的可再发行 DLL。 例如,在假设的 MFC 3.1 中,此 DLL 的升级将改为命名 MFC31.DLL
。 同样,如果修改 MFC 源代码以生成 MFC DLL 的自定义版本,请使用其他名称(最好是名称中没有“MFC”)。