相关概念
DLL
DLL 是一个完整的程序,中文名称为“动态链接库”,DLL中包含的主要有三块内容:
- 全部变量
- 函数接口
- 资源
我们这里说的DLL中包含许多的函数接口,以便供我们来调用,实现我们的软件功能。DLL中有一个函数导出表,其中每一项都是一个函数名称,我们可以通过一定的方式连接这些函数接口,来调用这些函数的功能。DLL中的代码在程序主动调用的时候才会被调入内存(DLL没有自己的内存,它会被分配到调用程序的内存区域中)。
LIB
LIB 被称为导入库文件,其中存放的内容根据动态和静态的区别会有两种不同的内容:
- 在静态库情况下,函数和数据被编译成一个二进制文件(*.LIB),编译器在处理程序代码时,将从静态库中恢复这些函数和数据,并把他们和应用程序中的其他模块组合在一起生成可执行文件,(通俗的说就是,*.LIB文件会被整个编译进可执行文件中),因此发布的时候,就无需发布整个静态库文件,应用程序只有一个单独的exe文件比较干净,但是exe文件可能要大很多。
- 在动态库情况下,有两个文件,一个因入库(*.LIB)一个是DLL文件,此时LIB文件中包含的就不是实际的函数和数据了,而是被DLL文件中导出表中所有导出函数的名称和位置,因此整个LIB文件会很小,相比静态库的LIB文件(这个小的LIB文件在编译的时候还是会被编译进程序体内,因此这种情况发布的程序,也不用发布LIB文件,只需要DLL文件),DLL文件中才真正包含函数和数据,此时DLL文件中的函数和数据并不复制到程序中,程序中存放的则是DLL中所要调用的函数的内存地址,而不是被调用的函数代码。
静态编译
所谓静态编译就是,把你所需要的调用所有外部函数和数据编译成一个LIB文件,然后再把这个文件整个的融合到应用程序中,则这个可执行程序可谓集各方武功捆绑于一身,只有一个exe文件(发布的时候仅仅只需要这个文件就可以完成所有功能),单相对会比较大。
动态编译
所谓动态编译就是,实现某些功能的代码分布在几个dll文件中,发布的时候你要将exe文件和这几个dll文件一起发布。Windows下的程序是最好的例子了,通常的程序只有一个单独的exe文件,其实很多实现某些功能的代码都在系统目录的几个dll文件中,需要的时候再去调用罢了
举个例子🌰
举个例子来说明。假如现在我在做一个项目,我需要用到语音识别功能,我就去往某开发公司要,而他们为了盈利也只是给我了几个用来测试的文件,并且是已经编译好的文件,一种可能会给 my.h、my.lib 两个文件,一种可能会给 my.h、my.lib、my.dll 三个文件。具体的区别就是:第一种给两个文件的需要静态编译,也就是说my.lib会被融合到可执行程序中,发布的时候只有一个exe文件;第二种给三个文件的是需要动态链接,my.lib文件依然会和可执行程序融为一体,发布的时候需要将exe文件和dll文件一起发布。
这里还要说明一下 *.lib 和 *.a 文件,这两个文件都是导入库文件,VC++只能识别 *.lib文件,而DEV-C++ 这两种文件都可以识别使用。
依然举例子来理解,现在假设语音开发公司,只给了我这样两个文件 my.h 与 my.dll ,因为没有 my.lib 文件,那样我在调用其语音功能时会相当痛苦。这里先说说,假如说有 my.lib 文件会发生什么,在my.dll 中有一个add(int a,int b) 用于将两个数相加结果返回,那么在调用文件中,我仅仅只需要引入 my.h 头文件,然后就可以直接调用add()函数得到结果;但是如果没有my.lib文件,将十分的痛苦,我需要调用Win32 API 来实现,需要调用LoadLibrary()与GetProcAddress(), 大体如下面这样调用(这种方式成为显示,另外的一种是常用的隐式):
1 #include <windows.h>
2 #include <stdio.h>
4
5 typedef int (*Fun)(int,int);
//这里声明一个函数指针,typedef 关键字是必须的,好像要显示调用dll中的函数,都需要这样用函数指针给出声明
6
7 int main()
8 {
9 HINSTANCE hDll;
10 Fun Add;
11 hDll=LoadLibrary("myDll.dll");
12 if (hDll==NULL)
13 {
14 printf("%s","failed to load dll!\n");
15 }
16 else
17 {
18 printf("%s","succeeded in loading dll\n");
19 Add=(Fun)GetProcAddress(hDll,"add");
20 if (Add!=NULL)
21 {
22 int i,j;
23 printf("%s","input the first number:");
24 scanf("%d",&i);
25 printf("%s","input the first number:");
26 scanf("%d",&j);
27 printf("sum = %d\n",Add(i,j));
28 }
29 }
30 FreeLibrary(hDll);
31
32 system("pause");
33 return 0;
34 }
确实很痛苦吧,好了,罗嗦了这末多了,还没说假如说没有 *.lib 文件,那么如何从 dll 文件中导出来呢?这里我们需要两个工具,一个是微软提供的 dumpbin.exe(它依赖于另外两个文件:link.exe 与 mspdb60.dll) 另一个是DEV-C++ 中的一个工具,叫 dlltool.exe。
dumpbin.exe可以从dll文件中导出一个 def 文件,注意这个文件只是个半成品,我们还需要手工改一下,后面会有讲解。
dlltool.exe 可以帮助我们用现有的 dll 和刚导出的 def 文件生成一个 .lib/.a 文件。
dumpbin的用法为:dumpbin /exports my.dll > my.def
dlltool的用法为:dlltool -D my.dll -d my.def -l my.lib
//生成 .lib 文件
或者为:dlltool -D my.dll -d my.def -l my.a
// 生成 .a 文件
经过这两步后就可以导出一个lib文件了。
接下来我们看一个用dumpbin.exe工具生成的def文件中的内容(def是一个文本文件):
Microsoft (R) COFF Binary File Dumper Version 6.00.8168
Copyright (C) Microsoft Corp 1992-1998. All rights reserved.
Dump of file myDll.dll
File Type: DLL
Section contains the following exports for myDll.dll
0 characteristics
4C6D24A5 time date stamp Thu Aug 19 20:33:41 2010
0.00 version
1 ordinal base
2 number of functions
2 number of names
ordinal hint RVA name
2 0 000011D0 HelloWorld
1 1 0000120B add
Summary
1000 .bss
1000 .data
1000 .edata
1000 .idata
1000 .rdata
1000 .reloc
1000 .text
其中对我们有用的只有粉红色的那两行,我们将其他内容删除,下面来修改一下剩下的这两行,其中每行最后的字符串为函数名,每行最前面的数字具体我也说不清楚,反正是根据这个数据来识别函数的好像,我就叫他识别数吧。我们将函数名放在最前面,然后在后面加上一个 “@” 符号,再在最后缀上 识别数,形成如下形式:(在def文件中,分号为注释符号)
LIBRARY my ;这个关键字是必须的,它告诉链接器(linker)如何命名你的DLL
EXPORTS ;这个关键字也是必须的,这个部分使得该函数可以被其它应用程序访问到并且它创建一个导入库
HelloWorld @ 2
add @ 1
OK,我们现在已经有了 my.h、my.lib、my.dll 三个文件,(我们将所有文件拷贝到一个文件夹下,因为方便...)下面开始用程序来调用里面的功能函数,激动人心的时刻到了,O(∩_∩)O~
- 首先我们需要用DEV-C++新建一个控制台程序(文件->新建->工程,选中其中的Console Application,然后选择右下角的C工程),这里还有一点要十分注意,由于我们刚才选的是C工程,所以这里只能用C来写,用C++代码来写会报错的。
- 在程序中引入头文件(#include "my.h" 在我时间过程中,用C写的dll可以完全不用包含这个头文件,而用C++写的dll则要包含,不知道为什么,为了保险,我们都包含头文件),然后再为工程添加 导入库文件 my.lib ,点击菜单栏的中的“工程”菜单->工程属性->参数选项卡,点击右下角的“加入库或者对象”按钮,如图:
- 将 my.dll 拷贝到程序的输出目录中,我们的准备工作就可以了,直接就可以调用dll中的函数了,如:
#include <stdio.h>
2 #include <stdlib.h>
3 #include "my.h"
4 int main(int argc, char *argv[])
5 {
6 printf("5 + 6 = %d",add(5,6));
//这里调用dll中的 add() 获得5和6的和,会成功的哦~~~
7 system("PAUSE");
8 return 0;
9 }
上面是一个C版的dll文件,C++版的dll文件也没什么不一样的地方,唯一的区别就是选择C++接口后,编译器会生成一个类,仔细查看代码你会发现在类名的左侧有 DLLIMPORT 宏修饰,这里的意思是:这个类中所有的东东都将被导出。
最重要的一点是,如果你在CPP代码中调用C接口的dll文件时候,在引入其头文件的时候,一定要加上 extern "C" {} 否则会链接出错。下面是一个标准示例:
extern "C"
{
#include "my.h"
}
最后呢,说一下,如何用DEV-C++编写dll文件。(这里以C接口为例,C++一样的)
文件->新建->工程,选择“ DLL选项”,然后选右下角的“C工程”,确定后,编译器为我们生成了两个文件(dll.h、dllmain.c),我们看一下dll.h里面的代码:
#ifndef _DLL_H_
#define _DLL_H_
#if BUILDING_DLL
# define DLLIMPORT __declspec (dllexport)
#else /* Not BUILDING_DLL */
# define DLLIMPORT __declspec (dllimport)
#endif /* Not BUILDING_DLL */
DLLIMPORT void HelloWorld (void);
#endif /* _DLL_H_ */
代码中的 __declspec(dllexport) 关键字从 DLL 导出数据、函数、类或类成员函数,这里为了代码的可读性,用了一个宏 DLLIMPORT 来代替,通俗的说用该宏修饰的数据、函数、类或类成员函数才能被外界调用;另外在dllmain.c中,对应函数的实现前也要加上 DLLIMPORT 宏修饰,必须的。
另外,看到网上一些人的dll头文件写的比较麻烦,比如这样:
extern "C" __declspec(dllexport) int maxfun(int,int);
//函数
对应的实现文件这样写:
extern "C" __declspec(dllexport)int maxfun(int x,int y)
{
return x+y;
}
我觉得这样是相当麻烦的,都是为了程序的兼容性,我们可以用更好的方法,比如说:在头文件的用extern "C"关键字包括住全部的函数声明;在实现文件中,同样用该关键字包括住所有的函数定义,比如:
extern "C"
{
DLLIMPORT int add(int a,int b);
DLLIMPORT int Fun(int a);
....
}
实现文件一样:
extern "C"
{
DLLIMPORT int add(int a,int b) {return a+b;}
DLLIMPORT int Fun(int a) {return a*a*a;}
.....
}
我觉得最简单的方法就是用我前面说的,如果在使用C版接口的dll文件时候,在引入其头文件的外面加上exter "C" 修饰最为方便。
Loading Comments...