9.3创建动态连接库
在一些情况下,必须使用动态连接库:
1.多个应用程序共享代码和数据:比如Office软件的各个组成部分有相似的外观和功能,这就是通过共享动态连接库实现的。
2.在钩子程序过滤系统消息时必须使用动态连接库
3.设备驱动程序必须是动态连接库
4.如果要在对话框编辑器中使用自己定义的控件,也必须使用动态连接库
5.动态连接库以一种自然的方式将一个大的应用程序划分为几个小的模块,有利于小组内部成员的分工与合作。而且,各个模块可以独立升级。如果小组中的一个成员开发了一组实用例程,他就可以把这些例程放在一个动态连接库中,让小组的其他成员使用。
6.为了实现应用程序的国际化,往往需要使用动态连接库。使用动态连接库可以将针对某一国家、语言的信息存放在其中。对于不同的版本,使用不同的动态连接库。在使用AppWizard生成应用程序时,我们可以指定资源文件使用的语言,这就是通过提供不同的动态连接库实现的。
MFC支持两类动态连接库的创建:
用户动态连接库
MFC扩展类库。
9.3.1用户动态连接库(_USRDLL)
用户动态连接库一般使用C语言接口。要创建一个动态连接库,选择File->New菜单,弹出New对话框。在Projects标签页下,选择“Win32 Dynamic-Link Library”。Visual C++就会创建动态连接库所需的工程文件和MAK文件。
然后把下面两个文件加入到工程中(Project-Add to Project-Files菜单)。
文件1:mymaths.cpp
////////////////////////////
//mymaths.cpp
//
//a maths API DLL.
//
///////////////////////////
#include<windows.h>
//Declare the DLL functions prototypes
int Summary(int);
int Factorial(int);
//////////////////////////
//DllEntryPoint():The entry point of the DLL
//
/////////////////////////
BOOL WINAPI DLLEntryPoint(HINSTANCE hDLL,DWORD dwReason,
LPVOID Reserved)
{
switch(dwReason)
{
case DLL_PROCESS_ATTACH:
{
//一些初始化代码
break;
}
case DLL_PROCESS_DETACH:
{
//一些用于清理的代码
break;
}
}
return TRUE;
}
int Summary(int n)
{
int sum=0;
int i;
for(i=1;i<=n;i++)
{
sum+=i;
}
return sum;
}
int Factorial(int n)
{
int Fact=1;
int i;
for(i=1;i<=n;i++)
{
Fact=Fact*i;
}
return Fact;
}
文件2:mymaths.def
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;Mymaths.DEF
;
;The DEF file for the Mymaths.DLL DLL.
;
LIBRARY mymaths
CODE PRELOAD MOVEABLE DISCARDABLE
DATA PRELOAD SINGLE
EXPORTS
;The names of the DLL functions
Summary
Factorial
在文件mymaths.cpp开头,声明了动态连接库所包含的两个函数:Summary和Factorial。接着是DllEntryPoint()函数的定义。DllEntryPoint()顾名思义是动态连接库的入口,应用程序通过该入口访问动态连接库提供的服务。DllEntryPoint()主体是一个switch/case语句:
switch(dwReason)
{
case DLL_PROCESS_ATTACH:
{
//一些初始化代码
break;
}
case DLL_PROCESS_DETACH:
{
//一些用于清理的代码
break;
}
}
其中,在case DLL_PROCESS_ATTACH分支可加入动态连接库执行时的一些初始化代码。在case DLL_PROCESS_DETACH加入动态连接库被卸载时的一些清理代码,比如释放动态连接库运行时申请的内存等。
在DllEntryPoint()函数后,是两个函数Summary和Factorial函数的定义。它们的定义与前面的静态库完全相同。在这里用户可以放入任何函数。
另外,我们还需要一个mymaths.def文件。这个文件记录了可被外部应用程序使用的DLL库函数名字。这些名字信息和对应的函数位置的信息将被编译进动态连接库文件中,然后应用程序根据函数名字和函数位置对照表来找到对应的函数。
按F7编译工程,Visual C++就在mymaths\debug目录下生成一个mymaths.dll动态连接库文件。
现在,我们来使用刚才生成的动态连接库。我们并不重新生成一个程序,而是修改前面测试静态库时的test程序。首先,把mymaths\debug目录下的mymaths.dll拷贝到test\debug目录下。test程序运行时,会在该目录下搜索动态连接库文件。然后修改testdlg.h,在其中加入一个函数LoadDLL()的声明,见清单9.4。LoadDLL用于载入动态连接库。
清单9.4 修改后的对话框头文件
class CTestDlg : public CDialog
{
// Construction
public:
CTestDlg(CWnd* pParent = NULL); // standard constructor
protected:
void LoadDLL();
//......
}
然后修改testdlg.cpp,修改后如清单9.5。
清单95. TestDlg.cpp文件
// TestDlg.cpp : implementation file
//
#include "stdafx.h"
#include "Test.h"
#include "TestDlg.h"
//#include "mymath.h" //注释掉mymath.h头文件
#ifdef _DEBUG
#define new DEBUG_NEW
#undef THIS_FILE
static char THIS_FILE[] = __FILE__;
#endif
//The instance of the Mymaths.DLL library
HINSTANCE ghMathsDLL=NULL;
//declare the Summary() function from the Mymaths.DLL libray.
typedef int (*SUMMARY)(int);
SUMMARY Summary;
//declare the Factorial() function from
//the Mymaths.DLL library.
typedef int (*FACTORIAL)(int);
FACTORIAL Factorial;
/////////////////////////////////////////////////////////////////////////////
// CAboutDlg dialog used for App About
class CAboutDlg : public CDialog
{
//...
};
//CAboutDlg的一些成员函数定义
//CTestDlg的一些成员函数定义
void CTestDlg::OnSum()
{
// TODO: Add your control notification handler code here
LoadDLL();
int nSum=Summary(10);
CString sResult;
sResult.Format("Sum(10)=%d",nSum);
AfxMessageBox(sResult);
}
void CTestDlg::OnFactorial()
{
// TODO: Add your control notification handler code here
LoadDLL();
int nFact=Factorial(10);
CString sResult;
sResult.Format("10!=%d",nFact);
AfxMessageBox(sResult);
}
void CTestDlg::LoadDLL()
{
//如果DLL已经载入,则返回
if(ghMathsDLL!=NULL)
{
return;
}
//载入Mymaths.DLL文件.
ghMathsDLL=LoadLibrary("mymaths.DLL");
//如果载入DLL失败,提示用户
if(ghMathsDLL==NULL)
{
AfxMessageBox("Cannot load DLL file!");
}
//获得DLL中Summary函数的地址
Summary=(SUMMARY)GetProcAddress(ghMathsDLL,"Summary");
//获得DLL中Factorial函数的地址
Factorial=(FACTORIAL)GetProcAddress(ghMathsDLL,"Factorial");
}
在testdlg.cpp文件开头,加入:
//The instance of the Mymaths.DLL library
HINSTANCE ghMathsDLL=NULL;
//declare the Summary() function from the Mymaths.DLL libray.
typedef int (*SUMMARY)(int);
SUMMARY Summary;
//declare the Factorial() function from
//the Mymaths.DLL library.
typedef int (*FACTORIAL)(int);
FACTORIAL Factorial;
首先加入一个ghMathsDLL的全局变量,它是动态连接库载入后的句柄(同应用程序一样,每个动态连接库载入都会有一个句柄和它相对应)。应用程序通过句柄访问库中的函数。然后加入Summary和Factorial函数指针的类型定义。
在LoadDLL()函数定义中,检查动态连接库句柄是否为空;若为空,则用LoadLibrary载入该动态连接库。然后用GetProcAddress取得Summary和Factorial函数地址。
在OnFactorial和OnSummary函数开头,调用LoadDLL(),载入动态连接库。现在编译运行程序,按Factorial按钮测试一下程序。
应用程序是如何查找DLL文件的
应用程序test按以下顺序查找动态连接库文件:
- 当前目录下(因此要将动态连接库拷贝至DEBUG目录下,因为可执行文件在该目录下)
- Windows目录
- Windows系统目录
- PATH环境变量中设置的目录
- 列入映射网络的目录表中的目录
调用动态连接库中的函数的方法
有两种方法可以调用动态连接库中的函数:
1.通过引入库:
利用Visual C++提供的IMPLIB工具为动态连接库生成引入库,为引入库设计一个头文件:
#ifndef _MYMATH_H
#define _MYMATH_H
extern “C”
{
int Summary(int n);
int Factorial(int n);
}
#endif
将该头文件包含在使用动态连接库的源文件中,连接应用程序时会连接上该引入库。这样,应用程序就可以象使用静态连接库一样自由的使用动态连接库中的函数了。注意要把动态连接库拷贝到应用程序可执行文件所在的目录(\TEST\DEBUG)下。
这是一种常用的方法。实际上,应用程序就是通过这种方式访问Windows的API函数的。Windows为其内核动态连接库生成引入库并提供了头文件。应用程序在编译时将引入库的信息带入可执行文件中,在运行时通过引入库信息访问API函数。
2. 直接指定库和函数地址
这种方式适合于一些提供文件格式转换等服务的动态连接库。比如,一个程序带有多个动态连接库,分别用于访问JPG、BMP、GIF等多种图像文件格式,这些动态连接库提供了相同的库函数接口。此时,无法使用引入库方式指定库函数。可以采用下面的方法来解决这个问题。
HANDLE hLibrary;
FARPROC lpFunc;
int nFormat;
if(nFormat==JPEG)//如果是JPEG格式,装入JPEG动态连接库
{
hLibrary=LoadLibrary(“JPEG.DLL”);
}
else//是GIF格式
hLibrary= LoadLibrary(“GIF.DLL”);
if(hLibrary>=32)
{
lpFunc=GetProcAddress(hLibrary,”ReadImage”);
if(lpFunc!=(FARPROC)NULL)
(*lpFunc)((LPCTSTR)strFileName);
FreeLibrary(hLibrary);
}
LoadLibrary函数装入所需的动态连接库,并返回库的句柄。如果句柄小于32,则载入库失败,错误含义参见有关手册。GetProcAddress函数使用函数名字取得函数的地址。利用该函数地址,就可以访问动态连接库的函数了。
FreeLibrary通过检查动态连接库的引用计数器,判断是否还有别的程序在使用这个动态连接库。如果没有,就从内存中移去该动态连接库;如果有,将动态连接库的使用计数器减1。LoadLibrary则将引用计数加1。
在用户动态连接库中,也可以使用MFC类。这时,可以选择静态连接和动态连接两种方式使用MFC库。
9.3.2 MFC扩展类库(_AFXDLL)
除了创建具有C语言接口的用户动态连接库外,MFC还允许用户在动态连接库中创建MFC类的派生类,这些类作为MFC类的自然延伸出现,可以为其他MFC应用程序所使用,就象使用普通的MFC类一样。
创建扩展类库
要创建扩展类库,可以选择File->New菜单,在Projects类型中选择MFC AppWizard(dll)。弹出MFC AppWizard 1of 1对话框,从中选择MFC Extension DLL(using shared MFC DLL)。AppWizard就会生成Extension DLL所需的框架。
这里不再创建动态连接库,而是用Visual C++的例子DLLHUSK程序(在SAMPLES\MFC\ADVANCED \DLLHUSK目录下)说明扩展类库的创建和使用。
在DLLHUSK项目工作区中,包含三个工程:DLLHUSK,TESTDLL1,TESTDLL2。
TESTDLL1和TESTDLL2分别定义了几个扩展类:CTextDoc、CHelloView和CListOutputFrame,DLLHUSK是使用这些类的示例程序。
在CListOutputFrame声明中,要加入AFX_EXT_CLASS,表明它是一个MFC扩展类。
class AFX_EXT_CLASS CListOutputFrame:public CMDIChildWnd
{
...
}
在函数定义处,还要包含afxdllx.h头文件
// Initialization of MFC Extension DLL
#include "afxdllx.h" // standard MFC Extension DLL routines
类的成员函数使用与应用程序中类的使用大致相同。
在CListOutputFrame类定义文件中,还提供了一个C函数。它的函数声明在类头文件testdll2.h中:
// Initialize the DLL, register the classes etc
extern "C" AFX_EXT_API void WINAPI InitTestDLL2();
这个函数用于初始化动态连接库和注册类:
// Exported DLL initialization is run in context of running application
extern "C" void WINAPI InitTestDLL2()
{
// create a new CDynLinkLibrary for this app
new CDynLinkLibrary(extensionDLL);
// nothing more to do
}
另外,源文件中还需要提供一个DllMain函数:
DllMain(HINSTANCE hInstance, DWORD dwReason, LPVOID lpReserved)
这个函数作用与前面的DllEntryPoint类似。
扩展类库也需要一个DEF文件,这个文件包含了动态连接库中可用的函数信息。由于现在动态连接库包含的是类,因此在函数命名上与用户动态连接库有所不同。
EXPORTS
?AddString@CListOutputFrame@@QAAXPBD@Z
??_7CListOutputFrame@@6B@
??_GCListOutputFrame@@UAAPAXI@Z
?OnEditCut@CListOutputFrame@@IAAXXZ
?_messageEntries@CListOutputFrame@@0QBUAFX_MSGMAP_ENTRY@@B
??0CListOutputFrame@@QAA@XZ
??1CListOutputFrame@@UAA@XZ
?Clear@CListOutputFrame@@QAAXXZ
?OnEditClear@CListOutputFrame@@IAAXXZ
?OnEditCopy@CListOutputFrame@@IAAXXZ
InitTestDLL2
......
有关函数名扩展的技术参考Visual C++帮助文档。
使用扩展类动态连接库
要使用扩展类库,要将类库的头文件包含在工程中。然后在适当位置初始化类库,DLLHusk是在InitInstance中完成这一工作的。
BOOL CHuskApp::InitInstance()
{
//...
InitTestDLL1();
InitTestDLL2();
//...
}
然后就可以象使用普通MFC类一样使用扩展类库中定义的类了。
m_pListOut=new CListOutputFrame;
访问DLL中的资源
当应用程序使用资源时,它按以下顺序查找资源:首先查找应用程序本身,看有没有对应的资源;如果没有,查找MFC400.DLL(或MFC400D.DLL,它包含调试信息)。再查找应用程序所带的动态连接库中的资源。如果想在DLL中直接使用资源而不经过以上搜索顺序,可以使用AfxGetResouceHandle()和AfxSetResourceHandle()函数。
AfxGetResourceHandle()和AfxSetResouceHandle()函数分别用来保存旧的资源句柄和设置新的资源句柄。比如,要想直接从DLL中载入一个位图资源,可以这么调用:
CBitmap mybitmap;
HINSTANCE hInstOld=AfxGetResourceHandle()
AfxSetResouceHandler(extensionDLL.hModule);
if(!mybitmap.LoadBitmap(IDR_BITMAP));
{
//restore the old resouce chain and return error
AfxSetResouceHandle(hInstOld);
return FALSE;
}
AfxSetResouceHandle(hInstOld);
//use this bitmap...
return TRUE;
还可以使用FindResource()搜索资源表,寻找给定的资源。
HRSRC FindResource(
HMODULE hModule,
LPCTSTR lpName,
LPCTSTR lpType
);
FindResource带三个参数,第一个参数是模块句柄,第二个是要查找的资源名字,如“MYDIALOG”,第三个是资源类型,可参见Visual C++文档。如果查找成功,则返回该资源句柄。可以用LoadResouce以该句柄为参数装入资源。