首页 > 代码库 > DLL的前世今因

DLL的前世今因

网上的有关DLL的文章有很多,也很杂。这里我花了一段时间,查了资料,把各种版本自己动手试了试。以下是我的学习笔记,希望对大家有所帮助。

DLL:动态链接库。

动态链接库就是一种别人已经写好的代码(一般是函数或类),并且已经编译和链接好了,我们只是需要按照一定的规则就可以使用这些代码。

并且动态链接库是可以进行跨语言的。就是说C的DLL可以在Java中使用。


DLL是与exe分开的,当exe执行的时候才去找DLL中函数,而普通的lib文件是链接的时候就已经加载到exe中的。

动态链接库 (DLL) 是作为共享函数库的可执行文件。动态链接提供了一种方法,使进程可以调用不属于其可执行代码的函数。函数的可执行代码位于一个DLL 中,该 DLL 包含一个或多个已被编译、链接并与使用它们的进程分开存储的函数。DLL还有助于共享数据和资源。多个应用程序可同时访问内存中单个 DLL 副本的内容。

动态链接与静态链接的不同之处在于:动态链接允许可执行模块(.dll 文件或 .exe 文件)仅包含在运行时定位 DLL 函数的可执行代码所需的信息。在静态链接中,链接器从静态链接库获取所有被引用的函数,并将库同代码一起放到可执行文件中。

 

Lib文件是已经编译好的,但没有链接,所以程序链接的时候就会去找lib

Dll文件是已经编译好,且链接好的,所以程序编译和链接的时候都和它没关系,只有执行的时候才去找他。

 

 

应用程序和 DLL 之间的区别

应用程序可有多个同时在系统上运行的实例,而 DLL只能有一个实例。

应用程序可以拥有堆栈、共用内存、文件句柄、消息队列这样的事物,而 DLL不能。

 

 

 

使用 DLL 的优点

1节省内存和减少交换操作。很多进程可以同时使用一个 DLL,在内存中共享该 DLL的一个副本。相反,对于每个用静态链接库生成的应用程序,Windows必须在内存中加载库代码的一个副本。

2节省磁盘空间。许多应用程序可在磁盘上共享 DLL的一个副本。相反,每个用静态链接库生成的应用程序均具有作为单独的副本链接到其可执行图像中的库代码。

3升级到 DLL更为容易。当 DLL 中的函数发生更改时,只要函数的参数和返回值没有更改,就不需重新编译或重新链接使用它们的应用程序。相反,静态链接的对象代码要求在函数更改时重新链接应用程序。

4提供售后支持。例如,可修改显示器驱动程序 DLL以支持当初交付应用程序时不可用的显示器。

5DLL可以被其他DLL调用,而lib库不可以被其他lib调用。

 

 

 

生成DLL文件

这里由于我们是在VS中生成的,所以可以选择生成C语言的DLL、或生成C++DLL这里我们是生成C语言的DLL,然后在C++中使用。

我们首先new project->C++->Win 32 Console->DLL-文件名:MYdll

这里我们可以得到MYdll.cpp文件了。(Dllmain.cpp是入口函数不用管)

这里由于后面使用时可能会用到.h文件,所以我们new一个头文件,文件名:MYhead.h

3种方法:

1. 使用__declspec(dllexport) 关键字生成DLL

1. MYhead.h文件中声明要导出函数

若要导出函数,__declspec(dllexport) 关键字必须出现在调用约定关键字的左边

若要导出类中的所有公共数据成员和成员函数,关键字必须出现在类名的左边。

例如:

//MYhead.h

extern "C" _declspec(dllexport) int Max(int a, intb);

//extern “C”出现的目的是由于我们此时在VS中,而此时默认的是用C++来编译的,而我们想要的是C语言的DLL,所以要加。如果你想得到C++DLL,这里就不需要加extern”C”了。

2. MYdll.cpp文件中把函数实现

//MYdll.cpp

#include”MYhead.h”

int Max(int a, int b)

{

return (a>b)? a:b;

}

3. 然后buildsolution就会在debug中出现libdll文件了。

2. 使用def文件生成DLL

1. MYhead.h文件中声明要导出函数

例如:

//MYhead.h

extern "C" int Max(int a, int b);

//extern “C”的原因上面已经说过。

2. MYdll.cpp文件中把函数实现

//MYdll.cpp

#include”MYhead.h”

int Max(int a, int b)

{

return (a>b)? a:b;

}

3. new一个def文件,名字随便。在此文件中写入下列语句:

LIBRARY MYdll(注意,这里是实现函数的文件名,不能是声明文件的文件名。)

EXPORTS

Max(这里是要导出的函数名,如果有多个,换行接着就可以了)

4. 然后buildsolution就会在debug中出现libdll文件了。

3. 使用#pragma comment(linker,”/export:_functionname”)

1. MYhead.h文件中声明要导出函数

例如:

//MYhead.h

#pragma comment(linker,”/export:_Max”)

//注意:这里是_Max,因为/export:后面的必须是函数编译之后的函数名,而C语言编译后的函数名就是在原函数名前加一个下划线。

注意:这种方法有个弊端,我们必须要预先知道函数编译后的函数名,C语言还可以表示,但是C++就很难表示了。所以这种方法限于生成C语言的DLL

extern "C" int Max(int a, int b);

//extern “C”的原因上面已经说过。

2. MYdll.cpp文件中把函数实现

//MYdll.cpp

#include”MYhead.h”

int Max(int a, int b)

{

return (a>b)? a:b;

}

3. 然后buildsolution就会在debug中出现libdll文件了。

 

注:这里为何生成了lib文件?

这里的lib文件是DLL文件中函数名称和地址,如果是隐式链接的话,链接的时候程序会链接DLL的导入库文件(就是这个lib文件)。在程序执行时,程序才会根据导入库文件中的函数地址去到DLL中寻找函数体。

DLL<-----àLIB<-----àEXE

注:DLL内部函数的调用是不用加什么_declspec(dllexport)的,想一想DLL的原理就知道原因了。

 

链接DLL的方法(如何使用DLL)

注:编译时是和库文件没有关系的

 

有两种类型的链接:隐式链接和显式链接。(隐式链接要把生成DLL的程序会产生DLL文件和导入文件lib(有时还需要.h文件)放到当前工程目录下,显示链接只需要DDL文件就可以了)

隐式链接:

隐式链接就是在程序开始执行时就将DLL文件加载到应用程序当中。为隐式链接到 DLL,可执行文件必须从 DLL的提供程序获取下列各项:

1包含导出函数和/ C++类的声明的头文件(.h文件)使用__declspec(dllimport)

将函数的定义导入进来 

2要链接的导入库(.LIB files)。(生成 DLL时链接器创建导入库。)

3实际的 DLL.dll文件)。

 例如:

// TestDll.h

#pragma comment(lib,"MyDll.lib")

(!!这一步开发人员也可以不使用#pragma comment(lib"MyDll.lib")语句,而直接在工程的Setting->属性->Linker->Input->Additional Dependencies最后加MYDll.lib;注意要加”;”)

extern "C" _declspec(dllimport)int Min(int a,int b); !!如果要导出的是变量:则

extern“C” extern _declspec(dllimport) int a;因为只有加上extern,这才是声明。

这行语句也可以用

extern“ C” {

#include”Mydll.h”

}

来代替,效果是一样的。(注意要把MyDll.h拷到此工程下).推荐这样使用。

这里我们是使用C语言生成的DLL,这里我们要用C++来使用,所以要加extern”C”.如果你使用的是用C++生成的DLL,这里就不需要extern “C”了。

<span style="font-size:18px;">//TestDll.cpp
#include<iostream>
#include<Windows.h>
#include"Dlltest.h"
void main()
{int a;
a=Min(8,10)
cout<<a<<endl;
}</span>


 

/////////////////////////////////////////////////////////////

隐式链接体会:

1. Dll和lib不是都已经有了吗,为何还需要#include<.h>或dllimport。

因为dll需要静态导入,所以需要lib导入库,由于lib导入库中的函数名都是c编译好的,函数名都是_funcname形式的,所以需要#include<..h>或dllimport函数名,这样编译时才会生成函数名类似_funcname形式的。然后链接时才会正确链接lib导入库。

 

2. 为何#include头文件中要加extern “C”或者_declspec(dllimport)前要加extern “C”。

因为lib导入库是用C语言编译好的,函数名形如_funcname.如果用C++来编译头文件或_declspec(dllimport)后面的函数名,由于C++支持多态,所以它编译函数名时要把函数的参数个数、参数类型一起柔和在一起,则函数名会是_func_int_float类似的东西。而这样的函数名显示与lib导入库的函数名匹配不了的。

 

//////////////////////////////////////////////////////////////

 

 

显式链接:

显式链接是应用程序在执行过程中随时可以加载DLL文件,也可以随时卸载DLL文件,这是隐式链接所无法作到的,所以显式链接具有更好的灵活性,对于解释性语言更为合适。

在显式链接下,应用程序必须进行函数调用以在运行时显式加载 DLL。为显式链接到 DLL,应用程序必须:

1调用 LoadLibrary(或相似的函数)以加载 DLL 和获取模块句柄。

2调用 GetProcAddress,以获取指向应用程序要调用的每个导出函数的函数指针。由于应用程序是通过指针调用 DLL 的函数,编译器不生成外部引用,故无需与导入库链接。

3使用完 DLL后调用 FreeLibrary

<span style="font-size:18px;">#include<iostream>
#include<Windows.h>
using namespace std;
void main(void)
{
typedef int(*pMax)(int a,int b);
HINSTANCE hDLL;
pMax Max;
hDLL=LoadLibrary("MyDll.dll");//加载动态链接库MyDll.dll文件;
Max=(pMax)GetProcAddress(hDLL,"Max");
int a=Max(5,8);
cout<<a<<endl;
FreeLibrary(hDLL);//卸载MyDll.dll文件;
System(“pause”);
}</span>


 

!!!!最后这里给出一个非常有用的命令

查看lib文件、dll文件,使用命令行:

>dumpbin /exports xxx.lib/xxx.dll

DLL的前世今因