VC++制作DLL详解

1.   
DLL的基本概念

应用程序(exe)要引用目标代码(.obj)外部的函数时,有两种实现途径——静态链接和动态链接。

  1.   
静态链接

链接程序搜索对应的库文件(.lib),然后将这个对象模块拷贝到应用程序(.exe)中来。Windows之所不使用静态链接库,是因为很多基础库被很多应用程序使用。如果每个应用程序一份拷贝,将带来内存的极大浪费。

  2.   
动态链接

链接程序搜索到对应的库文件(.lib),然后根据函数名得到对应的函数入口地址,即可进行编译链接。直到真正运行的时候,应用程序才会从lib文件中记录的DLL名字去搜索同名的DLL,然后将DLL的执行代码内存映射到exe中来。动态链接库的好处是多个应用程序可以共用一份DLL的代码段内存。但是数据段则是每个调用进程一份拷贝。

2.    静态链接库

静态链接库的使用比较简单,一般使用如下方式创建。

然后就像普通工程一样,添加头文件的声明以及源文件的实现。

编译该工程就可以得到StaticLib.lib文件了。

调用者调用.lib库也非常简单,只需要包含头文件声明以及指明.lib库路径即可。如:

#include "..\StaticLib\StaticLib.h"

#pragma comment (lib, "..\\Lib\\staticlib.lib")

或者在Configuration Properties\Liker\Input\Additional
Dependencies中指明.lib库路径。

3.    动态链接库

Visual C++支持三种DLL,它们分别是Non-MFC DLL(非MFC动态库)、MFC Regular
DLL(MFC规则DLL)、MFC Extension DLL(MFC扩展DLL)。他们之间的区别简单概括如下:

非MFC动态库:即Win32DLL,不采用MFC库函数,其导出函数为标准的C接口,能被非MFC和MFC编写的应用程序所调用。

MFC规则DLL:包含一个继承自CWinApp的类,但其无消息循环,可以使用MFC,但是接口不能为MFC。

MFC扩展DLL:采用MFC的动态链接版本创建,它只能被用MFC类库所编写的应用程序所调用。

  1.   
MFC动态库 
 

创建Win32DLL

DLL生成向导提供一些简单的示例,使得建立Win32DLL变得更简单。

?





1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

// The following ifdef block is the standard way of creating macros which make exporting  

// from a DLL simpler. All files within this DLL are compiled with the WIN32DLL_EXPORTS 

// symbol defined on the command line. this symbol should not be defined on any project 

// that uses this DLL. This way any other project whose source files include this file see  

// WIN32DLL_API functions as being imported from a DLL, whereas this DLL sees symbols 

// defined with this macro as being exported. 

#ifdef WIN32DLL_EXPORTS 

#define WIN32DLL_API __declspec(dllexport) 

#else 

#define WIN32DLL_API __declspec(dllimport) 

#endif 

  

// This class is exported from the Win32DLL.dll 

class
WIN32DLL_API CWin32DLL { 

public

    CWin32DLL(void); 

    // TODO: add your methods here. 

    int
Add(int
x, int
y); 

}; 

  

extern
WIN32DLL_API int
nWin32DLL; 

  

extern
“C” WIN32DLL_API int
fnWin32DLL(void);

调用程序有两种方式来调用DLL。

    1.    
隐式链接到DLL   

需要完成3步,头文件、.lib文件和DLL。具体实现如下:

?





1

2

3

#include "..\StaticLib\StaticLib.h" 

#pragma comment(lib, "..\\Lib\\staticlib.lib")

或者在Configuration Properties\Liker\Input\Additional
Dependencies中指明.lib库路径。

DLL的搜索路径见文末.

    2.    
显式链接到DLL

首先LoadLibary指定的DLL,然后GetProcAddress得到指定函数的入口指针,并且通过函数入口指针来访问DLL的函数,最后通过FreeLibrary制裁DLL。


1 typedef int (*PADDFUN)(void);
2
3 HINSTANCE hModule = LoadLibrary("Win32DLL.dll");
4
5 PADDFUN pAddFun = (PADDFUN)GetProcAddress(hModule, "GetValue");
6
7 pAddFun = (PADDFUN)GetProcAddress(hModule, MAKEINTRESOURCE(5));
8
9 FreeLibrary(hModule);

如果要保证导出的函数名是不带修饰的,一定要将指定函数为C编译器编译。否则函数名需要以被修饰过的以“?”开始的函数名来获取函数的入口指针。上图为Dependency
Walker查看到的。

除了直接以函数名获取入口地址外,还可以用索引获取函数入口地址。GetProcAddress获取的是入口地址,所以除了可以获取函数的入口地址,同样可以获取变量的地址。

  2.   
MFC规则DLL

MFC规则的DLL有两种,一种链接MFC动态库的,一种是链接MFC静态库的。选择MFC动态库还是静态库与调用者有关系。因为调用者必须与DLL链接MFC库一致,否则会导致库调用的冲突。如果不是追求追求生成的exe和DLL占较小的空间,推荐使用MFC静态库。

MFC规则DLL的导出基本上同Win32DLL一样,同样不允许导出继承自MFC库的类。不同点主要体现在MFC规则DLL中可以使用MFC库,其实WIN32DLL如果包含了MFC头文件以及链接库,也是可以使用MFC库的。

    1.    
链接MFC动态库

链接MFC动态库资源的切换。这一点需要注意,并且VC默认生成的代码中也用大篇幅的注释提示了,并且也给出了如下基本的解释。

?





1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

//TODO: If this DLL is dynamically linked against the MFC DLLs,

//        any functions exported from this DLL which call into

//        MFC must have the AFX_MANAGE_STATE macro added at the

//        very beginning of the function.

//

//        For example:

//

//        extern "C" BOOL PASCAL EXPORT ExportedFunction()

//        {

//            AFX_MANAGE_STATE(AfxGetStaticModuleState());

//            // normal function body here

//        }

//

//        It is very important that this macro appear in each

//        function, prior to any calls into MFC.  This means that

//        it must appear as the first statement within the

//        function, even before any object variable declarations

//        as their constructors may generate calls into the MFC

//        DLL.

//

//        Please see MFC Technical Notes 33 and 58 for additional

//        details.

  

    2.    
链接MFC静态库

链接MFC动态库基本上和链接MFC静态库除了上面介绍的不同,导出和添加文件之类的完全一样。所以下面重点讲解链接MFC静态库。

DLL导出变量、函数以及类有两种方法,前面使用的都是通过关键字来导出。另外还有一种方法,即通过模块定义(.def)文件来导出。

我们来看一下模块定义(.def)文件的基本格式:

; MFCDLL.def : Declares the module parameters for the
DLL.

LIBRARY     
"MFCDLL"

EXPORTS

; Explicit exports can go here

ShowDlg @2

nDllValue DATA

注释是通过;来完成的。

关键字LIBRARY,描述DLL的名字,并将此信息写入记录DLL信息的.lib文件中。所以如果在Linker\Output
File中修改了生成的DLL的名字,注意也一定要与LIBRARY中描述的一致。当然也可以直接注释掉LIBRARY,这样生成的DLL信息就直接与Linker\Output
File中指定的名字相同。另外LIBARAY后面描述的名字,可以加引号,也可以不加引号。ShowDlg,这个是需要导出的函数名。@2,这里的2是描述方法的地址索引,可以修改,也可以不使用,系统会生成默认的。其实不仅仅函数有,变量也有。

如果同时使用了.def和__declspec(dllexport)导出,编译器会优先使用.def文件的导出。.def文件的导出默认是C编译的,即和extern
“c” __declspec(dllexport)的导出效果一样。

导入函数的方法和前面使用的一样。下面只说一下导入变量的方法。

  1. 直接通过关键字__declspec(dllimport) int nDLLValue;

  2. int* pnDLLValue = (int*)GetProcAddress(hModule, "nDllValue");

pnDLLValue = (int*)GetProcAddress(hModule,
MAKEINTRESOURCE(3));

提供按序号导入的原因是这样导入的速度更快,不用去按名字比对查找。

这里只介绍了.def文件导出导入的常用的一些方法,但是基本上够用。如果想更深入的了解.def文件还涉及到很多知识点,可以参考:

http://blog.csdn.net/henry000/article/details/6852521

http://msdn.microsoft.com/zh-cn/library/28d6s79h.aspx

http://msdn.microsoft.com/zh-cn/library/54xsd65y.aspx

http://msdn.microsoft.com/zh-cn/library/d91k01sh.aspx

  3.   
MFC扩展DLL

MFC 扩展 DLL 是通常实现从现有 Microsoft 基础类库类派生的可重用类的 DLL。

MFC 扩展 DLL 具有下列功能和要求:

  • 客户端可执行文件必须是用定义的 _AFXDLL 编译的 MFC 应用程序。

  • 扩展 DLL 也可由动态链接到 MFC 的规则 DLL 使用。

  • 扩展 DLL
    应该用定义的 _AFXEXT 编译。 这将强制同时定义 _AFXDLL,并确保从
    MFC 头文件中拉入正确的声明。 它也确保了在生成 DLL
    时将 AFX_EXT_CLASS 定义为__declspec(dllexport),这在使用此宏声明扩展
    DLL 中的类时是必要的。

  • 扩展 DLL 不应实例化从 CWinApp 派生的类,而应依赖客户端应用程序(或
    DLL)提供此对象。

  • 但扩展 DLL 应提供 DllMain 函数,并在那里执行任何必需的初始化。

扩展 DLL 是使用 MFC 动态链接库版本(也称作共享 MFC 版本)生成的。 只有用共享 MFC 版本生成的
MFC 可执行文件(应用程序或规则 DLL)才能使用扩展 DLL。 客户端应用程序和扩展 DLL 必须使用相同版本的
MFCx0.dll。 使用扩展 DLL,可以从 MFC 派生新的自定义类,然后将此“扩展”版本的 MFC 提供给调用 DLL 的应用程序。

扩展 DLL 也可用于在应用程序和 DLL 之间传递 MFC
派生的对象。 与已传递的对象关联的成员函数存在于创建对象所在的模块中。 由于在使用 MFC 的共享 DLL
版本时正确导出了这些函数,因此可以在应用程序和它加载的扩展 DLL 之间随意传递 MFC 或 MFC 派生的对象指针。

客户端必须定义_AFXDLL 编译,其实就是说客户端必须使用MFC动态库,即共享MFC库。另外,扩展DLL中显示对话框,和动态链接MFC的DLL一样需要进行资源的切换,只是两个DLL的DllMain函数不同,导致切换资源的方法不同。扩展DLL的切换方法。MFC扩展DLL的导入导出,基本上和静态链接MFC的DLL一样。

HINSTANCE oldHInst =
AfxGetResourceHandle();

HINSTANCE hInst = LoadLibrary("ExDll.dll");

AfxSetResourceHandle(hInst);

CMyDlg dlg;

dlg.DoModal();

AfxSetResourceHandle(oldHInst);

另外,还可以使用构造函数、析构函数来自动完成资源的切换,详见示例代码。

MFC扩展DLL的使用比较复杂,尤其是涉及资源导出之类的,所以如果不是必需,尽量少用FMC扩展DLL,能够用MFC常规DLL代表的尽量代替。

想详细了解扩展DLL的请参考:

http://msdn.microsoft.com/zh-cn/library/1btd5ea3.aspx

http://msdn.microsoft.com/zh-cn/library/h5f7ck28(VS.80).aspx

   4.  
纯资源
DLL

一个纯资源 DLL 是一个 DLL,它包含资源如图标、 位图、 字符串和对话框。 使用一个纯资源 DLL
是共享一组相同的多个程序之间的资源的好办法。 它也是一个好的方法,以提供资源被针对多种语言进行本地化的应用程序。

要创建纯资源 DLL,请创建一个新的 Win32 DLL (非 MFC) 项目,并将资源添加到项目中。

  • 选择中的 Win32 项目新项目对话框中,在 Win32 项目向导中指定 DLL 的项目类型。

  • 为 DLL 创建新资源脚本包含资源 (如字符串或菜单) 并保存.rc 文件。

  • 项目 菜单上,单击 添加现有项,然后将新的.rc 文件插入到该项目。

  • 指定 /NOENTRY 链接器选项。
    / NOENTRY 防止链接器将 _main 的参考链接到 DLL ; 若要创建纯资源 DLL,必须使用此选项。

  • 生成 DLL。

使用纯资源 DLL 的应用程序应调用 LoadLibrary 到显式链接到
DLL
。 若要访问的资源,调用泛型函数 FindResource 和 LoadResource,其中从事任何种类的资源,或调用下面的特定资源的函数之一:

  • FormatMessage

  • LoadAccelerators

  • LoadBitmap

  • LoadCursor

  • LoadIcon

  • LoadMenu

  • LoadString

应用程序应调用完成时使用的资源。

可以调用与资源切换相同的方式完成资源的切换。

?





1

2

3

4

5

6

7

8

9

10

11

HINSTANCE oldHInst = AfxGetResourceHandle();

HINSTANCE hInst = LoadLibrary("ExDll.dll");

AfxSetResourceHandle(hInst);

CMyDlg dlg;

dlg.DoModal();

AfxSetResourceHandle(oldHInst);

  

也可以调用指定资源函数获取指定资源的句柄。

  注意项

  1.   
DLL搜索路径

  • 当前进程的可执行模块所在的目录。

  • 当前目录。

  • Windows
    系统目录。 GetSystemDirectory 函数检索此目录的路径。

  • Windows
    目录。 GetWindowsDirectory 函数检索此目录的路径。

  • PATH 环境变量中列出的目录。

上面是EXE默认的搜索DLL路径。但是有有时我们希望更改DLL存放的目录,那么就需要在搜索路径上做一些修改了。

如果去改上的模块目录、当前目录,会导致程序中使用目录上的不便,所以不建议修改上面这些目录。Windows其实提供了修改DLL搜索路径的API。

void  SetDllDirectory( LPCTSTR
lpPathName);

调用这个函数之后,DLL的搜索路径改变为:

  • The directory from which the application loaded.

  • The directory specified by the lpPathName parameter.

  • The system directory. Use the GetSystemDirectory function
    to get the path of this directory. The name of this directory is
    System32.

  • The 16-bit system directory. There is no function that obtains the path of
    this directory, but it is searched. The name of this directory is System.

  • The Windows directory. Use the GetWindowsDirectory
    function to get the path of this directory.

  • The directories that are listed in the PATH environment variable.

HMODULE LoadLibraryEx(
LPCTSRlpFileName,HANDLEhFile,
DWORD dwFlags);

以参数dwFlags为
_WITH_ALTERED_SEARCH_PATH调用上面的函数时,DLL搜索路径如下:

  • The directory specified by the lpFileName
    path. In other words, the directory that the specified executable module is
    in.

  • The current directory.

  • The system directory. Use the GetSystemDirectory function to get the path
    of this directory.

  • The 16-bit system directory. There is no function that obtains the path of
    this directory, but it is searched.

Windows Me/98/95:  This directory does not exist.

  • The Windows directory. Use the GetWindowsDirectory function to get the
    path of this directory.

  • The directories that are listed in the PATH environment variable.

通过上面两个修改DLL搜索路径的API,我们可以发现,LoadLibraryEx会将一个新添的搜索路径放在其他所有搜索路径之前。而SetDllDirectory则将搜索路径放在第2位。通过实验也发现,LoadLibraryEx的DLL搜索时间明显少于SetDllDirectory设置之后的DLL搜索时间。所以建议使用LoadLibraryEx修改DLL搜索路径。

  2.   
DLL中的静态变量

如果是在一个进程中,几个模块共同调用同一个DLL,那么DLL中的静态变量是全局共用的。

  3.   
关于释放DLL内存的问题

Windows允许一个进程有多个Head。我们知道每个DLL会有自己的数据区,也就是说每个DLL都会有自己的Head。Windows有一个规则,即谁的Head谁负责,也就是说每个DLL必须得自己负责Head上的内存申请以及释放。

简单的内存申请释放很容易发现,但是有一些vector、CString、CStringArray等,它们的内部实现其实都是有动态申请内存的,所以如果导出函数的参数涉及到这些类型时,也会导致内存释放的问题。

  4.   
ClientDLL在设置上必须一致

    1. 是否动态链接到MFC库。

    2. 运行时库也必须一致。MD/MDd;MT/MTd。

    3. 字符集是否一致,是否Unicode之类的必须一致。

    4. 导入导出的声明必须一致。

    5. 须相同的修饰名extern “C”;

    6. 必须相同的调用方式,_cdecl,_stdcall,_fastcall必须导入导出时一致。

以上只列举了一些容易出现的错误。总之如果在Client端链接出错时,就应该考虑Client与DLL的一致性问题了。

  5.   
动态链接到MFC共享DLL
  

If this DLL is dynamically linked against the MFC DLLs,any
functions exported from this DLL which call into MFC must have the
AFX_MANAGE_STATE macro added at the very beginning of the function.

即只要是动态链接到MFC共享DLL的,必须使用资源切换。

源码下载

时间: 2024-10-07 16:17:25

VC++制作DLL详解的相关文章

DLL详解及Denpendcy Walker的使用

下面的文章被N次转载,为了尊重原作,\(^o^)/~,贴出最早发布这篇文章的地址及作者.   动态链接库 Windows的活动大陆 2006-07-26 09:21  作者:狂ρκ来源:电脑爱好者 在Windows世界中,有无数块活动的大陆,它们都有一个共同的名字--动态链接库.现在就让我们走进这些神奇的活动大陆,找出它们隐藏已久的秘密吧! 初窥门径:Windows的基石 随便打开一个系统目录,一眼望去就能看到很多扩展名DLL的文件,这些就是经常说的"动态链接库",DLL是Dynami

VC++ CTime Format 详解

参考链接: VC++中CTime类Format参数详解 CTime/COleDateTime::Format方法的使用 http://stat.ethz.ch/R-manual/R-devel/library/base/html/strptime.html http://www.geezer.org/sw/mvform/doc/strftime.txt CTime::Format主要用来格式化日期和时间. 举例: CTime ctNow=CTime::GetCurrentTime(); CStr

产品流程图的制作方法详解

一个产品设计之初,必先从流程图做起,流程图可以用来表达产品各式各样的流程,今天我们就来聊聊Axure里面流程图的做法: 流程图组件 在元件区面板上点击下拉选择流程图,既可看到流程图中需要用的的各种组件的形状,代表不同的流程步骤及含义. 流程图组件也可以直接从组件选择面板中拖拉出来,然后通过工具列或快捷菜单来编辑样式与属性,如果要改变流程形状的话,可以按鼠标右键并选择“编辑流程形状”——子选单中的项目来设置. 若要把两个形状连接的话,需要先从软件左上角选择连接器模式,然后在形状的连点部位用鼠标拖拽

NGUI制作ScrollView详解

教程说明 版本:NUGI 3.5.1 1.创建NGUI对象 2.创建ScrollView对象 3.为ScrollView添加UIGrid控件(要点1后面会说明) 4.给ScrollView添加滚动包含的对象,避免太复杂没法把握制作原理,只用简单的Sprite 5.依次给sprite添加触控响应(要点2,不添加就等着没效果) 和 拖动操作方式(要点3) 6.复制多个sprite,这时你发现这些都叠加在一起,这时UIGrid就发挥作用了,你只管运行看效果就知道,这些sprite会自动排版,强大的NG

jQuery插件制作方法详解

jQuery插件制作方法详解 jquery插件给我的感觉清一色的清洁,简单.如Jtip,要使用它的功能,只需要在你的元素的class上加 上Jtip,并引入jtip.js及其样式即可以了.其他事情插件全包.我喜欢jquery的一个重要原因是发现她已经有了很多很好,很精彩的插件.写一 个自己的jQuery插件是非常容易的,如果你按照下面的原则来做,可以让其他人也容易地结合使用你的插件. jquery插件给我的感觉清一色的清洁,简单.如Jtip,要使用它的功能,只需要在你的元素的class上加上Jt

Android OTA升级包制作脚本详解(二,解压缩)

第一步:解压缩(ota_from_target_files) print "unzipping target target-files..." OPTIONS.input_tmp, input_zip = common.UnzipTemp(args[0]) 上面的代码是开始进行解压缩的入口 def UnzipTemp(filename, pattern=None): """Unzip the given archive into a temporary d

Android OTA升级包制作脚本详解(四,生成升级脚本updater-script)

updater-script简介: updater-script是我们升级时所具体使用到的脚本文件,它主要用以控制升级流程的主要逻辑.具体位置位于更新包中/META-INFO/com/google/android/目录下,在我们制作升级包的时候产生. updater-script生成: 那么升级脚本updater-script是如何产生的呢,我们来看ota_from_target_file中的一条语句,这个之前有过介绍. #在/build/tools/releasetools/目录下的模块edi

Android OTA升级包制作脚本详解(一,参数解析)

写在前面: "build/tools/releasetools/ota_from_target_files  -u lk.bin  -n target.zip update.zip"这是制作整包的命令,很显然这里支持lk升级.本系列博文主要对该命令的执行流程及原理进行一个系统的分析,涉及到/build/tools/releasetools/目录下多个模块如ota_from_target_files.common等.由于本人对python了解粗浅,文中所涉及到的python语法大都做了注

Android OTA升级包制作脚本详解(五,升级脚本updater-script的执行<1>)

写在前面: 首先当我们执行升级脚本updater-script的时候,就表示我们已经进入了升级安装状态.那么在我们就从实际的安装作为入口开始分析.也就是说我们从install.cpp中的install_package函数开始一步步来分析. 这里主要分析与脚本相关的部分,其他的请参考这位朋友的博文http://blog.chinaunix.net/uid-22028566-id-3533856.html,我也很受启发.这里也借用一张图来帮助流程上的分析. 下面是调用的流程:install_pack