`
yanlijun250
  • 浏览: 752559 次
文章分类
社区版块
存档分类
最新评论

DLL动态链接库初学手迹(垃圾文,高手勿入)

阅读更多

还在大三的时候写的一篇垃圾文章,磁盘里的删了,最后就放个尸体在这里吧,也算是活了一趟的见证……

总论

伴随着软件规模的扩大,一个系统不再能由一个或几个人从头到尾全部维护,模块化设计制作成了产业的共识。<?xml:namespace prefix = o ns = "urn:schemas-microsoft-com:office:office" />

也许说到模块,您就会想起class,的确,类是一种模块,但它却仅仅是一个创建时的模块,每次对某一个模块的小小修改,就会导致对所有其他模块的重新编译。也许对于现在的您,这重新编译仅仅不过是代码之余的一次简短的休憩,然而,当您写的不再是一个个小小的习作,而是和别人一起开发一个大的系统,特别是对这个系统进行后期测试的时侯,您会发现,那每次半个小时以上的编译可以给您足够的理由发疯。

DLL是另一种模块,它是运行时的模块。你可以把类封装到一个个小小的DLL里面,一块块单独测试,最后再把它们拼接到一起。这不仅仅是省时间的选择,而且,很多具有强扩展性的模块还可以发布给别人,当然同样您也可以从别人那里获得具有某个功能的DLL模块,以节约开发时间和成本。您可以在很多大型软件里面看到成堆的DLL,这无疑是它强盛的生命力的证据。当然,一个应用程序附带的文件应当越少越好,否则调入一次应用程序就要打开数百个磁盘文件,估计谁都受不了。

正如开始接触WinMain时的那种敬畏心理,很多人在面对DLL时有些不知所措。其实现在写DLL已经不是什么困难的事情了,而MFCDLL也有很好的支持。嗯,好的,费话不多说,让我们开始旅途。

Dll技术基础

在介绍如何去编写DLL之前,您应该明白DLL是如何工作的。

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

DLL的连接

DllMainDLL的入口,您可以将之类比于WinMainDllMain在连接到进程和断开与进程的连接和其他响应时被调用。如果没有它,我们导入的函数什么都不会做。具体细节,请参照《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系统目录(SystemSystem32

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

MFCAppWizard提供了两种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共享DLLDLL例子。

<?xml:namespace prefix = v ns = "urn:schemas-microsoft-com:vml" /><shapetype id="_x0000_t75" stroked="f" filled="f" path="m@4@5l@4@11@9@11@9@5xe" o:preferrelative="t" o:spt="75" coordsize="21600,21600"><stroke joinstyle="miter"></stroke><formulas><f eqn="if lineDrawn pixelLineWidth 0"></f><f eqn="sum @0 1 0"></f><f eqn="sum 0 0 @1"></f><f eqn="prod @2 1 2"></f><f eqn="prod @3 21600 pixelWidth"></f><f eqn="prod @3 21600 pixelHeight"></f><f eqn="sum @0 0 1"></f><f eqn="prod @6 1 2"></f><f eqn="prod @7 21600 pixelWidth"></f><f eqn="sum @8 21600 0"></f><f eqn="prod @7 21600 pixelHeight"></f><f eqn="sum @10 21600 0"></f></formulas><path o:connecttype="rect" gradientshapeok="t" o:extrusionok="f"></path><lock aspectratio="t" v:ext="edit"></lock></shapetype>

点击完成,主要生成了一个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_CLICK

OnClick

IDC_IN

m_iInput

IDC_OUT

m_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.dllSTDDLL.lib文件拷贝出来,STDDLL.dll放到系统文件夹(98Me的放到WindowsSystem文件夹下,NT2000XP的放到WinNTSystem32文件夹下),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中的MFCAppWizardDll),然后在紧接着的对话框中选择MFC Extension DLLMFC扩展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.hExtClass.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++技术内幕》(清华大学出版社)等经典教材。

分享到:
评论

相关推荐

Global site tag (gtag.js) - Google Analytics