MFC下DLL编程图解.docx
- 文档编号:23485550
- 上传时间:2023-05-17
- 格式:DOCX
- 页数:27
- 大小:321.19KB
MFC下DLL编程图解.docx
《MFC下DLL编程图解.docx》由会员分享,可在线阅读,更多相关《MFC下DLL编程图解.docx(27页珍藏版)》请在冰豆网上搜索。
MFC下DLL编程图解
MFC下DLL编程(图解)
DLL(DynamicLinkLibrary,动态链接库)是微软公司为Windows和OS/2操作系统设计一种供应用程序在运行时调用的共享函数库。
DLL是应用程序的一种扩展,也是软件共享和重用的传统方法。
DLL除了可同时被多个应用程序共享外,还可以在不改变调用接口(从而不需修改使用它的应用程序)的情况下,改进和升级里面的库函数。
而且DLL与编写它的语言无关,例如,用VC生成的规则DLL,可以被VB、Delphi等生成的应用程序使用。
DLL可以用多种语言和工具编写,我们这里只介绍如何使用MFC来编写和使用DLL。
相关说明文档位于MSDN帮助的“目录\开发工具和语言\VisualStudio\VisualC++\常见编程方法\DLL\”中。
8.1基础
本节先讨论DLL与静态库的区别,然后列出几种适合放置DLL的目录,最后介绍MFCDLL的三种类型。
8.1.1DLL与静态链接库
静态链接库Lib(StaticLinkLibrary),是在编译的链接阶段将库函数嵌入到应用程序的内部。
如果系统中运行的多个应用程序都包含所用到的公共库函数,则必然造成很大的浪费。
这样即增加了链接器的负担,也增大了可执行程序的大小,还加大了内存的消耗。
Lib的好处是应用程序可以独立运行,而不需要在操作系统中另外安装对应的DLL。
而DLL采用动态链接,对公用的库函数,系统只有一个拷贝(一般是位于系统目录的*.DLL文件),而且只有在应用程序真正调用时,才加载到内存。
在内存中的库函数,也只有一个拷贝,可供所有运行的程序调用。
当再也没有程序需要调用它时,系统会自动将其卸载,并释放其所占用的内存空间。
参见图8-1。
图8-1静态库函数与动态链接库的区别
DLL的缺点是应用程序不能独立运行,需要在操作系统中另外安装对应的DLL。
例如,如果你的MFC项目被设置成“在共享DLL中使用MFC”的,则虽然生成的可执行程序很小,但是在其他没有安装VisualC++(运行环境)的机器上是不能直接运行的,需要另外安装MFC的动态链接库(如mfc90.dll)。
8.1.2放置DLL的目录
为了使需要动态链接库的应用程序可以运行,需要将DLL文件放在操作系统能够找到的地方。
Windows操作系统查找DLL的目录顺序为:
1.所在目录——当前进程的可执行模块所在的目录,即应用程序的可执行文件(*.exe)所在的目录。
2.当前目录——进程的当前目录。
3.系统目录——Windows操作系统安装目录的系统子目录,如C:
\Windows\System32。
可用GetSystemDirectory函数检索此目录的路径。
4.Windows目录——Windows操作系统安装目录,如C:
\Windows\。
可用GetWindowsDirectory函数检索此目录的路径。
5.搜索目录——PATH环境变量中所包含的自动搜索路径目录,一般包含C:
\Windows\和C:
\Windows\System32\等目录。
可在命令行用Path命令来查看和设置,也可以通过(在“我的电脑”右键菜单中选“属性”菜单项)“系统属性”中的环境变量,来查看或编辑“Path”系统变量和“PATH”用户变量。
8.1.3MFCDLL的类型
使用MFC编写的DLL,可以分成两大类:
●规则DLL——规则(regular)DLL中所包含的函数,可以被所有Windows应用程序使用;
⏹共享MFC——DLL中不包含MFC库函数,需要另外安装MFC动态链接库后才能使用;
⏹静态MFC——DLL中包含MFC库函数,可以脱离MFC动态链接库独立使用。
●扩展DLL——扩展(extension)DLL中所定义的类和函数,只能被所MFC应用程序使用。
而且扩展DLL中不能包含MFC库函数,也需要另外安装MFC动态链接库后才能使用。
8.1.4导出函数的方法
使用MFC创建DLL时,从项目中导出(export)函数到DLL文件的方法有:
●使用模块定义文件(.def)。
●使用__declspec(dllexport)关键字或其替代宏AFX_EXT_CLASS。
这两种方法是互斥的,对每个函数只需用一种方法即可。
另外,DEF文件只能用来导出函数,不能用于导出整个类。
导出C++类,必须用__declspec(dllexport)关键字或其替代宏AFX_EXT_CLASS。
1.DEF文件
模块定义(moduledefinition)文件(.def)是包含一个或多个描述DLL各种属性的模块语句的文本文件。
DEF文件必须至少包含下列模块定义语句:
●文件中的第一个语句必须是LIBRARY语句。
此语句将.def文件标识为属于DLL。
LIBRARY语句的后面是DLL的名称(缺省为DLL项目名)。
链接器将此名称放到DLL的导入库中。
●EXPORTS语句列出名称,可能的话还会列出DLL导出函数的序号值。
通过在函数名的后面加上@符和一个数字,给函数分配序号值。
当指定序号值时,序号值的范围必须是从1到N,其中N是DLL导出函数的个数。
即,DEF文件的格式为:
(在这两个语句之间,还可以加上可选的描述语句:
DESCRIPTION"库描述串"。
分号;后的文本内容行为注释)
;库名.def
LIBRARY库名
EXPORTS
函数名1@1
函数名2@2
……
函数名n@n
在使用MFCDLL向导创建MFCDLL项目时,VC会自动创建一个与项目同名但没有任何函数导出项的DEF文件(项目名.def),格式为:
;项目名.def:
声明DLL的模块参数。
LIBRARY"项目名"
EXPORTS
;此处可以是显式导出
例如,项目名为RegDll的DEF文件(RegDll.def)的内容为:
;RegDll.def:
声明DLL的模块参数。
LIBRARY"RegDll"
EXPORTS
;此处可以是显式导出
如果生成扩展DLL并使用.def文件导出,则将下列代码放在包含导出类的头文件的开头和结尾:
#undefAFX_DATA
#defineAFX_DATAAFX_EXT_DATA
//<你的头文件体>
#undefAFX_DATA
#defineAFX_DATA
这些代码行确保内部使用的MFC变量或添加到类的变量是从扩展DLL导出(或导入)的。
例如,当使用DECLARE_DYNAMIC派生类时,该宏扩展以将CRuntimeClass成员变量添加到类。
省去这四行代码可能会导致不能正确编译或链接DLL,或在客户端应用程序链接到DLL时导致错误。
当生成DLL时,链接器使用.def文件创建导出(.exp)文件和导入库(.lib)文件。
然后,链接器使用导出文件生成DLL文件。
隐式链接到DLL的可执行文件在生成时链接到导入库。
请注意,MFC本身就是使用.def文件从MFCx0.dll导出函数和类的。
2.关键字或宏
除了使用DEF文件来导出函数外,还可以在源程序中使用__declspec(dllexport)关键字或其替代宏AFX_EXT_CLASS:
#defineAFX_EXT_CLASSAFX_CLASS_EXPORT(定义在头文件afxv_dll.h中)
#defineAFX_CLASS_EXPORT__declspec(dllexport)(定义在头文件afxver_.h中)
来导出函数和整个C++类。
具体的格式为:
●导出整个类:
classAFX_EXT_CLASS类名[:
public基类]
{
……
}
●导出类的成员函数:
class类名[:
public基类]
{
AFX_EXT_CLASS返回类型函数名1(……);
AFX_EXT_CLASS返回类型函数名2(……);
……
}
●导出外部C格式的(全局)函数:
extern"C"__declspec(dllexport)返回类型函数名(……)
{
……
}
如果希望用MFC(C++)编写的规则DLL中的函数,也能够被非MFC程序来调用,需要为函数声明指定extern"C"。
不然,C++编译器会使用C++类型安全命名约定(也称作名称修饰)和C++调用约定(使用此调用约定从C调用会很困难)。
为了使用方便,可以定义宏:
#defineDllExportextern"C"__declspec(dllexport)
然后再使用它,例如:
DllExportintAdd(intd1,intd2){……}
8.2扩展DLL
使用MFC编写的扩展DLL,可以导出整个类(从而能使用类中的所有成员,包括数据成员和成员函数),也可以导出指定的若干(成员或全局)函数。
下面我们通过一个四则运算的例子,看看如何用宏AFX_EXT_CLASS来编写和使用导出整个C++类的扩展MFCDLL。
8.2.1创建DLL项目
我们创建一个名为ExtDll的扩展DLL的“VisualC++”之“MFC”的“MFCDLL”项目,注意需选中“创建解决方案的目录”复选框,参见图8-2。
图8-2新建MFCDLL项目ExtDll的对话框
按“确定”钮,弹出“MFCDLL向导”对话框。
在“DLL类型”栏中,选中“扩展DLL”单选钮,参见图8-3。
按“完成”钮,创建ExtDll解决方案和项目。
图8-3选择“扩展DLL”的MFCDLL向导对话框
8.2.2添加导出类
为新项目添加用于四则计算的导出类CCompute。
方法有多种,可以在项目管理区的“类视图”页中,选中项目名“ExtDll”,按鼠标右键,在弹出菜单中选“添加\类”。
在弹出的“添加类”对话框中,选择“VisualC++”之“MFC”的“MFC类”项,参见图8-4。
图8-4添加类对话框
按“添加”钮,弹出“MFC类向导”对话框。
在“类名”栏中键入“CCompute”,在“基类”下拉式列表,选“CObject”,参见图8-5。
按“完成”钮,添加该类到ExtDll项目。
图8-5MFC类向导对话框
8.2.3编写导出类代码
我们将整个CCompute类设为导出类,并在里面添加2个成员变量、1个构造函数和4个用于四则运算的成员函数,外加1个演示导出函数的取模全局函数Mod。
下面是CCompute类的头文件(Compute.h),其中红色的部分是自己添加:
(注意导出宏AFX_EXT_CLASS的使用)
#pragmaonce
//CCompute命令目标
classAFX_EXT_CLASSCCompute:
publicCObject
{
public:
intm_data1,m_data2;
public:
CCompute();
CCompute(intd1,intd2);
virtual~CCompute();
public:
intAdd();
intSub();
intMul();
doubleDiv();
};
AFX_EXT_CLASSintMod(intd1,intd2);
下面是CCompute类的代码源文件(Compute.cpp),其中红色为自己添加的部分:
//Compute.cpp:
实现文件
//
#include"stdafx.h"
#include"Compute.h"
//CCompute
CCompute:
:
CCompute()
{
}
CCompute:
:
CCompute(intd1,intd2)
{
m_data1=d1;
m_data2=d2;
}
CCompute:
:
~CCompute()
{
}
//CCompute成员函数
intCCompute:
:
Add()
{
returnm_data1+m_data2;
}
intCCompute:
:
Sub()
{
returnm_data1-m_data2;
}
intCCompute:
:
Mul()
{
returnm_data1*m_data2;
}
doubleCCompute:
:
Div()
{
if(m_data2==0){
AfxMessageBox(L"Dividedbyzero!
");
return0;
}
return(double)m_data1/m_data2;
}
intMod(intd1,intd2)
{
if(d2==0){
AfxMessageBox(L"Modulobyzero!
");
return0;
}
returnd1%d2;
}
编译运行时,后弹出图8-6所示的对话框:
图8-6调试会话的可执行文件对话框
要求你选择或输入使用此DLL的应用程序之可执行文件的名称或路径。
这是因为DLL虽然包含了可运行函数的二进制代码,但是它并不是独立的应用程序,不能单独运行。
因此,我们必须编写使用DLL的客户程序。
8.2.4添加客户程序项目
为了演示扩展DLL的应用,我们在原解决方案ExtDll中,添加一个客户程序项目ExtClient。
具体做法是,打开新建项目对话框,选中“VisualC++”之“MFC”的“MFC应用程序”模板,键入项目名ExtClient。
注意,需选在对话框底部的“解决方案”下拉式列表中选中“添入解决方案”表项,参见图8-7。
图8-6新建客户程序项目的对话框
按“确定”钮进入“MFC应用程序向导”对话框,在“应用程序类型”页,选中“基于对话框”单选钮,按“完成”添加项目。
此时,ExtDll解决方案包含两个项目:
DLL项目ExtDll和客户程序项目ExtClient,生成的文件目录结构为:
ExtDll←解决方案目录
Debug←解决方案的调试目录
Release←解决方案的发行目录
ExtDll←DLL项目目录
Debug←DLL的调试目录
Release←DLL的发行目录
res←DLL的资源目录
ExtClient←客户程序项目目录
Debug←客户程序的调试目录
Release←客户程序的发行目录
res←客户程序的资源目录
8.2.5设置依赖项
为了使客户程序可以调用DLL,需要将它们关联起来。
最简单的办法是设置DLL项目为客户项目的依赖项。
具体做法是,在项目管理区中选中客户项目名“ExtClient”,选中菜单项“项目\项目依赖项”,在弹出的“项目依赖项”对话框中,选中“依赖栏”中的“ExtDll”复选框,参见图8-7。
图8-7设置ExtClient项目依赖项的对话框
8.2.6编写客户程序代码
1.编辑对话框资源
添加表示操作数的2个静态文本框和2个文本编辑框(ID值分别为IDC_DATA1和IDC_DATA2)、5个表示四则运算和取模运算的按钮(ID值分别为IDC_ADD、IDC_SUB、IDC_MUL、IDC_DIV和IDC_MOD)、表示计算结果的1个静态文本框和1个文本编辑框(ID值为IDC_RESULT),删除原来“确定”按钮,将原来的“取消”按钮的“Caption”属性值改为“退出”,参见图8-8。
图8-8客户程序的对话框界面
2.添加控件变量
为了动态获取用户输入的数据,我们需要为2个表示操作数据的文本编辑框,添加控件的Value值类别int型变量m_iData1和m_iData2。
3.添加事件处理
分别对5个计算按钮,为对话框类CExtClientDlg添加按钮通知消息BN_CLICKED(鼠标单击)事件的处理程序OnBnClickedAdd等。
4.编写代码
为了让客户程序可以使用DLL项目中的计算类CCompute,需要在客户程序对话框类CExtClientDlg的头文件的头部添加包含语句:
#include"..\ExtDll\Compute.h"
在对话框类的定义中,手工添加公共型类变量和成员函数:
public:
CCompute*m_pComp;
voidComp(UINTnID);
最后的头文件为:
(其中绿色为向导添加的,红色为手工添加的)
//ExtClientDlg.h:
头文件
//
#include"..\ExtDll\Compute.h"
#pragmaonce
//CExtClientDlg对话框
classCExtClientDlg:
publicCDialog
{
……
public:
intm_iData1;
intm_iData2;
CCompute*m_pComp;
voidComp(UINTnID);
afx_msgvoidOnBnClickedAdd();
afx_msgvoidOnBnClickedSub();
afx_msgvoidOnBnClickedMul();
afx_msgvoidOnBnClickedDiv();
afx_msgvoidOnBnClickedMod();
};
在客户对话框类的初始化对话框成员函数OnInitDialog中,手工添加设置数据编辑框初值的代码(红色部分):
BOOLCExtClientDlg:
:
OnInitDialog()
{
CDialog:
:
OnInitDialog();
……
//TODO:
在此添加额外的初始化代码
SetDlgItemInt(IDC_DATA1,5);
SetDlgItemInt(IDC_DATA2,3);
returnTRUE;//除非将焦点设置到控件,否则返回TRUE
}
代码文件ExtClientDlg.cpp中其他新加内容有:
(其中红色部分为手工添加的)
voidCExtClientDlg:
:
OnBnClickedAdd()
{
//TODO:
在此添加控件通知处理程序代码
Comp(IDC_ADD);
}
voidCExtClientDlg:
:
OnBnClickedSub()
{
//TODO:
在此添加控件通知处理程序代码
Comp(IDC_SUB);
}
voidCExtClientDlg:
:
OnBnClickedMul()
{
//TODO:
在此添加控件通知处理程序代码
Comp(IDC_MUL);
}
voidCExtClientDlg:
:
OnBnClickedDiv()
{
//TODO:
在此添加控件通知处理程序代码
Comp(IDC_DIV);
}
voidCExtClientDlg:
:
OnBnClickedMod()
{
//TODO:
在此添加控件通知处理程序代码
Comp(IDC_MOD);
}
voidCExtClientDlg:
:
Comp(UINTnID)
{
UpdateData();//动态获取用户输入的数据并赋值给对应的控件变量
m_pComp=newCCompute(m_iData1,m_iData2);//创建计算对象
intr;
doubledr;
switch(nID){//进行四则和取模运算
caseIDC_ADD:
r=m_pComp->Add();break;
caseIDC_SUB:
r=m_pComp->Sub();break;
caseIDC_MUL:
r=m_pComp->Mul();break;
caseIDC_DIV:
dr=m_pComp->Div();break;
caseIDC_MOD:
r=Mod(m_iData1,m_iData2);break;
}
deletem_pComp;
if(nID!
=IDC_DIV)SetDlgItemInt(IDC_RESULT,r);//显示整数结果
else{//显示除法所得的实数结果
wchar_tbuf[20];
swprintf_s(buf,20,L"%g",dr);
SetDlgItemText(IDC_RESULT,buf);
}
}
8.2.7编译运行
为了运行客户程序,需要将客户程序项目设置成启动项目。
具体做法是,先在项目管理区中选中ExtClient项目,然后选择菜单项“项目\设为启动项目”。
编译后,会在解决方案的Debug或Release目录中生成动态链接库文件ExtDll.dll和客户程序的可执行文件ExtClient.exe,以及DLL的导出文件ExtDll.exp和(静态连接)库文件ExtDll.lib。
运行结果如图8-9所示:
图8-9客户程序ExtClient的运行结果
8.3规则DLL
使用MFC编写的规则DLL,虽然只能导出函数而不能导出整个类,但是其导出的函数却可以其他被非MFC应用程序所调用。
下面我们仍通过上面的四则运算的例子,看看如何用关键字__declspec(dllexport)和extern"C"来编写和使用导出若干(全局)C函数的规则MFCDLL。
8.3.1创建DLL项目
我们创建一个名为RegDll的规则DLL的“VisualC++”之“MFC”的“MFCDLL”项目,注意仍需选中“创建解决方案的目录”复选框,参见图8-10。
图8-10新建MFCDLL项目RegDll的对话框
按“确定”钮,弹出“MFCDLL向导”对话框。
在“DLL类型”栏中,选中“使用共享MFCDLL的规则DLL”单选钮,参见图8-11。
按“完成”钮,创建RegDll解决方案和项目。
图8-11选择规则DLL的MFCDLL向导对话框
也可以选择“带静态链接MFC的规则DLL”,差别是所生成的DLL中会包含MFC库,当然所生成的库文件也会大一些(但因此可不用另外安装MFC动态链接库)。
例如,在此例中,选共享MFC所生成的RegDll.dll文件只有13KB大,而选择静态MFC的则有199KB。
规则DLL项目是使用共享MFC还是使用静态MFC,也可以在生成DLL项目之后,通过项目属性对话框的“配置属性\常规”页中的“MFC的使用”栏中的下拉式列表选项来切换,这一点与普通MFC应用程序项目的类似。
8.3.2使用DEF文件导出函数
1.编辑DEF文件
在项目管理区中,选择“解决方案资源管理器”页,展开“RegDll”项目项,双击其“RegDll.def”子项,打开DLL项目中自动生成的DEF文件。
在该DEF文件中加入需要导出的5个函数项:
(红色部分为手工添加的)
;RegDll.def:
声明DLL的模块参数。
LIBRARY"RegDll"
EXPORTS
;此处可以是显式导出
Add@1
Sub@2
Mul@3
Div@4
Mod@5
2.编写导出函数代码
可以在RegDll项目的应用程序类的代码文件RegDll.cpp的尾部手工添加如下代码:
extern"C"
- 配套讲稿:
如PPT文件的首页显示word图标,表示该PPT已包含配套word讲稿。双击word图标可打开word文档。
- 特殊限制:
部分文档作品中含有的国旗、国徽等图片,仅作为作品整体效果示例展示,禁止商用。设计者仅对作品中独创性部分享有著作权。
- 关 键 词:
- MFC DLL 编程 图解