您的位置:首页 > 其它

vc 下DLL动态链接库的总结

2008-11-13 14:19 295 查看

伴随着软件规模的扩大,一个系统不再能由一个或几个人从头到尾全部维护,模块化设计制作成了产业的共识。
也许说到模块,您就会想起class,的确,类是一种模块,但它却仅仅是一个创建时的模块,每次对某一个模块的小小修改,就会导致对所有其他模块的重新编译。也许对于现在的您,这重新编译仅仅不过是代码之余的一次简短的休憩,然而,当您写的不再是一个个小小的习作,而是和别人一起开发一个大的系统,特别是对这个系统进行后期测试的时侯,您会发现,那每次半个小时以上的编译可以给您足够的理由发疯。
DLL是另一种模块,它是运行时的模块。你可以把类封装到一个个小小的DLL里面,一块块单独测试,最后再把它们拼接到一起。这不仅仅是省时间的选择,而且,很多具有强扩展性的模块还可以发布给别人,当然同样您也可以从别人那里获得具有某个功能的DLL模块,以节约开发时间和成本。您可以在很多大型软件里面看到成堆的DLL,这无疑是它强盛的生命力的证据。当然,一个应用程序附带的文件应当越少越好,否则调入一次应用程序就要打开数百个磁盘文件,估计谁都受不了。
正如开始接触WinMain时的那种敬畏心理,很多人在面对DLL时有些不知所措。其实现在写DLL已经不是什么困难的事情了,而MFC对DLL也有很好的支持。嗯,好的,费话不多说,让我们开始旅途。

Dll技术基础

在介绍如何去编写DLL之前,您应该明白DLL是如何工作的。
DLL表现为磁盘上一个个文件,它无法自行启动,只能等待别人来调用它——或者更确切的说,载入它以调用它里面储存的数据和函数。如果要用DLL,必须要把它们映射到应用程序进程的地址空间去,这是显而易见的事情,DLL就像一个雇佣兵,你如果要让它为你卖命,起码应该去载入它到你的军队(进程地址空间)中来。请注意这里,DLL一旦调入,只会在内存中保留一份页面,无论多少对它们的调用仅仅不过是把它们映射,而不像EXE那样,一次运行,就是一个全新的空间。DLL的真实所占空间是一定的,这对内存的节约也有好处。如果您想对DLL更全面的认识,请参阅Jeffrey Richter的《Advanced Windows》(中文版译名:《Windows核心编程》机械工业出版社)

DLL的连接

DllMain是DLL的入口,您可以将之类比于WinMain。DllMain在连接到进程和断开与进程的连接和其他响应时被调用。如果没有它,我们导入的函数什么都不会做。具体细节,请参照《Windows核心编程》。
DLL可以隐式或者显式连接到进程中。在DLL中包含有一个导出函数表,客户程序装入DLL时,可以通过函数的符号化名字来得到这些函数,然后通过函数表得到这些函数在DLL模块内的地址,继而通过这些地址调用函数。
DLL中,我们通过以下的方式声明函数Fun是要被导出的:
extern “C” __declspec(dllexport) int Fun();
由于有些DLL需要调用别的DLL的函数,因此,某些DLL也会设置导入:
extern “C” __declspec(dllimport) int Fun2();
如果我们要使用刚刚的DLL导出的函数,需要这样:
extern “C” __declspec(dllimport) int Fun();
当然,同时需要把与Dll一起生成的Lib文件加入到工程中,而且,“客户程序必须至少调用了DLL导出函数中的一个函数”。LIB文件中记载的是DLL的导出符号,只有通过它我们才能够得知要调用哪些函数。在编译完进行链接的时候,LIB中的这些符号被匹配并绑定到EXE文件中,EXE同时保存下来LIB中的DLL文件名。当程序开始运行的时候,EXE去到下面几个地方找到DLL并装载,然后在运行时动态链接DLL中的功能:
1、当前运行进程的EXE的所在目录
2、进程当前目录
3、Windows系统目录(System/System32)
4、Windows目录
5、Path环境变量里列出的目录
显式连接不需要LIB文件,直接调用LoadLibrary(“DLL路径名”)就可以完成。如下:
HINSTANCE hInst;
hInstance = LoadLibary(“DLL路径名”);
如果要使用刚刚DLL导出的函数,现在需要这么做:
SORTPROC* pFun;
pFun = (SORTPROC*) GetProcAddress(hInstance , “Fun”);
int ret = (*pFun)();
显式连接的好处是可以根据需要在任何时候装载DLL,而隐式连接则在一开始就装载了所有的DLL。

用MFC来做DLL

正规的DLL和扩展的DLL

MFC的AppWizard提供了两种DLL的支持:扩展的DLL和正规的DLL。正规的DLL可以被任何一个Win32开发环境装载,不过它只能导出C风格的函数,而不能导出C++类、成员函数或者重载函数,但我们可以在正规DLL中使用这些东西。
而扩展的DLL可以导出整个C++类,但是它要求比较高:首先客户程序必须动态链接到MFC库,而且和要用的扩展DLL连接到同一个版本的MFC DLL。

一个正规DLL的例子

创建正规DLL时可以选择静态或者动态(使用MFC共享DLL)链接到MFC库。如果选择了静态链接,则DLL将包括所有它需要的MFC库代码的拷贝,这样一个DLL会比较大,但是可以独立运用,不再需要去考虑运行环境是否会有MFC支持。使用共享的MFC DLL则会小一些,但是必须保证客户机器上有相应的MFC DLL。
下面就是一个生成使用MFC共享DLL的DLL例子。


点击完成,主要生成了一个stddll.h和一个stddll.cpp。
现在,在stddll.cpp中加入我们要导出的函数,假设是一个平方函数:
extern “C” __declspec(dllexport) int DllFunction(int val)
{
AFX_MANAGE_STATE(AfxGetStaticModuleState()); //注意这一句一定要有
return val*val;
}
下面我们需要测试这个DLL,新建一个MFC的对话框项目:

注意这里一定要选择当共享DLL。

现在我们为这个对话框添加三个控件:一个按扭,两个编辑框。然后按照如下方式设置控件:

控件ID数据函数
IDC_CLICKOnClick
IDC_INm_iInput
IDC_OUTm_iOutput
首先我们要导入DllFunction,在TESTDLLDLG.H文件中添加:
extern “C” __declspec(dllimport) int DllFunction(int val);
写OnClick函数如下:
void CTESTDLLDlg::OnClick()
{
// TODO: Add your control notification handler code here
UpdateData(TRUE);
m_iOutput = DllFunction(m_iInput);
UpdateData(FALSE);
}

好了,编译下,不成功对吧,呵呵,我们还没有告诉MFC去哪里找DLL呢,那该怎么办呢?先把刚刚DLL工程的Debug文件夹下面的STDDLL.dll和STDDLL.lib文件拷贝出来,STDDLL.dll放到系统文件夹(98/Me的放到Windows/System文件夹下,NT/2000/XP的放到WinNT/System32文件夹下),STDDLL.lib放到当前TESTDLL工程的Debug文件夹下,然后作如下设置,在“工程”菜单下面找到“设置”:

在“对象/库模块”里面添加那样一句就可以了,这里用的是相对工程文件夹的路径,如果您用了别的工程设置方式,只需要这里添上相对于工程相应的.dsp文件的相对路径就可以。
下面再运行就应该成功了,结果如下:

总结一下,DLL方要完成的任务:
1、生成正规DLL项目。
2、在cpp文件中按格式添加所需要的函数。
Exe方要完成的任务:
1、在cpp文件中完成对dll中函数的调用。
2、在cpp相应的h文件中用__declspec(import)声明DLL要导出的函数。
3、完成对LIB路径的设置,并且最重要的,把DLL拷贝到EXE能找到它的地方。
其实在上面的例子中,你完全可以不把dll文件拷到系统目录下,而是拷贝到TESTDLL工程的Debug文件夹下,因为TESTDLL工程生成的Exe文件肯定在那里,这样,DLL就一定会在EXE可以找到的地方了。如果您有兴趣,还可以将DLL拷贝到别的地方试试看。

一个扩展DLL的例子

运行AppWizard,产生Projects中的MFCAppWizard(Dll),然后在紧接着的对话框中选择MFC Extension DLL(MFC扩展DLL)。如图:

然后,会主要产生下面的代码和一个Def文件:

// EXTDLL.cpp : Defines the initialization routines for the DLL.
//

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

#ifdef _DEBUG
#define new DEBUG_NEW
#undef THIS_FILE
static char THIS_FILE[] = __FILE__;
#endif

static AFX_EXTENSION_MODULE EXTDLLDLL = { NULL, NULL };

extern "C" int APIENTRY
DllMain(HINSTANCE hInstance, DWORD dwReason, LPVOID lpReserved)
{
// Remove this if you use lpReserved
UNREFERENCED_PARAMETER(lpReserved);

if (dwReason == DLL_PROCESS_ATTACH)
{
TRACE0("EXTDLL.DLL Initializing!/n");

// Extension DLL one-time initialization
if (!AfxInitExtensionModule(EXTDLLDLL, hInstance))
return 0;
//这里删掉了注释

new CDynLinkLibrary(EXTDLLDLL);
}
else if (dwReason == DLL_PROCESS_DETACH)
{
TRACE0("EXTDLL.DLL Terminating!/n");
// Terminate the library before destructors are called
AfxTermExtensionModule(EXTDLLDLL);
}
return 1; // ok
}
加入新的类:ExtClass.h和ExtClass.cpp,在ExtClass.h中添下如下代码:
#pragma once

#ifdef _WINDLL
#define DLL __declspec(dllexport)
#else
#define DLL
#endif

class DLL CExtDll
{
BOOL m_iNum;
public :
CExtDll();
~CExtDll();
void DllMessageBox(LPSTR pszString);
void Func(int i);
int ReturnVal();

} ;
在.cpp中添加:
#include "stdafx.h"
#include "ExtClass.h"

CExtDll:: CExtDll ( )
{
m_iNum = 255 ;
}

CExtDll:: ~CExtDll()
{ }

void CExtDll::DllMessageBox(LPSTR pszString)
{
AfxMessageBox ( pszString ) ;
}

void CExtDll::Func(int i)
{
m_iNum = i;
}

int CExtDll::ReturnVal()
{
return m_iNum;
}
然后编译,得到.lib和.dll文件。
然后建立一个基于对话框的工程TestExtDll,设置基本与TestDll相同。然后做如下设置:
1、添加一个编辑框控件,利用Class Wizzard添加成员变量int类型的m_iNum。
2、为确定按钮添加事件OnOK:在TestExtDllDlg.cpp中写下如下代码:
先是在最开头写一句:
#include “ExtClass.h”
这个.h文件应该从那个ExtDll工程中原封不动的拷贝过来。
然后为OnOK添加代码:

void CTestExtDllDlg::OnOK()
{
// TODO: Add extra validation here
UpdateData(TRUE);
//注意下面这几句,完成对Dll中类的调用
CExtDll aDll;
aDll.DllMessageBox("Hello : )!!");
aDll.Func(88);
m_iNum = aDll.ReturnVal();

UpdateData(FALSE);
}
3、然后把ExtDll工程生成的.lib和.dll文件如下处理:
3a、拷贝到当前TestExtDll工程的Debug文件夹下
3b、在工程-设置-link选项卡中,对“对象/库模块”中加入debug/ExtDll.lib。
最后编译,结果如下:

看起来也不是很困难吧。
到这里我们已经把MFC Dll制作的基本思路说完了,如果对Dll仍有疑问,请参照《Visual C++技术内幕》(清华大学出版社)等经典教材。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: