转自:http://www.a3gs.com/BookViews.asp?InfoID=2841&classID=819&InfoType=0
描述:
本文讲述了MFC扩展动态链接库相关原理及使用方法。
技术实现:
上面的两节中我们分别讲述了VC6中的非MFC动态链接库与MFC规则动态链接库;在这一节中我们将要讲述的是MFC扩展动态链接库。
1 MFC扩展动动态链接库
1.1 概述
MFC扩展DLL与MFC规则DLL的相同点在于在两种DLL的内部都可以使用MFC类库,其不同点在于MFC扩展DLL与应用程序的接口可以是MFC的。MFC扩展DLL的含义在于它是MFC的扩展,其主要功能是实现从现有MFC库类中派生出可重用的类。MFC扩展DLL使用MFC 动态链接库版本,因此只有用共享MFC 版本生成的MFC 可执行文件(应用程序或规则DLL)才能使用MFC扩展DLL。
从前文可知,MFC规则DLL被MFC向导自动添加了一个CWinApp的对象,而MFC扩展DLL则不包含该对象,它只是被自动添加了DllMain 函数。对于MFC扩展DLL,开发人员必须在DLL的DllMain函数中添加初始化和结束代码。
从下表我们可以看出三种DLL对DllMain入口函数的不同处理方式:
DLL类型 | 入口函数 |
非MFC DLL | 编程者提供DllMain函数 |
MFC规则 DLL | CWinApp对象的InitInstance 和 ExitInstance |
MFC扩展 DLL | MFC DLL向导生成DllMain 函数 |
对于MFC扩展DLL,系统会自动在工程中添加如下表所示的宏,这些宏为DLL和应用程序的编写提供了方便。像AFX_EXT_CLASS、AFX_EXT_API、AFX_EXT_DATA这样的宏,在DLL和应用程序中将具有不同的定义,这取决于_AFXEXT宏是否被定义。这使得在DLL和应用程序中,使用统一的一个宏就可以表示出输出和输入的不同意思。在DLL中,表示输出(因为_AFXEXT被定义,通常是在编译器的标识参数中指定/D_AFXEXT);在应用程序中,则表示输入(_AFXEXT没有定义)。
宏 | 定义 |
AFX_CLASS_IMPORT | __declspec(dllexport) |
AFX_API_IMPORT | __declspec(dllexport) |
AFX_DATA_IMPORT | __declspec(dllexport) |
AFX_CLASS_EXPORT | __declspec(dllexport) |
AFX_API_EXPORT | __declspec(dllexport) |
AFX_DATA_EXPORT | __declspec(dllexport) |
AFX_EXT_CLASS | #ifdef _AFXEXT AFX_CLASS_EXPORT #else AFX_CLASS_IMPORT |
AFX_EXT_API | #ifdef _AFXEXT AFX_API_EXPORT #else AFX_API_IMPORT |
AFX_EXT_DATA | #ifdef _AFXEXT AFX_DATA_EXPORT #else AFX_DATA_IMPORT |
1.2 MFC扩展DLL导出一个简单的MFC派生类
通过上面的介绍我们对VC6的MFC扩展DLL有了一定的了解,现在我们就利用MFC扩展DLL来导出一个简单的MFC派生类;运行VC6,选择FileàNew…,在弹出的New对话框中选择Project选项卡,在工程类型列表中选择MFC AppWizard(dll), 在Project name输入框中输入项目名称(如本例取SMFCExDll),在Location输入框中输入创建工程的位置,点击OK按钮,弹出MFC AppWizard Step 1 of 1对话框。
在所弹出的MFC AppWizard Step 1 of 1对话框中的What kind of DLL would you like to create?中选择MFC Extension DLL(use shared MFC DLL),别的都保持默认值,点击Finish按钮,在弹出的New Project Informaction对话框中点击OK。
现在Class Wizard就为我们生成了MFC扩展动态链接库的框架了,对与框架里的东西我想没什么好讲了吧看一下代码就一切都懂了;不过在这里还要讲一讲DllMain中的一此东西,中要有以下几点:
(1) DllMain完成MFC扩展DLL的初始化和终止处理;
(2) 初始化期间所创建的 CDynLinkLibrary 对象使MFC扩展 DLL 可以将 DLL中的CRuntimeClass 对象或资源导出到应用程序;
(3)AfxInitExtensionModule函数捕获模块的CRuntimeClass 结构和在创建 CDynLinkLibrary 对象时使用的对象工厂(COleObjectFactory 对象);
(4) AfxTermExtensionModule函数使 MFC 得以在每个进程与扩展 DLL 分离时(进程退出或使用AfxFreeLibrary卸载DLL时)清除扩展 DLL;
还是以导出对话框类为例子,现在我们在工程中添加一个CMytestDlg(它继承了Cdialog)。选择InsertàResource,在弹出的Inert Resource对话框中的Resource types中选择Dialog,然后点击New;OK一个对话框资源已经出现在我们的工程中了;现在你想把对话框改成什么样子由你自己高兴啦,反正这和平常的对话框程序开发没什么两样。
现在到了该为我们的对话框资源添加类的时候了;选择InsertàNew Class…,在弹出的New Class对话框中的Class type下拉框中选择MFC Class,在Class informaction中的Name输入框中输入所要创建的类名(本例子中取CMyTestDlg),在Class informaction中的Base class下拉框中选择CDialog,在Class informaction中的Dialog ID中选择我们刚才所添加的对话框资源ID(本例中IDD_DIALOG1),别的都保持默认,点击OK。
OK,现在一个测试用的简单对话框类已经生成,接下来你爱让这个对话框做些什么东西或者有什么表现那就由你的高兴啦(这和平常的对话框程序没什么区别)。
不过到现在为此上面的CMyTestDlg只能在本工程中使用还不能在别的应用工程中使用,因为我还没有把它导出来;导出这个对话框非常的简单,找到CmyTestDlg类的定义处(由于创建这个类时我们别的都用默认值,所以CMyTestDlg在MyTestDlg.h中定义),找到class CMyTestDlg : public Cdialog这一行,在class后面加上AFX_EXT_CLASS(这个宏请参见上面的说明)既可(既把class CMyTestDlg : public Cdialog这一行改成class AFX_EXT_CLASS CMyTestDlg : public Cdialog);然后按F7编译工程,OK现在我们就可以在应用工程中使用我们刚才所创建的CMyTestDlg类了。
1.3 MFC扩展动态链接库的加载
1.3.1 隐式加载
隐试加载MFC扩展动态链接库的方法非常的简单,和隐试加用非MFC动态链接库与此隐试加载MFC规则动态链接库差不多;以下代码是用隐试方法加载我们上面的例子工程(SMFCExDll):
#include "../MyTestDlg.h"//CMyTestDlg类的头文件
#pragma comment(lib,"..//Debug//SMFCExDll.lib")//引入SMFCExDll.lib
//而“调用DLL”按钮的单击事件的消息处理函数为:
void CLoadExtDllDlg::OnButton1()
{
CMyTestDlg dlg;
dlg.DoModal();
}
注意:如果有出现如下之类的提示error C2065: 'IDD_DIALOG1' : undeclared identifier,而这些提示是出现在我们动态库的头文件中,则把动态库中的相应头文件中的相应代码注释了更重新编译既可(如本例子中把MyTestDlg.h文件中的enum { IDD = IDD_DIALOG1 };注释)。
为提供给用户隐式加载(MFC扩展DLL一般使用隐式加载,具体原因见下节),MFC扩展DLL需要提供三个文件:
(1)描述DLL中扩展类的头文件;
(2)与动态链接库对应的.LIB文件;
(3)动态链接库.DLL文件本身。
有了这三个文件,应用程序的开发者才可充分利用MFC扩展DLL。
1.3.2 显示加载
显示加载MFC扩展DLL应使用MFC全局函数AfxLoadLibrary而不是WIN32 API中的LoadLibrary。AfxLoadLibrary 最终也调用了 LoadLibrary这个API,但是在调用之前进行了线程同步的处理。
AfxLoadLibrary 的函数原型与 LoadLibrary完全相同,为:
HINSTANCE AFXAPI AfxLoadLibrary( LPCTSTR lpszModuleName );
与之相对应的是,MFC 应用程序应使用AfxFreeLibrary 而非FreeLibrary 卸载MFC扩展DLL。AfxFreeLibrary的函数原型也与 FreeLibrary完全相同,为:
BOOL AFXAPI AfxFreeLibrary( HINSTANCE hInstLib );
如果我们把上例中的“调用DLL”按钮单击事件的消息处理函数改为:
void CLoadExtDllDlg::OnButton1()
{
HINSTANCE hDll = AfxLoadLibrary( "..//Debug//SMFCExDll.dll" );
if(NULL == hDll)
{
AfxMessageBox( "MFC扩展DLL动态加载失败" );
return;
}
CMyTestDlg dlg;
dlg.DoModal();
AfxFreeLibrary(hDll);
}
则工程会出现link错误:
TestDlg.obj : error LNK2001: unresolved external symbol "__declspec(dllimport) public: virtual __thiscall CMyTestDlg::~CMyTestDlg(void)" (__imp_??1CMyTestDlg@@UAE@XZ)
TestDlg.obj : error LNK2001: unresolved external symbol "__declspec(dllimport) public: __thiscall CMyTestDlg::CMyTestDlg(class CWnd *)" (__imp_??0CMyTestDlg@@QAE@PAVCWnd@@@Z)
Debug/Test.exe : fatal error LNK1120: 2 unresolved externals
提示CMyTestDlg的构造函数和析构函数均无法找到!是的,对于派生MFC类的MFC扩展DLL,当我们要在应用程序中使用DLL中定义的派生类时,我们不宜使用动态加载DLL的方法。至于怎么解决这个问题本人暂时还没找到,如果有那位朋友已经解决了这个问题还望不吝赐教。
1.3.3 MFC扩展DLL加载MFC扩展DLL
我们可以在MFC扩展DLL中再次使用MFC扩展DLL,但是,由于在两个DLL中对于AFX_EXT_CLASS、AFX_EXT_API、AFX_EXT_DATA宏的定义都是输出,这会导致调用的时候出现问题。
我们将会在调用MFC扩展DLL的DLL中看到link错误:
error LNK2001: unresolved external symbol ….......
因此,在调用MFC扩展DLL的MFC扩展DLL中,在包含被调用DLL的头文件之前,需要临时重新定义AFX_EXT_CLASS的值。下面的例子显示了如何实现:
//临时改变宏的含义“输出”为“输入”
#undef AFX_EXT_CLASS
#undef AFX_EXT_API
#undef AFX_EXT_DATA
#define AFX_EXT_CLASS AFX_CLASS_IMPORT
#define AFX_EXT_API AFX_API_IMPORT
#define AFX_EXT_DATA AFX_DATA_IMPORT
//包含被调用MFC扩展DLL的头文件
#include "CalledDLL.h"
//恢复宏的含义为输出
#undef AFX_EXT_CLASS
#undef AFX_EXT_API
#undef AFX_EXT_DATA
#define AFX_EXT_CLASS AFX_CLASS_EXPORT
#define AFX_EXT_API AFX_API_EXPORT
#define AFX_EXT_DATA AFX_DATA_EXPORT
1.4 MFC扩展DLL导出函数和变量
MFC扩展DLL导出函数和变量的方法也十分简单,下面我们给出一个简单的例子。
我们在上面的例子工程(SMFCExDll)中添加gobal.h和global.cpp两个文件:
//global.h:MFC扩展DLL导出变量和函数的声明
extern "C"
{
int AFX_EXT_DATA g_nVal; //导出变量
int AFX_EXT_API add( int x, int y ); //导出函数
}
//global.cpp:MFC扩展DLL导出变量和函数定义
#include "StdAfx.h"
#include "global.h"
extern "C" int g_nVal=100;
int add(int x,int y)
{
int ntotal = x + y;
return ntotal;
}
编写一个简单的测试程序来测试我们刚才所添加的:
就在上面的测试工程中再加一个按钮,这个按钮的事件处理函数如下:
void CTestDlg::OnButton2()
{
char ch[10];
sprintf(ch,"%d",g_nVal);
AfxMessageBox(ch);
sprintf(ch,"%d",add(100,123));
AfxMessageBox(ch);
}
记得必需在文件的开头加一句:#include “../gobal.h”或者直接在#pragma comment(lib, "..//Debug//SMFCExDllTest.lib")后面加上:
extern "C"
{
int AFX_EXT_DATA g_nVal; //导出变量
int AFX_EXT_API add( int x, int y ); //导出函数
}
另外,在Visual C++下建立MFC扩展DLL时,MFC DLL向导会自动生成.def文件。因此,对于函数和变量,我们除了可以利用AFX_EXT_DATA、AFX_EXT_API宏导出以外,在.def文件中定义导出也是一个很好的办法。与之相比,在.def文件中导出类却较麻烦。通常需要从工程生成的.map文件中获得类的所有成员函数被C++编译器更改过的标识符,并且在.def文件中导出这些“奇怪”的标识符。因此,MFC扩展DLL通常以AFX_EXT_CLASS宏直接声明导出类。
1.5 后话
上面我们以例子形式讲述了MFC扩展动态链接库的开发使用方法,以及这之间应该注意的一些问题;当然如果就象上面的例子那样调用MFC扩展动态链接库只为了弹出一个链接库只的对话框或者调用动态库中的一些全局变量及函数之类,那么就没有必要用到MFC扩展动态链接库了,因为这些事情MFC规则动态链接库已经可以做到。
我们使用MFC扩展动态链接库的最大的目的在于它和应用程序之间的接口支持MFC;所以我们用MFC扩展动态链接库主要是为了开发一个VC的控件或子窗体之类的东西;比如我们可以利用MFC扩展动态链接库开发CStatic的扩展类,使之具备象IE只的超链接一样的表现,或使之可以方便的改变字体或前景颜色与背景颜色之类的扩展CStatic类;这样就可以更为方便的为我们的应用程序开发更为美观的应用程序界面。