VC++ 动态链接库之VS2010版本

在网上看到一篇介绍动态链接库的文章,感觉写的很好,不过是针对VC6.0的,看年份是作者2007年写的了,拿到文章个人感觉可能VC6.0中会与VS2010/C++中有不同,所以自己写一份关于VS2010版本的,原版见出处:http://www.cnblogs.com/chio/archive/2007/11/03/948480.html 同时感谢这位朋友的贡献,谢谢。


序:动态链接库这个概念是在读硕士期间第一次接触的,当时写算法模块,需要我提供库函数。后来就再也没有自己写过动态链接库,现在新任务需要架构一套桌面软件,又需要该知识,发现全忘记了,而且本来当时也不是理解特别透彻,现在只好重新学起。

先理解lib文件吧。

LIB有两种,一种是静态库,比如C-Runtime库,这种LIB中有函数的实现代码,一般用在静态链编上,它是将LIB中的代码加入目标模块(DLL或者EXE)文件中


include文件夹中都是.h头文件;lib文件夹中一般都是.lib文件,但是bin中一般都是exe?或者dll吗?


win32控制台程序要使用system("pause"); 代码需要头文件 #include <stdlib.h>或者#include <iostream>



PE文件--- PE文件的全称是Portalble Executable,意思是可移植的执行体,常见的EXE,DLL,OCX,SYS,COM都是PE文件。PE文件可能是间接被执行,如DLL。

可执行文件 --- 全程是Executable File,属于PE文件,它可以是exe文件,.sys文件,com文件等,在win32中可执行文件就是指PE文件。Win32可执行文件叫做PE文件。

在链接库这块儿来说,可执行文件就是指.exe文件了。


资源文件中可以包括对话框、快捷键、菜单、字符串、版本信息和一些图形资源等内容

。资源文件的源文件是一种类似“脚本”的文本文件,它的扩展名一般为rc,其中用不同的语法定义了不容类型的资源,资源脚本文件最后由资源编译器编译成资源文件*。res.资源脚本文件同样用到很多预定义值,所以软件包中一般也包括资源头文件供源文件来导入。一般资源头文件是Resource.h。

编译好目标文件*.obj和资源文件*.res后,最后一步是链接器将它们链接成可执行文件。链接的时候要用到函数库。在DOS环境下编程的时候,使用的函数库是静态库。静态库是一些已经编译好的代码模块。当用户在源程序中用到某个函数的时候,链接器从库文件中将这个函数的二进制代码取出,与obj文件合在一起生成最终的exe文件。但在win32环境下,大部分的公用函数封装在dll文件中,以动态链接的方式供用户程序调用。这时候库文件中只需要包含函数在dll中的位置信息,不再需要有二进制代码部分。所以链接的时候也只是把库文件中的位置信息取出放入最后的可执行文件中。win32的这种只包含位置信息的库文件成为导入库。


个人理解:

静态库是一些已经编译好的代码模块,当用户在源程序中用到某个函数时候,链接器从库文件中将这个函数的二进制代码取出,与obj文件合在一起生成最终的exe文件。

如果此时链接的是事先编译好的动态库(静态方式链接还是动态方式链接与编译时候采用的方式有关吗?),这时只需要导入库文件.lib中的信息,链接时候把lib导入库中的位置信息取出后放入最后的可执行文件中。win32中这种只包含位置信息的库文件叫导入库。

导入库和静态库都是以.lib为后缀名。但是导入库中只有函数的位置信息,没有函数体信息,而静态库是全部包含了的。



需要了解的背景知识:

一、VS2010下一个exe文件生成的步骤

咱们一般在VS2010下写好一个程序之后,按下CTRL+F5(CTRL+F5:开始执行不调试;F5:开始debug调试)时,VS2010做了以下工作。

编译

1.预处理:VS讲cpp文件中的注释忽略,将宏转化等,同时包含头文件等。在包含头文件时,VS根据路径寻找相关的头文件(<a.h>代表默认路径VS下的include文件夹,比如#include <stdlib.h>   system("pause");"a.h"代表的默认路径是此项目文件夹(注意不是解决方案文件夹,同一个解决方案下可以有多个项目),当头文件不在对应路径时候,会出现找不到头文件的编译错误 )

2.编译:将包含了头文件的被修改的源程序转化为汇编程序(还是文本文件)。此阶段输出是汇编文件,也属于文本文件。

3.汇编:将汇编程序汇编为可重定向的二进制目标文件。此阶段输出二进制obj文件


链接

(动态链接库和静态链接库都是此阶段介入的,也就是对lib文件是此阶段介入的   编译好的静态链接库lib文件叫静态库;编译好的动态链接库文件lib文件叫动态库的导入库。虽然都是.lib文件,但是叫法不同,内容也不同。静态库的lib文件是全的,动态库的lib文件只有函数位置信息)

1.连接器将汇编后的可重定向的目标文件进行链接,生成可执行的二进制文件。链接时有两种,一种是静态链接,此时VS启动连接器进行链接操作,如果链接时需要的库文件在链接配置中不存在时,会出现链接错误(每个VS项目都有属性,属性中有链接配置,其中包含了需要使用的库文件,连接器需要库文件时就在这里找)。另一种是动态链接,就是在运行时进行链接操作。

2.链接是指:将各个obj文件以及静态库的lib文件中的代码模块加进来,或者动态库的导入库文件lib中的函数位置信息加进来形成一个完整的exe程序。

此阶段输出:exe文件

运行
1.运行时用到动态链接库(.dll)时,系统会在系统文件夹下找相应的dll文件。这里和VS环境已经没有关系了。




也有人把程序运行前的步骤分为:预处理、编译、汇编和链接几个步骤,具体如下所述。

程序运行一般需经过预处理,编译,汇编和链接几个步骤。在我们的应用中,有一些公共代码是需要反复使用,就把这些代码编译为“库”文件;在链接步骤中,链接器将从库文件中取得所需要的代码,复制到生成的可执行文件中。这种库称为静态库,其特点是可执行文件中包含了库代码的一份完整拷贝;缺点就是被多次使用就会有多分冗余拷贝。

静态库和动态库是两种共享程序代码的方式。它们的区别是:静态库在程序的链接界面被复制到了程序中,和程序运行时候没关系;动态链接库在链接阶段没有被复制到程序中,只是获取到函数的位置信息,而在程序运行时由系统动态加载到内存中供程序调用。使用动态库的优点是系统只需载入一次动态库,不同的程序可以得到内存中相同的动态库的副本,因此节省了很多内存。



总结:无论是静态方式引入(静态)库还是动态方式引入(动态)库,都是在链接阶段将.lib文件中内容加入进来(对静态库来讲需要事先编译好静态库.lib,该库中包含了函数定义和声明,此时链接是将全部链接进来;对于动态库来讲是事先编译好动态库和导入库.lib文件,导入库中只有函数位置信息,函数定义是在dll中)。


静态链接库
静态链接库就是你使用的.lib文件,库中的代码最后需要链接到你的可执行文件中,所以静态链接的可执行文件一般比较大一些。比如:#pragma comment(lib,"xxx.lib")。
windows下的静态链接库
针对开发环境,如果是VisualStudio,位置在 项目属性----配置属性---链接器---输入---附加依赖项 中加入lib文件。
静态链接库不同于动态链接库,在静态库情况下,函数和数据被编译进一个二进制文件(通常扩展名为*.lib),VC++的编译器在链接过程中将从静态库中恢复这些函数和数据,并把他们和应用程序中的其他模块组合在一起生成可执行文件。这个过程称为"静态链接".此时因为应用程序锁需的全部内容都是从库中复制了出来,所以静态库本身并不需要与可执行文件一起发行。


个人理解对应VS中,VS中的"编译"就包括上面所讲的预处理、编译、汇编阶段;VS中的生成就是上面所讲的链接;VS中的CTRL+F5就是运行。

对于VS2010下,新建一个win32控制台项目或者应用程序项目---静态库来说,编辑好代码后,点击编译只是在debug中生成了obj文件,即符合上面所说的一般程序编译后生成obj文件的规律,此时并没有生成可执行文件lib库,需要点击生成,才会在debug中看到.lib文件生成。

如果要使用该静态库,需要将该lib文件和生成lib文件的头文件一起拷贝到调用程序源文件中,在调用程序中#pragma comment(lib,"*.lib")   *代表该静态库的名字    即可。

相关提示技巧:

1.如果使用了某个静态库中的函数,但是没有在调用程序中引用该函数所在的头文件,即没写 #include "*.h",编译时候会提示该函数找不到标识符。编译阶段检查头文件等,详细可参考上面相关知识。

比如:


2.如果调用程序中,添加了静态库中某个函数的头文件,即#include "*.h",此时需要把相应的头文件拷贝到当前调用程序的源代码同级目录下,否则编译时会提示无法打开包括头文件,没有该文件或者路径。或者也可以写#include "*.h",而把头文件放到某个路径下的lnclude文件夹中,此时程序编译时候要找到.h文件需要配置项目属性---配置属性C/C++---常规---附加包含目录,将该.h文件所在的include目录包含进来。


3.由于编译阶段不需要链接静态库.lib文件,所以即使不在调用程序中添加 #pragma comment(lib,"*.lib"),点击VS“编译”按钮时候也能成功不报错。如下所示:


但是点击“生成”时候就报错了,包链接错误,因为VS的“生成”过程就对应实际的链接过程,链接时候需要用到.lib库文件。具体如下:



4.或者如果源程序中不添加#pragma comment (lib,"*.lib"),也可以项目属性---配置属性---连接器---输入---附加依赖项中加入静态库;前提是都要想将*.lib文件拷贝到调用源程序同级目录中。


5.如果*.lib库文件没有像第4条那样放到当前项目路径下,而是放到其他路径下,则需要#pragma comment (lib,"*.lib"),做相应的改动,比如说将*.lib文件放到第4条中所述的上级目录中,则需要写成#pragma comment (lib,"../*.lib"),

6.对于调用程序中使用#pragma comment (lib,"*.lib")或者#pragma comment (lib,"../*.lib")的形式,将整个工程拷贝给其他人使用的时候,在别人VS环境下可以直接使用;但是对于项目属性---配置属性---连接器---输入---附加依赖项中加入静态库这种方式的,拷贝到其他人机器上,其他人的机器也还是需要配置项目属性---配置属性---连接器---输入---附加依赖项的,或者在程序中自己手动加上#pragma comment (lib,"*.lib")或者#pragma comment (lib,"../*.lib")。

7.#pragma comment (lib,"*.lib")这里的路径是相对路径,与项目属性---配置属性---链接器---常规---附加库目录定位到*.lib所在的文件夹路径,该配置路径与*.lib构成*.lib的绝对路径。这样调用程序就可以找到*.lib文件了。


注意:附加依赖项和附加库目录都是在项目属性---配置属性---链接器中搞定的。因为*.lib只与链接阶段有关。

总结:对于引入静态库,解决链接阶段的问题有两种处理方式。

第一种:前提将*.lib文件拷贝到当前项目源文件同级目录下:

(1)调用程序中,写入#pragma comment (lib,"*.lib");

(2)调用程序中不写#pragma comment (lib,"../*.lib"),需要在项目属性---配置属性---链接器---常规---附加依赖项中添加*.lib;

小结:不涉及lib文件相对调用源程序文件夹路径有变化,就不需要设置附加库目录。

第二种:前提*.lib文件不在当前项目源文件同级目录下,而是在某个路径下的lib文件夹中:

(1)调用程序中,写入#pragma comment (lib,"*.lib"),此时还需要设置附加库目录,定位到该lib文件夹下。

(2)调用程序中不写#pragma comment (lib,"../*.lib"),而是在项目属性---配置属性---链接器---常规---附加依赖项中添加*.lib,此时还需要设置附加库目录,定位到该lib文件夹下。


着重理解下面两张图
1.
2.


生成之后的运行
经过上述步骤生成exe文件之后,将单独的exe拷贝到任何地方都能运行,不需要再将*.lib也带上了。
上述是编译完静态链接方式,这种方式,exe比较大,但是对于使用第三方的插件来说可以不被发现。当然前提是使用第三方的静态库。(该描述是否准确,有待商榷)
我们得出这样的结论:我们在链接的时候需要静态链接库,一旦链接成功,生成了可执行文件,那么,静态链接库就不再需要了。

如果目标生成的是x.dll,那么还会附带生成一个x.lib,这时这个x.lib称为"输入库(Import library)"或者导入库,输入库x.lib中只有一些导出函数的地址信息,没有实际的函数执行体(指令部分),在你link到一个这样的x.lib时,无论如何,都依赖于x.dll

 

如果目标生成的是x.lib,这时,并没有一个相应的x.dll生成,这时x.lib是真正的"静态库(Static library)",静态库x.lib中有实际的函数执行体,在你link到这种x.lib时,不依赖于x.dll,linker会把相应的函数执行体直接生成到exe里

 


#ifdef _DEBUG
#pragma comment(lib, "debug/xxx.lib")
#else
#pragma comment(lib, "release/xxx.lib")
#endif

3,_declspec用法三
_declspec(dllimport)     是说这个函数是从别的DLL导入。我要用。
_declspec(dllexport)   是说这个函数要从本DLL导出。我要给别人用。

如,

#define  Test_API __declspec(dllexport)

Class test
{
   
public:
   Test_API HRESULT WINAPI Initialize(LPCTSTR filename);
}
静态链接库实际开发源代码详细见:
使用 #pragma comment(lib,"static.lib")方式;具体见程序http://download.csdn.net/detail/xiaxiaojing/8379623
没有用#pragma comment(lib,"static.lib"),而是在项目属性--配置属性---链接器---输入---附加依赖项---添加static.lib;具体见程序http://download.csdn.net/detail/xiaxiaojing/8379665
动态链接库
创建动态链接库与创建静态链接库的区别:1.在创建向导时候选择不一样,进入win32控制台程序或者win32应用程序,点击下一步,一个是选择DLL,一个是选择静态库。然后动态库中需要比静态库中(以函数为例).cpp文件中在函数类型前面加上 _declspec(dllexport)修饰。动态链接库可以没有头文件,入股哦动态库没有头文件,调用该动态库时候需要在调用前声明要调用的函数,比如在调用的源程序中需要写上 extern int add(int x,inty)或者写上_declspec(diiimport) int add(int x,int y),  使用的时候需要一个.lib导入库和一个dll即可。注意:编译时候需要.lib引入库,引用方式同静态库测试程序中引入导入库的方式。运行时候需要dll与exe位于同级目录下。但是更一般的情况是咱们使用第三方的dll是不知道具体函数的声明的,这时候就需要在使用dll的源程序中#include “*.h”  *.h中声明了dll中的函数,这时候就不需要再加extern int add(int x,inty)或者_declspec(diiimport) int add(int x,int y),之类的了。 *.h的引入方式同静态库。
具体见源程序http://download.csdn.net/detail/xiaxiaojing/8379975

一般来说,客户程序并不知道函数的原型,所以我们在发布动态库时,除了提供*.dll和*.lib文件,还应提供导出函数声明的头文件*.h。基于这种情况,我们使用该dll的方式如下:
#include "*.h"
#pragma comment(lib,"*.lib")  此时*.h文件和*.lib文件都应该在调用程序同级目录下。如果没有则需要配置项目属性---配置属性---C/C++---附加包含目录,定位到include的头文件所在的文件夹;同时配置项目属性---配置属性---链接器---常规---附加库目录,定位到*.lib导入库所在的文件夹。

下面是复制别人的:

lib文件与dll

  (1)lib是编译时需要的,dll是运行时需要的。

  如果要完成源代码的编译,有lib就够了。

  如果也使动态连接的程序运行起来,有dll就够了。

  在开发和调试阶段,当然最好都有。

  (2)一般的动态库程序有lib文件和dll文件。lib文件是必须在编译期就连接到应用程序中的,而dll文件是运行期才会被调用的。如果有dll文件,那么对应的lib文件一般是一些索引信息,具体的实现在dll文件中。如果只有lib文件,那么这个lib文件是静态编译出来的,索引和实现都在其中。静态编译的lib文件有好处:给用户安装时就不需要再挂动态库了。但也有缺点,就是导致应用程序比较大,而且失去了动态库的灵活性,在版本升级时,同时要发布新的应用程序才行。

  (3)在动态库的情况下,有两个文件,一个是引入库(.LIB)文件,一个是DLL文件,引入库文件包含被DLL导出的函数的名称和位置,DLL包含实际的函数和数据,应用程序使用LIB文件链接到所需要使用的DLL文件,库中的函数和数据并不复制到可执行文件中,因此在应用程序的可执行文件中,存放的不是被调用的函数代码,而是DLL中所要调用的函数的内存地址,这样当一个或多个应用程序运行是再把程序代码和被调用的函数代码链接起来,从而节省了内存资源。从上面的说明可以看出,DLL文件必须随应用程序一起发行,否则应用程序将会产生错误。

加载LIB文件方法

直接加入

  在VC中打开File View一页,选中工程名,单击鼠标右键,然后选中"Add Files to Project"菜单,在弹出的文件对话框中选中要加入DLL的LIB文件即可。

设置工程的 Project Setting

  打开工程的 Project Settings菜单,选中Link,然后在Object/library modules下的文本框中输入DLL的LIB文件。

通过程序代码

  加入预编译指令#pragma comment (lib,"*.lib"),这种方法优点是可以利用条件预编译指令链接不同版本的LIB文件。因为,在Debug方式下,产生的LIB文件是Debug版本,如Regd.lib;在Release方式下,产生的LIB文件是Release版本,如Regr.lib。

  当应用程序对DLL的LIB文件加载后,还需要把DLL对应的头文件(*.h)包含到其中,在这个头文件中给出了DLL中定义的函数原型,然后声明。



  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值