如何编写DLL文件

——动态链接库(dll)是包含共享函数库的二进制文件,可以被多个应用程序同时使用。建立应用程序的可执行文件时,不必将DLL连接到应用程序中,而是在运行时动态装载DLL,装载时DLL被映射到调用进程的地址空间中。通常我们在调用DLL时所需的DLL文件必须位于以下三个目录之一: 
——(1)Windows的系统目录:/windows/system; 
——(2)DOS中path所指出的任何目录; 
——(3)程序所在的目录; 

一.动态链接库(DLL)结构 
——DLL中定义有两种函数:导出函数(export function)和内部函数 
(internal function),导出函数可以被其他模块调用,内部函数只能在DLL内部使用。我们在用C++定制DLL文件时,需要编写的就是包含导出函数表的模块定义文件(.DEF)和实现导出函数功能的C++文件。下面以Sample.dll为例介绍DEF文件和实现文件的结构: 

——1.模块定义文件(.DEF)是由一个或者多个用于描述DLL属性的模块语 
句组成的文本文件,每个.DEF文件至少必须包含以下模块定义语句: 
第一个语句必须是LIBRARY语句,指出DLL的名字。 
EXPORTS语句列出被导出函数的名字。 
可以使用DESCRIPTION语句描述DLL的用途(此句可选)。 
";"对一行进行注释(可选) 
——2.实现文件(.cpp文件为例) 
——实现入口表函数的.cpp文件中,包含DLL入口点处理的API函数和导出 
函数的代码。 

二.创建Sample.dll 
——1.首先创建Sample.dll的工程,启动VC++5.0按以下步骤生成DLL工程: 
在菜单中选择File/New/Project 
在工程列表中选择Win32Dynamic-LinkLibrary 
在ProjectName中输入工程名:Sample 
单击Location右边按钮,选择c:/sample目录 
单击OK完成,至此已创建了Sample.dll的工程文件 
----2.创建Sample.def文件 
在菜单中选择File/New/TextFile 
输入以下完代码后保存文件名"Sample.def" 
;Sample.def 
;指出DLL的名字Sample,链接器将这个名 
字放到DLL导入库中 
LIBRARY Sample 
;定义导出函数ShowMe()为例 
EXPORTS 
ShowMe 
;def文件结束 
---- 3. 创 建Sample.cpp 
.在菜单中选择File/New/C++ Source File项 
.输入以下代码后保存文件名"Sample.cpp" 
//Sample.cpp 
#include < windows.h > 
int ShowMe(void); 
//DllEntryPoint为DLL入口点函数, 
负责初试化并终止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 ShowMe(void) 

//蜂鸣器响一下 
MessageBeep((WORD)-1); 
MessageBox("你好!"); 
return 1; 

——4.编译DLL文件——从 Build 菜单中选择 BuildSample.DLL, 产生 
Sample.DLL文件,以后就可以随时调用了。 

三.在应用程序中调用DLL文件 
——在应用程序中要首先装入DLL后才能调用导出表中的函数,例如用MFC 
创建基于对话框的工程Test,并在对话框上放置"Load"按钮,你就必须添加装载代码。----1.首先在TestDlg.cpp的首部添加变量设置代码: 
//设置全局变量gLibSample用于存储DLL句柄 
HINSTANCE gLibSample=NULL; 
//第二个变量ShowMe是指向DLL 
库中ShowMe()函数的指针 
typedef int(* SHOWME)(void); 
SHOWME ShowMe; 
2.利用ClassWizard为"Load"按钮添加装载DLL的代码 
Void CTestDlg::OnLoadButton() 

//要添加的代码如下 
if(gLibMyDLL!=NULL) 

MessageBox("The Sample.DLL has already been load."); 
return; 

//装载Sample.dll,未加路径,将在三个默认路径中寻找 
gLibSample=LoadLibrary("SAMPLE.DLL"); 
//返回DLL中ShowMe()函数的地址 
ShowMe=(SHOWME) 
GetProcAddress(gLibSample,"ShowMe"); 
//代码添加完毕 

——3.只要DLL装载成功,在应用程序中就可以直接调用ShowMe()函数,此时已完成了定制和调用DLL的全部过程。----本程序在Windows95,VC++5.0中运行通过。 


生成一个Dll工程,然后将你的类和函数都添加进去,然后将要输出的函数在该工程下的Translate.def文件中将名字列出(注意,只要名字,不要括号、参数等);实际上和你的Exe工程没有很大的区别。

如果你要用VB用,在Dll中定义你的函数的时候前面要加上“__stdcall”关键字,否则VB没有办法使用。

建议Dll中少用资源,只用函数比较好,如果要用资源,在资源载入时要做一个处理,不然会由于hModule的问题发生错误。


第十三章 动态链接库

在Windows应用程序中使用动态链接库有很多的好处。最主要的一点说是它可以使得多个应用程序共享一段代码,从而可以大幅度的降低应用程序的资源开销,同时很缩小了应用程序的最终执行代码的大小。此外,通过使用动态链接库,我们可以把一些常规的例程独立出来,有效的避免了不必要的重复开发,并且,由于应用程序使用了动态链接的方式,还可以在不需重新改写甚至编译应用程序的基础上更新应用程序的某些组件。

本章介绍Visual C++ 5.0中的动态链接库的创建和使用。这些内容包括


为什么要使用DLL 


不同的DLL类型之间的选择 


在程序中创建和使用DLL 


使用DLL扩展MFC 

本章的重点在讲述Win32 DLL和基于MFC的常规DLL,对于MFC扩展DLL仅给了一个很简单的例子,更深入的资料请参阅Visual C++的联机文档。

第一节 概述

动态链接库(DLL,Dynamic-Link Library)也是一种可执行文件,只不过它不能象普通的EXE文件那样可以直接运行,而是用来为其它可执行文件(包括EXE文件和其它DLL)提供共享函数库。使用DLL的应用程序可以调用DLL中的导出函数(imported function),不过在应用程序本身的执行代码中并不包含这些函数的执行代码,它们经过编译和链接之后,独立的保存在DLL中。这是一种和过去常用的静态链接不同的方式,使用静态链接库(static link library)的应用程序从函数库得到所引用的函数的执行代码,然后把执行代码放进自身的执行文件中,这样,应用程序在运行时就可以不再需要静态函数库的支持。而使用DLL的应用程序只包括了用于从DLL中定位所引用的函数的信息,而没有函数具体实现,要等到程序运行时才从DLL中获得函数的实现代码。显然,使用了DLL的应用程序在运行时必须要有相应的DLL的支持。

DLL在Windows编程中得到了广泛的应用。Windows应用程序的基础:Windows API函数中的相当部分就是由一组DLL所提供的,这些DLL从安装Windows起就存在于系统中了。尽管在前面的几章中我们没有明确的提到,但事实上我们早就在使用DLL进行编程了,只不过,所使用的DLL都是现成的,并且所有调用DLL的操作都由Visual C++的编译和链接程序替我们完成了。

本章将详细的介绍如何创建自己的DLL和如何使用自己创建的DLL进行编程。


为什么要使用DLL呢?这当然是因为与传统的静态链库相比,使用DLL有着更多的优势:


使用DLL提供了一种共享数据和代码的方便途径。并且,由于多个应用程序可以共享同一个DLL中的函数,因此,使用DLL可以显著的节省磁盘空间。尤其对于Windows应用程序,有很多的操作都是“标准化”了的,如果使用传统的静态链接,每一个需要完成这些操作的应用程序都必须在自己的执行文件中包括相同的执行代码,这不但使单个的应用程序变得更长,也浪费了磁盘空间。 


由于相同的原因,多个应用程序还可以同时共享DLL在内存中的同一份拷贝,这样就有效的节省了应用程序所占用的内存资源,减少了频繁的内存交换,从而提高了应用程序的执行效率。 


由于DLL是独立于可执行文件的,因此,如果需要向DLL中增加新的函数或是增强现有函数的功能,只要原有函数的参数和返回值等属性不变,那么,所有使用该DLL的原有应用程序都可以在升级后的DLL的支持下运行,而不需要重新编译。这就极大的方便了应用程序的升级和售后支持。 


DLL除了包括函数的执行代码以外,还可以只包括如图标、位图、字符串和对话框之类的资源,因此可以把应用程序所使用的资源独立出来做成DLL。对于一些常用的资源,把它们做到DLL中后,就可为多个应用程序所共享。 


便于建立多语言的应用程序。我们可以把多语言应用程序中所使用的与语言相关的函数做到DLL中,只要不同语言的应用程序所调用的函数都具有相同的接口,这样就可以通过简单地更换DLL来实现多语言支持。 


然而,使用DLL也有其不足之处。最典型的就是应用程序在运行时必须要有相应的DLL的支持。另外,使用DLL也增大了程序运行的开销,好在这种额外的开销对于大多数应用程序的影响并不明显,我们也只是在某些对运行速度要求苛刻的特殊场合,才不得不考虑这一点。


Visual C++ 5.0支持多种DLL,包括:


非MFC DLL 


静态链接到MFC的常规DLL 


动态链接到MFC的常规DLL 


MFC扩展DLL 

其中非MFC DLL(non-MFC DLL)内部不使用MFC,调用非MFC DLL提供的导出函数的可执行程序可以使用MFC,也可以不使用MFC。一般来说,非MFC DLL的导出函数都使用标准的C接口(standard C interface)。

其余三种DLL的内部都使用了MFC。顾名思义,静态链接到MFC的常规DLL(regular DLL statically linking to MFC)和动态链接到MFC的常规DLL(regular DLL dynamically linking to MFC)的区别在于一个使用的是MFC的静态链接库,而另一个使用的是MFC的DLL。这和一般的MFC应用程序的情况是很类似的。

MFC扩展DLL一般用来提供派生于MFC的可重用的类,以扩展已有的MFC类库的功能。MFC扩展DLL使用MFC的动态链接版本。只有使用MFC动态链接的可执行程序(无论是EXE还是DLL)才能访问MFC扩展DLL。MFC扩展DLL的另一个有用的功能是它可以在应用程序和它所加载的MFC扩展DLL之间传递MFC和MFC派生对象的指针。在其它情况下,这样做是可能导致问题的。


注意: 


只有Visual C++ 5.0的专业版和企业版才支持到MFC的静态链接。 


静态链接到MFC的常规DLL过去的USRDLL有着相同的特性,同样的,MFC扩展DLL和过去的AFXDLL有着相同的特性。在Visual C++ 5.0中已不再使用USRDLL和AFXDLL这两个术语。 


如何选择所应该使用的DLL的类型呢?我们可以从以下几个方面来考虑:


相比使用了MFC的DLL而言,非MFC DLL显得更为短小精悍。因此,如果DLL不需要使用MFC,那么使用非MFC DLL是一个很好的选择,它将显著地节省磁盘和内存空间。同时,无论应用程序是否使用了MFC,都可以调用非MFC DLL中所导出的函数。 


如果需要创建使用了MFC的DLL,并希望MFC和非MFC应用程序都能使用所创建的DLL,那么可以选择的范围包括静态链接到MFC的常规DLL和动态链接到MFC的常规DLL。动态链接到MFC的常规DLL比较短小,因此可以节省磁盘和内存,但是,在分发动态链接到MFC的常规DLL时,必须同时分发MFC的支持DLL,如MFCx0.DLL和MSVCRT.DLL等。而使用静态链接到MFC的常规DLL则不存在这种问题。 


如果希望在DLL中实现从MFC派生的可重用的类,或者是希望在应用程序和DLL之间传递MFC的派生对象时,必须选择MFC扩展DLL。 

第二节 创建和使用动态链接库

本节以非MFC DLL为例来讲解DLL的结构和导出方法,并介绍创建和使用DLL的方法和步骤。

13.2.1 DLL的结构和导出方式

DLL文件和EXE文件都属于可执行文件,不同的是DLL文件包括了一个导出表,导出表中给出了可以从DLL中导出的所有函数的名字。外部可执行程序只能访问包括在DLL的导出表中的函数,DLL中的其它函数是私有的,不能为外部可执行程序所访问。

可以使用Visual C++提供的DUMPBIN实用程序(可以在DevStudio/VC/bin目录下找到这个工具)来查看一个DLL文件的结构。举一个例子,如果需要查看DLL文件msgbox.dll(我们将在本小节的后续内容中创建该DLL)的导出表,可以在命令提示符下键入下面的命令:

>dumpbin /exports msgbox.dll

运行结果如下:

Microsoft (R) COFF Binary File Dumper Version 5.00.7022

Copyright (C) Microsoft Corp 1992-1997. All rights reserved.

Dump of file msgbox.dll

File Type: DLL

Section contains the following Exports for MSGBOX.dll

0 characteristics

351643C3 time date stamp Mon Mar 23 19:13:07 1998

0.00 version

1 ordinal base

1 number of functions

1 number of names

ordinal hint name

1 0 MsgBox (00001000)

Summary

7000 .data

1000 .idata

2000 .rdata

2000 .reloc

17000 .text

由上面的结果得知,msgbox.dll中仅包括了一个导出函数MsgBox()。


注意: 


仅仅知道导出函数的名称并不足以从DLL中导出该函数。若在应用程序中使用显式链接(link explicitly),至少还应该知道导出函数的返回值的类型以及所传递给导出函数的参数的个数、顺序和类型;若使用隐含链接(link implicitly),必须有包括导出函数(或类)的定义的头文件(.H文件)和引入库(import library,.LIB文件),这些文件是由DLL的创建者所提供的。关于显式链接和隐含链接,将在本章的“13.2.2 链接应用程序到DLL”小节中讲述。


从DLL中导出函数有两种方法:


在创建DLL时使用模块定义(module DEFinition,.DEF)文件。 


在定义函数时使用关键字__declspec(dllexport)。 


下面我们通过一个简单的例子来分别说明两种方法的使用。在这个例子中,我们将创建一个只包括一个函数MsgBox()的DLL,函数MsgBox()用来显示一个消息框,它和Win32 API函数MessageBox()的功能是一样,只不过在函数MsgBox()中,不需要指定消息的父窗口,而且可以缺省其它所有的参数。

(1) 使用模块定义文件

模块定义文件是一个文本文件,它包括了一系列的模块语句,这些语句用来描述DLL的各种属性,典型的,模块语句定义了DLL中所导出的函数的名称和顺序值。


在讲解模块定义文件之前,我们先创建一个Win32 Dynamic-Link Library工程。

1. 在Microsoft Developer Studio中选择File菜单下的New命令,在Projects选项卡中选择Win32 Dynamic-Link Library,并为工程取一个名字,如msgbox。单击OK后,Visual C++创建一个Win32 DLL的空白工程,必须手动的将所需要的文件添加到工程中。

2. 单击Project菜单下的Add To Project子菜单下的New命令,在Files选项卡中选择Text File,在File文本框中输入DEF文件名,如msgbox.def。

3. 双击Workspace窗口的FileView选项卡中的msgbox.def节点,在msgbox.def文件中输入下面的内容:

LIBRARY MSGBOX

DESCRIPTION "一个DLL的简单例子"

EXPORTS

MsgBox @1

在DEF文件中的第一条语句必须是LIBRARY语句,该语句表明该DEF文件属于一个DLL,在LIBRARY之后是DLL的名称,这个名称在链接时将放到DLL的引入库中。

EXPORTS语句下列出了DLL的所有导出函数以及它们的顺序值。函数的顺序值不是必须的,在指定导出函数的顺序值时,我们在函数名后跟上一个@符号和一个数字,该数字即导出函数的顺序值。如果在DEF中指定了顺序值,它必须不小于1,且不大于DLL中所有导出函数的数目。

DESCRIPTION语句是可选的,它简单的说明了DLL的用途。

4. 下一步是向工程中添加一个头文件,它定义了DLL中的函数的返回值的类型和参数的个数、顺序和类型。

单击菜单项Project|Add To Project|New...,在Files选项卡下选择C/C++ Header File,在File文本框中指定头文件名,如msgbox.h(可以省略后缀名.h)。

在头文件中输入如下的内容:

#include 

extern "C" int MsgBox(

// 消息框的文本

LPCTSTR lpText="虽然这个例子有一些幼稚,但它工作得非常的好!",

// 消息框的标题

LPCTSTR lpCaption="一个简单的例子",

// 消息框的样式

UINT uType=MB_OK);

请注意函数定义前的关键字extern "C",这是由于我们使用了C++语言来开发DLL,为了使C语言模块能够访问该导出函数,我们应该使用C链接来代替C++链接。否则,C++编译器将使用C++的类型安全命名和调用协议,这在使用C调用该函数时就会遇上问题。在本例中并不需要考虑到这个问题,因为我们在开发DLL和应用程序时都是使用C++,但我们仍然强烈建议使用extern "C",以保证在使用C编写的程序调用该DLL的导出函数不会遇上麻烦,在本章后面的内容中我们还会讨论到这个问题。

5. 下面要做的事是向工程中添加一个C++源文件,在该文件中实现函数MsgBox()。

仿照上面的过程,单击菜单项Project|Add To Project|New...,在Files选项卡下选择C++ Source File,在File文本框中指定源文件名,如msgbox.cpp。

在msgbox.cpp文件中添加如下的代码:

#include "test1.h"

int MsgBox(LPCTSTR lpText,

LPCTSTR lpCaption,

UINT uType)

{

return MessageBox(NULL,lpText,lpCaption,uType);

}

编译该工程,在Debug目录下生成文件msgbox.lib和msgbox.dll。

在“13.2.2 链接应用程序到DLL”小节中将讲述如何使用在本节中所创建的DLL:msgbox.dll。

(2) 使用关键字__declspec(dllexport)

从DLL中导出文件的另一种方法是在定义函数时使用__declspec(dllexport)关键字。这种方法不需要使用DEF文件。

仍使用前面的例子,在工程中删除msgbox.def文件,将msgbox.h文件修改如下:

#include 

extern "C" __declspec(dllexport) int MsgBox(

// 消息框的文本

LPCTSTR lpText="虽然这个例子有一些幼稚,但它工作得非常的好!",

// 消息框的标题

LPCTSTR lpCaption="一个简单的例子",

// 消息框的样式

UINT uType=MB_OK);

msgbox.cpp文件并不需要做任何修改,重新编译该工程,在Debug目录下仍生成两个文件msgbox.lib和msgbox.dll。

在下一小节“13.2.2 链接应用程序到DLL”中讲述了如何在应用程序中使用所创建的DLL:msgbox.dll的导出函数MsgBox()。

使用__declspec(dllexport)从DLL中导出类的语法如下:

class __declspec(dllexport) CDemoClass

{

...

}


注意: 


如果在使用__declspec(dllexport)的同时指定了调用协议关键字,则必须将__declspec(dllexport)关键字放在调用协议关键字的左边。如: 


int __declspec(dllexport) __cdacl MyFunc(); 


在32位版本的Visual C++中,__declspec(dllexport)和__declspec(dllimport)代替了16版本中使用的__export关键字。因此,在将16位的DLL源代码移植到Win32平台时,需要把每一处__export替换为__declsped(dllexport)。 


如何从这两种导出函数的方法中作出选择,可以从下面的几个方面考虑:


如果需要使用导出顺序值(export ordinal value),那么应该使用DEF文件来导出函数。只在使用DEF文件导出函数才能指定导出函数的顺序值。使用顺序值的一个好处是当向DLL中添加新的函数时,只要新的导出函数的顺序值大于原有的导出函数,就没有必要重新链接使用隐含链接的应用程序。相反,如果使用__declspec(dllexport)来导出函数,如果向DLL中添加了新的函数,使用隐含链接的应用程序有可以需要重新编译和链接。 


使用DEF文件来导出函数,可以创建具有NONAME属性的DLL。具有NONAME属性的DLL在导出表中仅包含了导出函数的顺序值,这种类型的DLL在包括有大量的导出函数时,其文件长度要小于通常的DLL。 


使用DEF文件从C++文件导出函数,应该在定义函数时使用extern "C"或者在DEF文件中指定导出函数的decorated name。否则,由于编译器所产生的decorated name是基于特定编译器的,链接到该DLL的应用程序也必须使用创建DLL的同一版本的Visual C++来编译和链接。 


由于使用__declspec(dllexport)关键字导出函数不需要编写DEF文件,因此,如果编写的DLL只供自己使用,使用__declspec(dllexport)较为简单。 

 


注意: 


MFC本身使用了DEF文件从MFCx0.DLL中导出函数和类。 

13.2.2 链接应用程序到DLL

同样,链接应用程序到DLL也有两种方法:


隐含链接 


显式链接 


隐含链接有时又称为静态加载。如果应用程序使用了隐含链接,操作系统在加载应用程序的同时加载应用程序所使用的DLL。显式链接有时又称为动态加载。使用动态加载的应用程序必须在代码中明确的加载所使用的DLL,并使用指针来调用DLL中的导出函数,在使用完毕之后,应用程序必须卸载所使用的DLL。

同一个DLL可以被应用程序隐含链接,也可以被显式链接,这取决于应用程序的目的和实现。

下面我们在分别讲述两种不同的链接方式之后再作对比。

(1) 使用隐含链接

在使用隐含链接除了需要相应的DLL文件外,还必须具备如下的条件:


一个包括导出的函数或C++类的头文件 


一个输入库文件(.LIB文件) 

通常情况下,我们需要从DLL的提供者那里得到上面的文件。输入库文件是在DLL文件被链接时由链接程序生成的。

在“13.2.1 DLL的结构和导出方式”中所创建的DLL:msgbox.dll所对应的头文件msgbox.h如下:

#include 

extern "C" __declspec(dllimport) int MsgBox(

// 消息框的文本

LPCTSTR lpText="虽然这个例子有一些幼稚,但它工作得非常的好!",

// 消息框的标题

LPCTSTR lpCaption="一个简单的例子",

// 消息框的样式

UINT uType=MB_OK);

需要注意的是,这个msgbox.h文件和创建DLL时所使用msgbox.h是不同的,唯一的差别在于,创建DLL时的msgbox.h中使用的是__declspec(dllexport)关键字,而供应用程序所使用的msgbox.h中使用的是__declspec(dllimport)关键字。无论创建DLL时使用的是DEF文件还是__declspec(dllexport)关键字,均可使用__declspec(dllimport)关键字从DLL中引入函数。引入函数时也可以省略__declspec(dllimport)关键字,但是使用它可以使编译器生成效率更高的代码。


注意: 


如果需要引入的是DLL中的公用数据和对象,则必须使用__declspec(dllimport)关键字。 


现在使用Microsoft Developer Studio创建一个Win32 Application工程,命名为tester。向工程中添加一个C++源文件,如tester.cpp。在tester.cpp文件中输入下面的代码:

#include "msgbox.h" // 应将msgbox.h文件拷贝到工程tester的目录下。

int WINAPI WinMain(HINSTANCE hInstance,

HINSTANCE hPrevInstance,

LPSTR lpCmdLine,

int nCmdShow)

{

return MsgBox();

}

在上面的代码中,MsgBox()函数的所有参数都使用了缺省值。 
阅读更多

没有更多推荐了,返回首页