为什么需要dll
代码复用是提高软件开发效率的重要途径。一般而言,只要某部分代码具有通用性,就可将它构造成相对独立的功能模块并在之后的项目中重复使用。比较常见的例子是各种应用程序框架, 如ATL、MFC等,它们都以源代码的形式发布。由于这种复用是“源码级别”的,源代码完全暴露给了程序员,因而称之为“白盒复用”。“白盒复用”的缺点 比较多,总结起来有4点。1.暴露了源代码;
2.容易与程序员的“普通”代码发生命名冲突;
3.多份拷贝,造成存储浪费;
4.更新功能模块比较困难。
以上4点概括起来就是“暴露的源代码”,造成“代码严重耦合”。为了弥补这些不足,就提出了“二进制级别”的代码复用。使用二进制级别的代码复用一定程度上隐藏了源代码,对于缓解代码耦合现象起到了一定的作用。这样的复用被称为“黑盒复用”。
在Windows操作系统中有两种可执行文件,其后缀名分别为.exe和.dll。它们的区别在于,.exe文件可被独立的装载于内存中运行;.dll文件却不能,它只能被其它进程调用。然而无论什么格式,它们都是二进制文件。上面说到的“二进制级别”的代码复用,可以使用.dll来实现。
与白盒复用相 比,.dll很大程度上弥补了上述4大缺陷。.dll是二进制文件,因此隐藏了源代码;如果采用“显式调用”(后边将会提到),一般不会发生命名冲突;由 于.dll是动态链接到应用程序中去的,它并不会在链接生成程序时被原原本本拷贝进去;.dll文件相对独立的存在,因此更新功能模块是可行的。
C/C++有两种库:分别为ddl和lib,其中:
1、包含了函数所在的DLL文件和文件中函数位置的信息(入口),代码由运行时加载在进程空间中的DLL提供,称为动态链接库dynamic link library。
2、包含函数代码本身,在编译时直接将代码加入程序当中,称为静态链接库static link library。
动态链接库 (DLL) 是作为函数和资源的共享库的可执行文件。 动态链接可使执行文件调用函数或使用存储在单独文件中的资源。 可从使用这些函数和资源的可执行文件中对其分别进行编译和部署。 操作系统可在已加载可执行文件时或在运行时按需将 DLL 加载到可执行的内存空间中。 DLL 还可以在可执行文件之间轻松共享函数和资源。 多个应用程序可同时访问内存中单个 DLL 副本的内容。
静态链接会将 .lib 文件中所有对象代码复制到可执行文件中。 动态链接仅包括在运行时用于查找和加载含有数据项或函数的 DLL 所需的信息。 在创建 DLL 时,还会创建一个包含此信息的 .lib 文件。 生成调用 DLL 的可执行文件时,链接器会使用 .lib 文件中的导出符号来为加载程序存储此信息。 当加载程序加载 DLL 时,该 DLL 会映射到你的可执行文件的内存空间中。 将调用 DLL 中的特殊函数 DllMain 来执行 DLL 要求的任何初始化。
使用动态链接代替静态链接有若干优点。 当使用 DLL 时,可以节省内存空间,并减少交换操作。 当多个应用程序可以使用 DLL 的单个副本时,可以节省磁盘空间和下载带宽。 DLL 可单独部署和更新,这可以使你在无需重新生成和发布所有代码的情况下,提供售后支持和软件更新。 DLL 是一种提供特定区域资源的简便方法,可以支持多语言程序,并简化创建国际版本应用程序的过程。
相应的,c/c++共有两种链接方式:隐式调用和显示调用。
隐式调用:即通过LIB和头文件。该方法需要DLL工程经编译后产生的LIB文件,此文件包含DLL允许应用程序调用的所有函数列表。LINKER检测应用程序调用了LIB文件中的某个函数时,就会在应用程序EXE文件中加入相关信息。该应用程序运行时,系统会查看这个文件的DLL信息,后将DLL文件映射到地址空间。因为LIB文件并没有包含函数的具体实现,因此LIB文件相较于DLL文件和STATIC-LIB文件(静态链接库)会比较小。
系统寻找DLL文件的路径先后顺序如下:1. EXE文件所在目录 2. 当前程序工作目录 3. 系统目录 4. Windows目录 4. 环境变量中所有目录
VC中加载DLL的LIB文件的方法如下:1. LIB文件直接加入到工程文件列表中 2. 设置Project Settings加载LIB文件 3. 预编译指令 #pragma comment (lib, "*.lib")
显示调用:当只提供DLL文件而没有其相关的LIB文件和头文件的情况下,只能使用显示调用。显示调用能够更加有效的使用内存,编写大型程序时往往使用显示调用。该方法使用Load Library或者AfxLoadLibrary对DLL进行动态加载;使用GetProcessAdress获得所调用函数的指针;使用完毕后以Free Library或者AfxFreeLibrary将DLL从地址空间中卸载。
使用lib需注意两个文件:
1.h头文件,包含lib中说明输出的类或符号原型或数据结构。应用程序调用lib时,需要将该文件包含入应用程序的源文件中。
2.LIB文件。
使用dll需注意三个文件:
1.h头文件,包含dll中说明输出的类或符号原型或数据结构的.h文件。应用程序调用dll时,需要将该文件包含入应用程序的源文件中。
2.LIB文件,是dll在编译、链接成功之后生成的文件,作用是当其他应用程序调用dll时,需要将该文件引入应用程序,否则产生错误(如果不想用lib文件或者没有lib文件,可以用WIN32 API函数LoadLibrary、GetProcAddress装载)。
3.dll文件,真正的可执行文件,开发成功后的应用程序在发布时,只需要有.exe文件和.dll文件,并不需要.lib文件和.h头文件。
在使用时,静态链接库只要把.h和.lib文件加入到工程文件夹中即可。而动态链接库要把.h、.lib和.dll文件加入到工程中。
本文主要总结调用动态链接库的方法:C++调用DLL的方式有两种,一种是静态,另外一种是动态。
仅有.dll文件时候的使用方法
在没有.h和.lib文件时,需要函数指针和WIN32 API函数LoadLibrary、GetProcAddress装载,只需要.dll文件即可(将.dll文件置入工程目录中)。具体思路:
1.先编写一个DLL,我这里是直接在CPP里编写了函数声明和定义,没有单独的头文件,因为很多情况下的DLL都是没有和lib和头文件一起的。
2.然后另外新建一个项目,来调用DLL,方法是:
2.1.声明头文件<windows.h>,说明我想用windows32方法来加载和卸载DLL
2.2然后用typedef定义一个指针函数类型.typedef void(*fun) //这个指针类型,要和你调用的函数类型和参数保持一致,记住,是指针参数就是(int *,int)
2.3.定一个句柄实例,用来取DLL的实例地址。HINSTANCE hdll;格式为hdll=LoadLibrary(“DLL地址”);这里字符串类型是LPSTR,当是unicode字符集的时候会不行,因此要在配置-属性-常规里面把默认字符集“unicode”改成支持多字符扩展即可。
2.4.取的地址要判断,返回的句柄是否为空,如果为无效句柄,那么要释放加载DLL所占用的内存:FreeLibrary(hdll);
2.5.然后定义一个函数指针,用来获取你要用的函数地址。先是定一个函数指针 fun FUN;然后通过GetProcAdress来获取函数的地址,这个函数参数是DLL的句柄和你要调用的函数名:比如:FUN=(fun)GetProcAdress(hdll,"sum");这里也要判断要函数指针是否为空,如果没取到要求的函数,那么要释放句柄:FreeLibrary(hdll);
2.6.然后通过函数指针来调用函数。FUN(int *p,int count);这里不能用函数名来使用函数,因为这个DLL本身不是当前CPP的一部分,而是通过windows去调用.没有在这个工程里声明或者定义,而是暴露出一个头,要指针获取他的地址,通过指针来调用.
2.7.最后调用结束后,就释放句柄FreeLibrary(hdll);
(1)LoadLibrary 函数
function LoadLibrary(LibFileName : PChar): Thandle;
[功能]:加载由参数 LibFileName 指定的 DLL 文件。[说明]:参数 LibFileName 指定了要装载的 DLL 文件名,如果 LibFileName 没有包含一个路径,系统将按照:当前目录、Windows 目录、Windows 系统目录、包含当前任务可执行文件的目录、列在 PATH 环境变量中的目录等顺序查找文件。
如果函数操作成功,将返回装载 DLL 库模块的实例句柄,否则,将返回一个错误代码,错误代码的定义如下表所示。
(2)GetProcAddress 函数
function GetProcAddress(Module:Thandle; ProcName:PChar): TfarProc;
[功能]:返回参数 Module 指定的模块中,由参数 ProcName 指定的过程或函数的入口地址。[说明]:参数 Module 包含被调用函数的 DLL 句柄,这个值由 LoadLibrary 返回, ProcName
是指向含有函数名的以 nil 结尾的字符串指针,或者可以是函数的次序值,但大多数情况下,用函数名是一种更稳妥的选择。如果该函数执行成功,则返回 DLL 中由参数 ProcName 指定的过程或函数的入口地址,否则返回 nil 。
(3)FreeLibrary 函数
procedure FreeLibrary(Module: Thandle);
[说明]:将由参数 Module 指定的 DLL 文件从内存中卸载 1 次。[说明]:Module 为 DLL 库的句柄。这个值由 LoadLibrary 返回。由于 DLL 在内存中只装载一次,因此调用 FreeLibrary 首先使 DLL 的引用计数减 1,如果计数减为 0 则卸载该 DLL。
[注意]:每调用一次 LoadLibrary 函数就应调用一次 FreeLibrary 函数,以保证不会有多余的库模块在应用程序结束后仍留在内存中,否则导致内存泄漏。
实例:
#include <iostream>
#include <windows.h>
using namespace std;
typedef int(*FUNA)(int&, int&);//定义指向和DLL中相同的函数原型指针
int main() {
const char* dllName = "add.dll";
const char* funName = "add";
int x(10), y(10);
HMODULE hDLL = LoadLibrary(dllName);//加载dll
if (hDLL != NULL)
{
FUNA fp = FUNA(GetProcAddress(hDLL, "add"));//获取导入到应用程序中的函数指针,根据方法名取得
if (fp != NULL)
{
cout << "Input 2 Numbers:";
cin >> x >> y;
fp(x, y);
}
else
{
cout << "Cannot Find Function : " << funName << endl;
}
FreeLibrary(hDLL);
}
else
{
cout << "Cannot Find dll : " << dllName << endl;
}
return 1;
}
在调试过程中可能出现问题:const char *与LPCWSTR 不兼容:在VC 6.0中编译成功的项目在VS2005 vs2005、vs2008、vs2010中常会出现类型错误。如使用MessageBox(hwnd,"TEST",NULL,0)就会报错,如果使用强制转换(LPCWSTR)"TEST",虽然能够通过,但是会出现乱码。
出错原因:程序在UNICODE(宽字节)字符集下运行,如果调用了 MessageBox ,实际上调用的是 MessageBoxW 函数;如果程序在 ANSI 字符集运行,调用 MessageBox ,就相当于调用 MessageBoxA;其中 MessageBoxW 支持 UNICODE;MessageBoxA 支持ANSI;UNICODE与ANSI 有什么区别:UNICODE版的字符比ANSI 的内存占用大,比如:Win32程式中出现的标准定义 char 占一个字节,而 char 的UNICODE版被定义成这样:typedef unsigned short wchar_t ;占2个字节。所以有字符做参数的函数相应也用两个版本了。
解决上面问题可用的方法是:项目菜单——项目属性(最后一个)——配置属性——常规——项目默认值——字符集,将使用Unicode字符集改为“未设置”即可。
另外发现,该种方法只适应于C方式生成的dll文件,并不适应于C++方式生成的dll文件。
参考:
http://blog.csdn.net/waterbinbin/article/details/52625508
https://msdn.microsoft.com/zh-cn/library/ms235636
http://blog.csdn.net/cd520yy/article/details/49455127
http://www.jb51.net/article/36447.htm
http://www.cnblogs.com/lhbssc/archive/2012/02/08/2342853.html
http://blog.csdn.net/xl_lbj/article/details/12434175 (5)
http://blog.csdn.net/u010111422/article/details/38681289