一、DLL简介
它是Dynamic Link Library 的缩写形式,DLL 是一个包含可由多个程序同时使用的代码和数据的库,DLL不是可执行文件。动态链接提供了一种方法,使进程可以调用不属于其可执行代码的函数。函数的可执行代码位于一个 DLL 中,该 DLL 包含一个或多个已被编译、链接并与使用它们的进程分开存储的函数。DLL 还有助于共享数据和资源。多个应用程序可同时访问内存中单个DLL 副本的内容。DLL 是一个包含可由多个程序同时使用的代码和数据的库。例如,在 Windows 操作系统中,Comdlg32 DLL 执行与对话框有关的常见函数。因此,每个程序都可以使用该 DLL 中包含的功能来实现“打开”对话框。这有助于促进代码重用和内存的有效使用。
通过使用 DLL,程序可以实现模块化,由相对独立的组件组成。例如,一个计帐程序可以按模块来销售。可以在运行时将各个模块加载到主程序中(如果安装了相应模块)。因为模块是彼此独立的,所以程序的加载速度更快,而且模块只在相应的功能被请求时才加载。
此外,可以更为容易地将更新应用于各个模块,而不会影响该程序的其他部分。例如,您可能具有一个工资计算程序,而税率每年都会更改。当这些更改被隔离到 DLL 中以后,您无需重新生成或安装整个程序就可以应用更新。
与动态链接库相对应,还有一种称之为静态链接库的函数库,两者的主要区别在于使用方法上面。
静态连接库就是把(lib)文件中用到的函数代码直接链接进目标程序,程序运行的时候不再需要其它的库文件;动态链接就是把调用的函数所在文件模块(DLL)和调用函数在文件中的位置等信息链接进目标程序,程序运行的时候再从DLL中寻找相应函数代码,因此需要相应DLL文件的支持。
静态链接库与动态链接库都是共享代码的方式,如果采用静态链接库,则无论你愿不愿意,lib 中的指令都全部被直接包含在最终生成的 EXE 文件中了。但是若使用 DLL,该 DLL 不必被包含在最终 EXE 文件中,EXE 文件执行时可以“动态”地引用和卸载这个与 EXE 独立的 DLL 文件。静态链接库和动态链接库的另外一个区别在于静态链接库中不能再包含其他的动态链接库或者静态库,而在动态链接库中还可以再包含其他的动态或静态链接库。
“每一个lib文件就是若干函数(假设只有函数)的定义”
lib库有两种,一种是包含了函数所在DLL文件和文件中函数位置的信息,称为导出库;一种是包含函数代码本身,一般现有的DLL,用的是前一种库
lib有静态lib和动态lib之分。
静态lib将导出声明和实现都放在lib中。编译后所有代码都嵌入到宿主程序
动态lib相当于一个h文件,是对实现部分(.dll文件)的导出部分的声明。编译后只是将导出声明部分编译到宿主程序中,运行时候需要相应的dll文件支持
“通过#include包含这些函数声明的头文件后,我们的应用程序就可以使用lib文件中的函数”
以下省略一千多字的DLL优缺点分析。如果又兴趣,百度之。
按照DLL是否使用MFC,可以将DLL分为两种:普通的Win32的DLL和基于MFC的DLL。DLL文件一般具备以下三个文件:包含文件、导入库文件、实际代码文件,它们对应的扩展名分别是:.H、.LIB、.DLL。
下面看看例子,循序渐进
二、普通Win32 DLL开发
这个win32 DLL的例子只是实现一个简单的DLL,里面包含一个对整数数组进行比较并返回最大的数的函数。
(1) 新建工程项目,其他选择默认即可,按下ok按钮后直接点击向导中的Finish按钮
(2) 添加DLL的功能文件
由前面的DLL简介可知,当DLL开发完成并编译之后,会生成三个文件,即包含文件、导入库文件、实际代码文件,它们对应的扩展名分别是:.H、.LIB、.DLL。
所以我们在开发的第一步是产生头文件,头文件中包含导出内容,另外两个文件在导出时会自动生成。在项目中添加头文件,并包含要导出的函数 int Max(int[],int)
然后编写Max的实现代码,同样新建一个Max.cpp文件并将它包含进项目中,如下图:
到此我们的DLL功能实现文件已完成,下面我们将导出这个函数
(3) 导出函数
我们需要在这一步安排开发的DLL中那些内容需要导出,在DLL中导出的内容可以包括函数、类和各类资源等。要导出内容,有两种方式:一是在定义这些导出内容的时候使用导出关键字_declspec(dllexport),另一种方法是在DLL项目中加入模块定义文件.DEF,注意:这两种方法不能同时使用,只能选择其中一种。
我们以这个项目为例,看看怎么通过上面的两种方法导出内容
l 使用导出关键字_declspec(dllexport)
// sort.h ——DLL的头文件(其中含有要导出的内容)
extern "C" _declspec(dllexport) void Max(int[],int);
我们可以进行相应的扩展,可以使用这个关键字来导出类中所有的公共成员变量和函数成员,例如:
Class _declspec(dllexport) myclass
{
//类myclass中所有的公共成员变量和函数成员都变成导出内容
}
l 使用模块定义文件.DEF
先在项目文件夹中添加一文本文件,命名为Win32DLLExample.DEF,然后往此文件中添加以下内容:
LIBRARY Win32DLLExample
DESCRIPTION “实现一个简单的Win32DLL”
EXPORTS
Max @1
下面对DEF文件的内容进行简单的解析:
DEF第一个语句必须是LIBRARY语句,她标识该DEF文件属于哪个DLL,LIBRARY后面紧跟着的就是DLL的名称(此处为Win32DLLExample),
第二个语句是DLL用途描述语句,不起实际作用,可省略
第三个语句EXPORTS是DEF文件的核心,它用于列举导出的内容,EXPORTS允许给函数编号(1到n),编号前面有个@,编号可省略
l 在本例中,我们选择使用导出关键字_declspec(dllexport)来导出函数Max():
(4) 创建DLL和LIB文件
把前面的工作做好后,直接点击build就会在项目Debug文件夹内生成DLL和LIB文件了。
到此,我们已经完成了这个简单的Win32 DLL开发,下面看看如何使用我们开发的这个DLL
三、普通Win32 DLL的使用
使用DLL有两种方式:隐式链接和显示链接,由于隐式链接不能正真体现DLL的灵活性,隐式链接是在应用程序加载时就把DLL也加载如内存,在程序使用多个DLL的情况下,隐式链接特别占用系统资源,而显示链接在需要用到DLL时才把DLL加载如内存,不需要时则可以释放,节约系统资源,提高效率。
此例中我们采用显示链接,windows提供了三个API函数用于显示链接方式使用DLL:首先,调用LoadLibrary函数加载DLL,其次,调用GetProcAddress得到和使用DLL中的函数首地址,最后,调用FreeLibrary函数卸载DLL,它们的具体用法见例子的代码。
(1) 创建应用程序
首先,创建一个控制台应用程序,命名为UseWin32DLL
(2) 添加包含文件和DLL,将刚才的Win32DLLExample项目中的max.h文件和Win32DLLExample.DLL拷贝到新建的项目UseWin32DLL目录下,再通过Project->Add to Project->Files菜单把sort.h添加入项目UseWin32DLL中,因为此时相对于UseWin32DLL项目来说,导出内容(相对于原来的DLL项目)变成了导入(相对于使用此DLL的程序),所以要把max.h中所有的_declspec(dllexport)改成_declspec(dllimport)
修改后的max.h文件内容如下:
(3) 编辑应用程序
为项目添加一个包含main主函数的c++源文件,该源文件的内容如下:
(4) Build应用程序查看结果
截图如下:
结果正常。
注意:
我们开发完成的程序当拷贝到其他目录或者其他机器上时,需要把DLL和程序一同打包过去并且放在同一个目录下,或者其他正确的目录(当前exe目录->windows系统目录->windows所在目录->环境变量Path中所指定的一系列目录),以上的括号内为exe搜索DLL的目录顺序,当这些目录中没有所需的DLL,则程序会报错,提示DLL加载失败,程序会自动退出。
看完了Win32的DLL开发,应该没有什么难度,拿到一个DLL怎么用呢?关键在于我们知道它提供了那些函数、资源等导出内容供我们使用,并且知道这些内容的用处,一般DLL会配有一个说明文档可供查阅。下面就让我们来看看基于MFC的DLL开发,更加强大。
四、MFC DLL开发
MFC DLL能够使用MFC类库,从而能在MFC下开发出功能更强大的模块。基于MFC的DLL有三种类型:静态链接MFC的常规DLL,动态链接MFC的常规DLL,和扩展MFC DLL,扩展MFC DLL比较常用,它支持C++接口,即能该DLL能够导出定义在其内部的类,其他两种MFC DLL不能导出MFC的派生类供其他模块使用,这也是扩展MFC DLL的优势所在,我们可以从MFC类库中派生出自己的类,然后把类导出,功能更强大。
我们通过一个简单的例子来看看如何开发和使用基于MFC的DLL,这里DLL类型为扩展MFC DLL
1、 创建MFC DLL项目
有图有真相,请看下图,文字我就不说了。
到此,直接按Finish按钮完成新建项目
2、 添加包含文件
这一步我们在项目中添加一个Date类,选择Insert->New Class;为了简单起见,在弹出的新建类对话框中class type我们选择Generic Class,新建一个普通的类,如下图:
点击OK完成类的创建。
3、 编辑导出内容
类比较简单,为了能更清楚的了解各种资源的导出以及在应用程序中的使用,共有三个导出内容类型:一个是菜单资源,一个是Date类,另外一个是导出函数。
完善我们的Date类,Date.h文件代码如下:
// Date1.h: interface for the Date class.
//
//////////////////////////////////////////////////////////////////////
#if !defined(AFX_DATE1_H__8D209C73_DEED_4B9F_BA73_435D30DA0A9A__INCLUDED_)
#define AFX_DATE1_H__8D209C73_DEED_4B9F_BA73_435D30DA0A9A__INCLUDED_
#if _MSC_VER > 1000
#pragma once
#endif // _MSC_VER > 1000
//定义菜单资源
#define IDR_MENU_MAIN 5000
#define IDR_MENU_SHOW 5001
//定义导出类
class AFX_EXT_CLASS Date
{
public:
Date();
virtual ~Date();
public:
void SetYear(int num);
void SetMonth(int num);
void SetDay(int num);
int GetYear();
int GetMonth();
int GetDay();
CString GetDate();
private:
int year;
int month;
int day;
};
//定义导出函数
extern "C" AFX_EXT_API Max(int,int);
#endif // !defined(AFX_DATE1_H__8D209C73_DEED_4B9F_BA73_435D30DA0A9A__INCLUDED_)
Date.cpp实现文件代码如下:
// Date1.cpp: implementation of the Date class.
//
//////////////////////////////////////////////////////////////////////
#include "stdafx.h"
#include "Date1.h"
#ifdef _DEBUG
#undef THIS_FILE
static char THIS_FILE[]=__FILE__;
#define new DEBUG_NEW
#endif
//////////////////////////////////////////////////////////////////////
// Construction/Destruction
//////////////////////////////////////////////////////////////////////
Date::Date()
{
day=0;
month=0;
year=0;
}
Date::~Date()
{
}
void Date::SetDay(int num)
{
day=num;
}
void Date::SetMonth(int num)
{
month=num;
}
void Date::SetYear(int num)
{
year=num;
}
int Date::GetDay()
{
return day;
}
int Date::GetMonth()
{
return month;
}
int Date::GetYear()
{
return year;
}
CString Date::GetDate()
{
CString date;
date.Format("%d年%d月%d日",year,month,day);
return date;
}
int Max(int a,int b)
{
return a>b?a:b;
}
上面的代码中,我们用了MFC定义的两个宏AFX_EXT_CLASS和AFX_EXT_API,其定义是:
#ifdef _AFXEXT
#define AFX_EXT_CALSS _declspec(dllexport)
#define AFX_EXT_API _declspec(dllexport)
#else
#define AFX_EXT_CALSS _declspec(dllimport)
#define AFX_EXT_API _declspec(dllimport)
#endif
我们现在资源编辑器中插入菜单:
由于我们在Date类中定义了菜单的两个ID名与系统默认的ID不一样,所以我们需要更改菜单的ID,如下:
同样,我们预定义的菜单ID值为5000和5001,而系统默认的ID值一般不同,所以我们要修改系统默认的ID值,具体做法是:打开resource.h文件,找到我们预定义的两个宏:
修改上图中被选中的两个宏的值,如下:
OK,所以的工作我们都做完了,剩下生成DLL和Lib文件。记得Build项目查看debug文件里面是否有后缀名为DLL和LIB的文件:
五、使用MFC DLL
才上面的例子中我们已经生成了自己的扩展MFC DLL了,现在让我们以通过使用这个DLL来熟悉MFC DLL的使用。
1、 新建一个基于对话框的MFC应用程序,名称为UseMFCDLL这里就不说怎么生成了
2、 添加文件
把上面开发的DLL项目文件夹中的MFCDLLExample.DLL和MFCDLLExample.LIB以及Date.h文件拷贝到当前项目UseMFCDLL文件夹下,将添加Date.h添加如项目中,设置项目的Link属性(project->Setting->Link属性页面),把MFCDLLExample.lib加入项目中。
3、 接下来我们为对话框添加DLL中定义的菜单资源。
在UseMFCDLLDlg.cpp文件中添加文件包含语句:#include ”Date.h”
修改UseMFCDLLDlg.cpp文件中的OnInitDialog()函数,如下:
4、 因为我们是使用DLL里面的菜单资源,我们不能直接在ClassWizard为菜单项IDR_MENU_SHOW命令添加相应函数,我们需要手动添加,如下:
(1) 在对话框头文件添加命令映射函数声明afx_msg OnCommandShowDate();
(2) 在对话框实现文件中添加消息映射:
(3) 然后在对话框实现文件中编写响应IDR_MENU_SHOW命令的函数OnCommandShowDate()的实现,我们在其中用到DLL中定义的Date类,代码如下:
void CUseMFCDLLDlg::OnCommandShowDate()
{
Date date;
date.SetDay(27);
date.SetMonth(Max(3,4));
date.SetYear(2010);
AfxMessageBox(date.GetDate());
}
运行结果如下图:
一切如我们所期望的。
备注:
上面两个例子的演示代码可以在csdn下载:
Win32DLL:http://download.csdn.net/source/2290658
MFC DLL:http://download.csdn.net/source/2290696