静态链接库与动态链接库.docx
《静态链接库与动态链接库.docx》由会员分享,可在线阅读,更多相关《静态链接库与动态链接库.docx(20页珍藏版)》请在冰点文库上搜索。
静态链接库与动态链接库
一、 介绍
本文意在讲解静态链接库与动态链接库的创建与使用,在此之前先来对二者的概念、区别及优缺点进行简要的阐述。
其中大多内容参考相关网络资料,由于本人能力有限,不能确保完全准确无误,若有偏差之处请不吝指出。
文中使用到的代码均在VisualStudio2008中编译通过,如果您使用的IDE与本文不同,可根据实际情况进行相应项目创建与操作。
希望本文内容对您有所帮助。
二、 概念定义
1. 分别编译与链接
大多数高级语言都支持分别编译(Compiling),程序员可以显式地把程序划分为独立的模块或文件,然后由编译器(Compiler)对每个独立部分分别进行编译。
在编译之后,由链接器(Linker)把这些独立编译单元链接(Linking)到一起。
链接方式分为两种:
(1) 静态链接方式:
在程序开发中,将各种目标模块(.OBJ)文件、运行时库(.LIB)文件,以及经常是已编译的资源(.RES)文件链接在一起,以便创建Windows的.EXE文件。
(2) 动态链接方式:
在程序运行时,Windows把一个模块中的函数调用链接到库模块中的实际函数上的过程。
2. 静态链接库与动态链接库
静态链接库(StaticLibrary,简称LIB)与动态链接库(DynamicLinkLibrary,简称DLL)都是共享代码的方式。
如果使用静态链接库(也称静态库),则无论你愿不愿意,.LIB文件中的指令都会被直接包含到最终生成的.EXE文件中。
但是若使用.DLL文件,该.DLL文件中的代码不必被包含在最终的.EXE文件中,.EXE文件执行时可以“动态”地载入和卸载这个与.EXE文件独立的.DLL文件。
2.1. 动态链接方式
链接一个DLL有两种方式:
2.1.1 载入时动态链接(Load-TimeDynamicLinking)
使用载入时动态链接,调用模块可以像调用本模块中的函数一样直接使用导出函数名调用DLL中的函数。
这需要在链接时将函数所在DLL的导入库链接到可执行文件中,导入库向系统提供了载入DLL时所需的信息及用于定位DLL函数的地址符号。
(相当于注册,当作API函数来使用,其实API函数就存放在系统DLL当中。
)
2.1.2 运行时动态链接(Run-TimeDynamicLinking)
使用运行时动态链接,运行时可以通过LoadLibrary或LoadLibraryEx函数载入DLL。
DLL载入后,模块可以通过调用GetProcAddress获取DLL函数的入口地址,然后就可以通过返回的函数指针调用DLL中的函数了。
如此即可避免导入库文件了。
2.2. 二者优点及不足
2.2.1 静态链接库的优点
(1) 代码装载速度快,执行速度略比动态链接库快;
(2) 只需保证在开发者的计算机中有正确的.LIB文件,在以二进制形式发布程序时不需考虑在用户的计算机上.LIB文件是否存在及版本问题,可避免DLL地狱等问题。
2.2.2 动态链接库的优点
(1) 更加节省内存并减少页面交换;
(2) DLL文件与EXE文件独立,只要输出接口不变(即名称、参数、返回值类型和调用约定不变),更换DLL文件不会对EXE文件造成任何影响,因而极大地提高了可维护性和可扩展性;
(3) 不同编程语言编写的程序只要按照函数调用约定就可以调用同一个DLL函数。
2.2.3 不足之处
(1) 使用静态链接生成的可执行文件体积较大,包含相同的公共代码,造成浪费;
(2) 使用动态链接库的应用程序不是自完备的,它依赖的DLL模块也要存在,如果使用载入时动态链接,程序启动时发现DLL不存在,系统将终止程序并给出错误信息。
而使用运行时动态链接,系统不会终止,但由于DLL中的导出函数不可用,程序会加载失败;
(3) 使用动态链接库可能造成DLL地狱。
2.3. DLL 地狱
DLL 地狱(DLLHell)是指因为系统文件被覆盖而让整个系统像是掉进了地狱。
简单地讲,DLL地狱是指当多个应用程序试图共享一个公用组件时,如某个DLL或某个组件对象模型(COM)类,所引发的一系列问题。
最典型的情况是,某个应用程序将要安装一个新版本的共享组件,而该组件与机器上的现有版本不向后兼容。
虽然刚安装的应用程序运行正常,但原来依赖前一版本共享组件的应用程序也许已无法再工作。
在某些情况下,问题的起因更加难以预料。
比如,当用户浏览某些web站点时会同时下载某个MicrosoftActiveX控件。
如果下载该控件,它将替换机器上原有的任何版本的控件。
如果机器上的某个应用程序恰好使用该控件,则很可能也会停止工作。
在许多情况下,用户需要很长时间才会发现应用程序已停止工作。
结果往往很难记起是何时的机器变化影响到了该应用程序。
这些问题的原因是应用程序不同组件的版本信息没有由系统记录或加强。
而且,系统为某个应用程序所做的改变会影响机器上的所有应用程序—现在建立完全从变化中隔离出来的应用程序并不容易。
三、 静态链接库的创建与使用
在此通过一个实例来介绍静态库的创建与使用。
在该实例中,我们将一个实现两整数相加求和的函数封装到静态库中供其他程序调用。
1. 创建
首先,使用VisualStudio2008来创建一个带预编译头的静态库项目Static,该项目包含在名为Library的解决方案中。
1.1. 创建静态库项目
创建一个不带预编译头的静态链接库项目有以下几个步骤:
(1) 单击菜单命令 “文件”-“新建”-“项目”,弹出“新建项目”对话框;
(2) 在弹出的“新建项目”对话框中,选择左边“类别”列表中选择 “VisualC++”-“Win32”,在右边的“模版”中选择“Win32项目”;
(3) 在下方输入项目名称“Static”,并选择项目创建的位置,勾选“生成解决方案”,并输入解决方案名称“Library”,然后点击“确定”按钮;
(4) 点击两次“下一步”按钮,进入“应用程序设置”界面;
(5) 在“应用程序设置”界面中,选择“静态库”,并确保下方“附加选项”中的“预编译头”被勾选,然后点击“完成”按钮。
1.2. 编辑项目
经过上面的步骤,初步创建了一个带预编译头的静态库项目,接下来编辑该项目以达到我们的创建静态库的目的。
首先添加一个用于定义导出函数的源文件Static.cpp,编码实现两个整数相加的Add函数。
源文件代码如下:
#include“StdAfx.h” // 标准头文件
intAdd(inta,intb)
{
returna+b;
}
接着点击菜单命令,“工具”-“生成Static”。
如果一切顺利的话,就会在解决方案的“Debug”目录中生成了名为“Static.lib”的静态链接库。
同时,需要给该静态链接库编写一个声明头文件Static.h,以便在链接时告知编译该链接库中的导出函数声明。
Static.h中的代码很简单,只要声明一下Add函数就可以:
#ifndef__STATIC_H__ // 防止该头文件重复引用
#define__STATIC_H__
intAdd(inta,intb); // 声明导出函数
#endif
接着点击菜单命令,“工具”-“生成Static”。
如果一切顺利的话,就会在Library解决方案的Debug目录中生成了名为MyDLL.lib的静态链接库。
2. 使用
在Library解决方案下,再添加一个Win32控制台应用程序空项目UseLIB。
程序主文件名为UseLIB.cpp,其中包含用于调用Add函数的程序入口函数main。
将刚才创建的Static.lib及其声明头文件Static.h一同复制到UseLIB项目目录下。
并在源文件UseLIB.cpp中使用预编译命令链接Static.lib(也可以在IDE的项目属性中设置链接器选项,或者只复制Static.h文件并设置UseLIB项目的“项目依赖项”为Static项目)。
源文件UseLIB.cpp中的代码如下:
#pragmacomment(lib,“Static.lib”) // 链接静态库Static.lib
#include
#include“Static.h” // 包含Static.lib的声明头文件,声明导出函数Add
intmain(void)
{
inta=1,b=2;
printf(“%d+%d=%d\n”,a,b,Add(a,b)); // 调用Static.lib中的Add函数
return0;
}
接下来点击菜单命令,“工具”-“生成UseLIB”。
如果顺利的话,就会在Library解决方案的Debug目录中生成了名为UseLIB.exe的可执行执文件,运行UseLIB.exe,将在控制台中输出结果:
1+2=3
3. 注意
由于项目中创建的源文件为.CPP文件,即C++源文件,因此VisualC++按C++规范,并采用__cdecl调用约定对其进行编译。
这样得到的导出函数就不能被C语言程序所调用。
解决该问题的办法是在函数体名称前添加extern“C”修饰,告诉编译器,该函数按照C语言规范,并采用__cdecl调用约定进行编译。
因此源文件Add.cpp中的代码可修改如下:
extern“C”intadd(inta,intb)
{
returna+b;
}
最后重新编译该静态链接库项目,导出函数Add就能够被C语言程序所调用了。
另一种不改变代码的方法是在“Static属性页”左边的列表中选择“配置属性”-“C/C++”-“高级”,然后在右边的“调用约定”选择“__cdecl(/Gd)”,“编译为”选择“编译为C++代码 (/TP)”。
这种方法在不同的IDE上设置方法有所不同。
四、 动态链接库的创建与使用
在此同样通过一个实例来介绍动态链接库的创建与使用。
在实例中,依然使用Add函数进行讲解,这样一方面可以沿用上面静态链接的有关内容,另一方面也可以了解动态链接库与静态链接库在创建和使用上的异同。
1. 创建
首先,在之前创建的Library解决方案中添加一个带预编译头的动态链接库项目,项目名称为Dynamic。
使用不同IDE的朋友可以根据实际情况进行创建。
1.1. 创建动态链接库项目
创建一个带预编译头的动态链接库项目有以下几个步骤:
(1) 单击菜单命令 “文件”-“新建”-“项目”,弹出“新建项目”对话框;
(2) 在弹出的“新建项目”对话框中,选择左边列表的“VisualC++”–“Win32”,在右边的项目模版中选择“Win32项目”;
(3) 在下方输入项目名称“Dynamic”,并选择项目创建的位置,然后点击“确定”按钮;
(4) 点击两次“下一步”按钮,进入“应用程序设置”界面;
(5) 在“应用程序设置”界面中,选择“DLL”,然后点击“完成”按钮。
1.2. 编辑项目
Dynamic项目自动生成的dllmain.cpp源文件含有一个名为DllMain的函数,该函数是DLL被链接时的入口函数,它由系统自动调用,在这里我们不用去理会它。
与前面创建静态态链接库类似的,首先添加一个用于定义导出函数的源文件Dynamic.cpp,编码实现两个整数相加的Add函数。
源文件代码如下:
extern“C”__declspec(dllexport)intAdd(inta,intb) // 声明为DLL导出函数
{
returna+b;
}
与前面静态链接库不同,在Add函数体名称前不只添加了extern“C”修饰,还多添加了一个 __declspec(dllexport)修饰。
__declspec(dllexport)修饰的作用是告诉编译器,这个函数将作为导出函数,并在输入库中生成该函数的地址符号等信息,这样其他程序就可以使用载入时动态链接方式来调用该函数。
另外,extern“C”在封装DLL还有另一个作用,就是告诉编译器,在DLL中的导出函数不要使用函数名修饰规则,这样在采用运行时动态链接时就可以直接使用原函数名来调用导出函数了。
关于函数调用方式和导出方式的详细说明在后面还将提出,现在先撇开这些烦人的问题。
接着点击菜单命令,“工具”-“生成Dynamic”。
如果一切顺利的话,就会在Library解决方案的Debug目录中生成了名为Dynamic.dll的动态链接库和名为Dynamic.lib的导入库(与静态链接库不同,只存放DLL的导出表,不包含代码)。
最后需要给该Dynamic.dll的输入库Dynamic.lib编写一个声明头文件Dynamic.h,以便在以后链接时告知编译器该链接库中的具体的导入内容(一般包括代码和资源)。
Dynamic.h中的代码很简单,只要声明一下Add函数就可以:
#ifndef__DYNAMIC_H__ // 防止该头文件重复引用
#define__DYNAMIC_H__
__declspec(dllexport)intAdd(inta,intb); // 声明导出函数
#endif
2. 使用
在同Library解决方案中,添加一个名为UseDLL的Win32控制台应用程序空项目。
程序主文件名为UseDLL.cpp,其中包含用于调用Add函数的程序入口函数main。
一下使用两种动态链接方式来链接Dynamic.dll。
2.1. 载入时动态链接
载入时动态链接是一种轻松使用动态链接库的方法,它使得使用动态链接库如同使用静态链接库一样方便。
将导入库Dynamic.lib及其声明头文件Dynamic.h一同复制到UseDLL项目目录下,并把Dynamic.dll复制到项目的Debug目录中。
并在源文件UseDLL.cpp中使用预编译命令链接Dynamic.lib(也可以在IDE的项目属性中设置链接器选项,或者只复制Dynamic.h文件并设置UseDLL项目的“项目依赖项”为Dynamic项目)。
源文件UseDLL.cpp中的代码如下:
#pragmacomment(lib,“Dynamic.lib”) // 链接导入库Dynamic.lib
#include
#include“Dynamic.h” // 包含Dynamic.lib的声明头文件,提供导出函数Add的声明
intmain(void)
{
inta=1,b=2;
printf(“%d+%d=%d\n”,a,b,Add(a,b)); // 调用Dynamic.DLL中的Add函数
getchar(); // 用于查看输出结果
return0;
}
几乎跟使用静态链接库一样。
接下来点击菜单命令,“工具”-“生成UseDLL”。
如果一切顺利的话,就会在Library解决方案的Debug目录中生成了名为UseDLL.exe的可执行文件,运行UseDLL.exe文件,将在控制台中输出结果:
1+2=3
2.2. 运行时动态链接
运行时动态链接的代码相对麻烦些,需要使用到Windows的三个API函数,还要进行一些判断以防止不必要的麻烦。
我们在UseDLL项目的基础上做些修改来实现运行时动态链接。
这里只需要把Dynamic.dll复制到UseDLL项目的Debug目录中,因为不用在编译的时候链接导入库,只要在运行根据需要链接Dynamic.dll。
下面先给出修改后的源文件Dynamic.cpp的代码:
#include // 用于声明windowAPI函数及宏等
#include
typedefint(*FuncAdd)(inta,intb); // 定义将要调用的导出函数Add的指针类型
intmain(void)
{
FuncAddAdd; // 定义Add函数指针
inta=1,b=2;
HMODULEhDLL=LoadLibrary(TEXT("MyDLL.dll")); // 载入DLL,并获取其句柄
if(hDLL) //MyDLL.dll载入成功
{
Add=(FuncAdd)GetProcAddress(hDLL, "Add"); // 获取导出函数Add指针
if(Add) // 正确获取Add函数指针
{
printf("%d+%d=%d\n",a,b,Add(a,b)); // 调用导出函数Add
}
else // 没有找到Add函数
{
printf("AddNotFound!
\n");
}
}
else //MyDLL.dll载入失败
{
printf("LoadLibraryFailed!
\n");
}
getchar();
FreeLibrary((TEXT("MyDLL.dll")); // 释放DLL
return0;
}
看到了吧,调用方法比较繁琐。
由于没有链接导入库,不能使用地址符号定位导出函数的入口地址,只能通过GetProcAdress来获取其在地址空间中的指针,再通过指针调用。
但程序在运行之前,GetProcAdress无法判断指针的有效性。
因此,为了防止Dynamic项目中不存在Add函数而使程序在运行时出错,有必要在调用Add之前判断其函数指针的有效性。
最后,点击菜单命令,“工具”-“重新生成UseDLL”。
如果一切顺利的话,就会在Library解决方案的Debug目录中生成了名为UseDLL.exe的可执行文件,运行UseDLL.exe文件,将在控制台中输出结果:
1+2=3
五、 补充阅读
1. VisualStudio2008中DLL项目只生成dll文件不生成lib文件的解决方案
在创建动态链接库项目时,如果在“应用程序设置”中只勾选“预编译头”,而没有勾选“空项目”或“导出符号”,那么在对该项目进行编译链接时将只会生成动态链接库dll文件,不生成导入库lib文件。
此问题的解决办法如下:
(1) 右键单击该项目,在弹出的菜单中点击“添加”-“新建项”;
(2) 在弹出的“添加新项”对话框中,在左边“类别”列表中选择“VisualC++”-“代码”,在右边“模版”中选择“模块定义文件(.def)”;
(3) 在“名称”输入框中输入任意名称,这里使用该项目名称,如“MyDLL”,单击“确定”按钮;
(4) 重新生成该项目,即生成lib文件。
注意,此时在项目属性的配置列表“配置属性”-“链接器”-“输入”中的“模块定义文件”项目中将出现刚才创建的模块定义文件MyDLL.def。
如果此前不是添加“新建项”,而是添加“现有项”,那么必须在此项目上填写该现有模块定义文件的文件名,否则将不会生成lib文件。
2. 动态链接库的应用举例
(1) 所有的Windows系统调用(WindowsAPI函数)都是以动态链接库的形式提供的。
我们在Windows目录下的system32文件夹中会看到kernel32.dll、user32.dll和gdi32.dll,windows的大多数API都包含在这些DLL中。
kernel32.dll中的函数主要处理内存管理和进程调度;user32.dll中的函数主要控制用户界面;gdi32.dll中的函数则负责图形方面的操作。
与这些动态库相对应的导入库分别为kernel32.lib、user32.lib和gdi32.lib。
(2) 软件的自动更新。
Windows应用的开发者常常利用动态链接库来分发软件更新。
他们生成一个动态库的新版本,然后用户可以下载,并用它替代当前的版本。
当然,新、旧版本动态库的输出接口(即导出函数)必须一致。
下一次用户运行应用程序时,应用将自动链接和加载新的动态库。
(3) 软件插件技术。
许多Windows应用软件都支持插件扩展方式,如IE浏览器、Photoshop、Office等等。
插件在本质上都是动态库。
(4) 可扩展的Web服务器。
(5) 每个Windows驱动程序在本质上都是动态链接库。
3. __declspec(dllexport)与 .def文件
在 32 位编译器版本中,可以使用__declspec(dllexport)关键字从DLL导出数据、函数、类或类成员函数。
__declspec(dllexport)将导出指令添加到对象文件(即obj文件),若要导出函数,__declspec(dllexport)关键字必须出现在调用约定关键字的左边(如果指定了关键字)。
例如:
__declspec(dllexport)void__cdeclFunction1(void);
若要导出类中的所有公共数据成员和成员函数,关键字必须出现在类名的左边,如下所示:
class__declspec(dllexport)CExampleExport:
publicCObject
{...classdefinition...};
生成DLL时,通常创建一个包含正在导出的函数原型和/或类的头文件,并将__declspec(dllexport)添加到头文件中的声明。
若要提高代码的可读性,请为__declspec(dllexport)定义一个宏并对正在导出的每个符号使用该宏:
#defineExport__declspec(dllexport)
模块定义文件(.def)是包含一个或多个描述各种DLL属性的模块语句的文本文件。
二者的目的都是将公共符号导入到应用程序中或从 DLL 导出函数。
添加__declspec(dl