MFC--Extension DLL(扩展DLL)

来源:互联网 发布:淘宝客会给新店推广吗 编辑:程序博客网 时间:2024/04/30 03:17

本文阐述的动态链接库的一种,mfc 扩展动态链接库--MFC Extension DLL.最后以一个程序的启动画面来用实例说明一下这个dll的使用

MFC扩展dll主要是为了实现可重用类,也就是MFC标准类库进行继承,然后通过DLL进行重用,一次开发,多次使用。扩展dll使用MFC的动态连接的库版本生成的。只有使用动态连接库的可执行程序或是regular dll(规则动态链接库)才可以使用扩展dll。我们知道在创建mfc程序的时候,可以有选择如下选择:

使用共享版的DLL的可执行程序才可以使用扩dll。在程序和扩展dll之间,可以传递MFC对象,被传递的对象存在于创建它们的模块中。当使用共享版的MFC dll时候函数被正确的输出,因此你可以在程序以及扩展dll之间自由的传递MFC对象。

1初始化扩展dll

由于扩展dll没有继承于cwinapp的类的对象(regular dll中有),因此,你应该讲初始化代码和终止代码放到有MFC  dll向导生成的DllMain函数中,如下,就是由MFC dll向导生成的DllMain:

#include "stdafx.h"
#include <afxwin.h>
#include <afxdllx.h>


#ifdef _DEBUG
#define new DEBUG_NEW
#endif


static AFX_EXTENSION_MODULE mfcdllDLL = { NULL, NULL };
extern HINSTANCE hmodule;


extern "C" int APIENTRY
DllMain(HINSTANCE hInstance, DWORD dwReason, LPVOID lpReserved)
{
// 如果使用 lpReserved,请将此移除
UNREFERENCED_PARAMETER(lpReserved);


if (dwReason == DLL_PROCESS_ATTACH)
{
TRACE0("mfcdll.DLL 正在初始化!\n");

// 扩展 DLL 一次性初始化
if (!AfxInitExtensionModule(mfcdllDLL, hInstance))
return 0;


// 将此 DLL 插入到资源链中
// 注意: 如果此扩展 DLL 由
//  MFC 规则 DLL (如 ActiveX 控件)隐式链接到,
//  而不是由 MFC 应用程序链接到,则需要
//  将此行从 DllMain 中移除并将其放置在一个
//  从此扩展 DLL 导出的单独的函数中。使用此扩展 DLL 的
//  规则 DLL 然后应显式
//  调用该函数以初始化此扩展 DLL。否则,
//  CDynLinkLibrary 对象不会附加到
//  规则 DLL 的资源链,并将导致严重的
//  问题。


new CDynLinkLibrary(mfcdllDLL);
hmodule=hInstance;
}
else if (dwReason == DLL_PROCESS_DETACH)
{
TRACE0("mfcdll.DLL 正在终止!\n");


// 在调用析构函数之前终止该库
AfxTermExtensionModule(mfcdllDLL);
}
return 1;   // 确定
}

创建一个新的CDynLinkLibrary对象是为了让扩展dll将CRuntimeClass 对象或是资源输出到程序。

如果是你在regular dll中使用扩展dll,你必须输出一个初始化函数,由这个函数创建CDynLinkLibrary对象,也就是说你要输出一个c函数,在这个函数中创建这个对象。正确的调用地方就是regular dll中继承于CWinApp类的对象的InitInstance函数中,这个函数类似于DllMain,在regular dll中的初始化代码就在这个函数中。

AfxInitExtensionModule 的调用可以捕捉到模块运行时类(也就是CRuntimeClass结构体)和他的对象的工厂(COleObjectFactory对象)以便于在创建CDynLinkLibrary对象时使用。你应该检查AfxInitExtensionModule的返回值,如果是0,则从DllMain返回0.

如果扩展dll是直接连接到可执行程序(也就是使用AfxLoadLibrary),那么应该添加DLL_PROCESS_DETACH处理,调用AfxTermExtensionModule函数,这个函数可以让MFC在扩展dll从执行程序卸载的时候进行清理工作。如果是间接连接到可执行程序,也就是加载时连接的,就可以不用调用这个函数。

使用直接连接到可执行程序的扩展dll,当释放dll的时候(从可执行程序卸载),必须调用AfxTermExtensionModule ,加载和卸载dll的时候,应该使用AfxLoadLibrary 和 AfxFreeLibrary,而不应该使用win32函数LoadLibrary 和 FreeLibrary,这可以保证不会中断全局MFC状态。

2扩展dll

MFC扩展dll有以下特性和要求:

a使用扩展dll的可执行程序必须是MFC程序,且编译的时候定义了 _AFXDLL 。这个定义是在项目属性的C/C++的预处理器--预处理定义中设置的。

b扩展dll可以由使用了动态链接到MFC的regular dll使用(使用了MFC共享版dll)

c扩展dll编译时应该使用_AFXEXT 定义,这样可以强制_AFXDLL也被定义,确保从MFC的头文件中获取正确的声明。也可以确保AFX_EXT_CLASS被定义为__declspec(dllexport)。这个宏就可以声明这个类及对象可以从dll中输出。

d扩展dll中不能实例化一个从CWinApp继承的类。而应该使用使用这个扩展dll的程序中提供这个对象。

e扩展dll应该提供DllMain函数去执行必要的初始化。

如果你正在使用模块定义文件输出,也就是使用.def文件来输出dll中的类和函数,那么在头文件的开始和结尾放置一下代码:

#undef AFX_DATA
#define AFX_DATA AFX_EXT_DATA
// <body of your header file>
#undef AFX_DATA
#define AFX_DATA

这四行代码可以你的代码正确编译,省略了可能引起你的dll编译或是链接不正确。

3内存管理

MFCx0.dll(MFC dll)和所有的扩展dll,加载到程序都使用了相同的内存分配器,资源加载和别的MFC全局状态,只要它们在同一个程序中。这一点和non-MFC dll和regular dll不同。

一个扩展dll分配了内存,那么这个内存可以与任何别的程序分配的对象混用。如果程序动态连接到MFC失败,那么操作系统保护性也会维护别的共享这个dll的程序的完整性。

4共享资源和类

资源的输出是通过资源列表来完成的,每个程序都包含了各个CDynLinkLibrary对象的链接列表,这也是为什么每个扩展dll初始化的时候,要创建一个CDynLinkLibrary对象。当寻找资源的时候,首先寻找当前模块(AfxGetResourceHandle),然后继续资源列表往下寻找。使用AfxFindResourceHandle 函数可以直接找到与资源匹配的模块的实例句柄。

///对于动态链接库的基本知识,如链接方式,函数输出,相关的宏等等,可以自己查阅msdn做进一步了解。

下面根据一个程序启动画面的例子来实际看看应该如何使用扩展dll

其实程序的启动画面,简单来说,其实就是在主程序界面显示之前实现的的一个窗口,在这个窗口中显示一个位图,文字,甚至动画来向用户告知一些程序的基本信息,如果主程序的加载文件众多,那么就可以使用一个启动窗口来显示加载的情况,如ps就是这样的程序。但是启动画面窗口也有一些特定,如一般不带边框,在任务栏也不会显示这个窗口的程序图标。对于不带边框,在我的博文中有相关的文章,在任务栏不显示程序图标,是由窗口样式决定。而这一切,其实在基于对话框的程序中,已经有满足这两个条件的窗口属性选项。在资源编辑器中,对话框属性中,Border属性设置为none,就是无边框的意思,就可以了。



进入之后,就已经了DllMain函数,和一些别的框架代码,我们不做修改。我们在资源编辑器中添加一个对话框,然后将样式设置好,将多余的按钮去掉。然后双击这个对话框,就可以显示一个添加类的向导出来,我设置CSpice类名,然后我们将这个类改成非模式对话框的关闭方式,这个在我的博文,MFC--非模式对话框中有说明如何修改。但是这里和那里有点不同,那就是虚函数PostNcDestroy,如果我们创建的对象是一个指针,那么就使用这个虚函数,在当中添加delete this,否则,我们不使用。这里我没有指针,所以不做处理。

然后我在资源中添加图片资源,显示的时候使用。加入之后,在jpg下面有一个资源id为IDR_JPG1的资源,就是我的图片,添加之后,在类CSpic中添加一个成员函数ReadToDraw准备要绘制的图片,这个函数的实现如下:

HRSRC HJPG= :: FindResource(hmodule,MAKEINTRESOURCE(IDR_JPG1),L"JPG");///查找资源
if(HJPG==NULL)
{
return FALSE;
}
HGLOBAL HJPGR=::LoadResource(hmodule,HJPG);///导入资源
if(HJPGR==NULL)
{
return FALSE;
}
DWORD len=::SizeofResource(hmodule,HJPG);///获取资源大小
BYTE *byte=(BYTE*)LockResource(HJPGR);///获取资源内存指针的初始位置
if(byte==NULL)
{
return FALSE;
}
HGLOBAL hmem=GlobalAlloc(GMEM_MOVEABLE,len);///在内存中分配一块内存,保存图片资源到内存中
if(hmem==NULL)
{
return FALSE;
}
BYTE *Ibyte=(BYTE*)GlobalLock(hmem);//获取分配内存的内存指针的初始位置
CopyMemory(Ibyte,byte,len);///将图片数据复制到我分配的内存中


HRESULT hr=CreateStreamOnHGlobal(hmem,TRUE,&picStream);///用我分配的内存初始化一个IStream com对象
if(hr!=S_OK)
{
GlobalUnlock(hmem);///关闭分配内存的指针
return FALSE;
}
//////////////////
GlobalUnlock(hmem);
return TRUE;

当图片资源通过一系列函数,如FindResource,LoadResource等等加载到内存,并获取内存指针之后,为什么我还要自己分配呢,因为这个内存是不能使用像我们自己用GlobalAlloc分配的内存的操作,具体信息,可以参考msdn,但是我要用图片数据初始化一个IStream对象,因此我自己分配了一个内存,最终初始化了一个IStream对象,这个对象是一个com对象,我们可以用文件句柄来理解它的角色,但是它不能将它作为一个文件句柄来使用,具体信息,参考msdn,在这里,我之所以初始化它,是因为我将在gdi+绘图中会使用到它。

完成这里的操作之后,我在OnPaint中进行绘制:

void CSPic::OnPaint()
{
CPaintDC dc(this); // device context for painting
// TODO: 在此处添加消息处理程序代码
// 不为绘图消息调用 CDialogEx::OnPaint()
if(picStream)
{
       Graphics g(dc.m_hDC );
       Image i(picStream,FALSE);
       g.DrawImage(&i,0,0);
}
}

对于Gdi+的使用,这里不做详细说明,可以参阅msdn,另外我还在响应擦除背景的消息响应中,也进行了绘制,因为,我这个窗口是动画显示的,如果不在这个窗口消息响应中绘制,那么在动画窗口的时候,客户区的信息就没有了,就是灰色,如果我在这里添加,那么在动画显示和隐藏窗口的时候,在窗口上也有图片。

BOOL CSPic::OnEraseBkgnd(CDC* pDC)
 {
// TODO: 在此添加消息处理程序代码和/或调用默认值
if(picStream)
{
       Graphics g(pDC->m_hDC );
       Image i(picStream,FALSE);
       g.DrawImage(&i,0,0);
       return 0;
}
else
{
return CDialogEx::OnEraseBkgnd(pDC);
}
// 
 }

有关这个消息的时候,在我的博文“MFC--序幕”中有说明,可以参考。

另外,Gdi+是一个平台,我们在使用之前,要进行初始化,但是我们查看msdn就可以看到,它的初始化函数不能再DllMain中使用,因此我们必须要通过输出C函数,在我们创建启动窗口之前,调用这个C函数,初始化Gdi+。在使用结束之后,在调用相应的函数卸载Gdi+,如下:

__declspec(dllexport) BOOL WINAPI InitGdiPlusFunction();
__declspec(dllexport) VOID WINAPI UnInitGdiPlusFunction();

这个是声明,我们放到头文件中。以便我们在使用的时候,可以通过函数名称调用这两个函数,对于这两个函数的具体实现,在cpp文件中,这里不贴出来,在文章的最后,我会写出这个项目的下载位置。

接着我添加一个测试程序,添加方法在我的博文“调试动态链接库”中有说明,添加之后如下:

并将testdll设为启动项,并在testdll的属性中设置引用mfcdll,如下:


这个时候,我们将mfcdll下的Spic.h文件复制到testdll的目录下,因为这里面有类和函数的声明。

和平常一样,我们在testdll的C***Dlg头文件中添加对Spic.头文件的包含,然后在testdll的OnInitDialog中添加如下代码:

BOOL bresult=InitGdiPlusFunction();///初始化gdi+
if(bresult)
{
startuppic.CreateMyDialog();//自定义函数,创建和显示窗口
Sleep(5000);    ///启动窗口留在屏幕上的时间
startuppic.Close();///关闭窗口
Sleep(1000);     ///保证启动窗口已经关闭
UnInitGdiPlusFunction();////卸载gdi+
}
else
{
MessageBox(L"启动画面加载失败",L"testdll");
}

为什么我有一个Sleep(1000)呢,其实是为了保证第一个窗口关闭过程结束,因为我们可以看见这个窗口对象是一个局部对象,而且是栈中对象,如果再关闭步骤没有执行完毕就销毁对象,必然会造成一些问题,本来应该用同步对象的,但是这里为了简便,没有使用,而是停留了1秒。

下面就是效果图:

启动画面:


测试窗口:


至此就结束了。

本文程序代码:http://download.csdn.net/detail/xinzhiyounizhiyouni/6615603  

vs2010 win7平台。

联系邮箱:andyhl1987@126.com 欢迎交流!!