最近接触到的游戏会有很多的dll和lib文件,之前关于动态链接库和静态链接库一直很不理解,最近发现了一篇很好的文章,非常清晰的讲解了dll与lib的关系,这里拿出来给大家分享下。

原文链接:http://blog.163.com/zhengjiu_520/blog/static/3559830620093583438464/

前面有一章说编译与链接的,说得很简略,其实应该放到这一章一块儿来说的。许多单讲C++的书其实都过于学院派,对于真实的工作环境,上百个源文件怎么结合起来,几乎没有提及。我引导读者一步步看看lib与DLL是怎么回事。 
一个最简单的C++程序,只需要一个源文件,这个源文件包含了如下语句

int main(){return 0;} 

自然,这个程序什么也不做。 
当需程序需要做事情时,我们会把越来越多的语句添加到源文件中,例如,我们会开始在main函数中添加代码:

#include <stdio.h>
int main()
{ printf("Hello World!\n"); return 0;
} 

由于人的智力水平的限制,当一个函数中包含了太多的语句时,便不太容易被理解,这时候开始需要子函数:

#include <stdio.h>
void ShowHello()
{ printf("Hello World!\n");
}
int main()
{ ShowHello(); return 0;
} 

同样的道理,一个源文件中包含了太多的函数,同样不好理解,人们开始分多个源文件了

// main.cpp
void ShowHello();//[1]
int main()
{ ShowHello(); return 0;
}
// hello.cpp
#include <stdio.h>
void ShowHello()
{ printf("Hello World!\n");
} 

将这两个文件加入到一个VC工程中,它们会被分别编译,最后链接在一起。在VC编译器的输出窗口,你可以看到如下信息

--------------------Configuration: hello - Win32 Debug--------------------
Compiling...
main.cpp
hello.cpp
Linking...
hello.exe - 0 error(s), 0 warning(s) 

这展示了它们的编译链接过程。 
接下来,大家就算不知道也该猜到,当一个工程中有太多的源文件时,它也不好理解,于是,人们想到了一种手段:将一部分源文件预先编译成库文件,也即lib文件,当要使用其中的函数时,只需要链接lib文件就可以了,而不用再理会最初的源文件。 
在VC中新建一个static library类型的工程,加入hello.cpp文件,然后编译,就生成了lib文件,假设文件名为hello.lib。 
别的工程要使用这个lib有两种方式:

1 在工程选项-〉link-〉Object/Library Module中加入hello.lib 
2 可以在源代码中加入一行指令 
#pragma comment(lib, "hello.lib")

注意这个不是C++语言的一部分,而是编译器的预处理指令,用于通知编译器需要链接hello.lib 
根据个人爱好任意使用一种方式既可。 
这种lib文件的格式可以简单的介绍一下,它实际上是任意个obj文件的集合。obj文件则是cpp文件编译生成的,在本例中,lib文件只包含了一个obj文件,如果有多个cpp文件则会编译生成多个obj文件,从而生成的lib文件中也包含了多个obj,注意,这里仅仅是集合而已,不涉及到link,所以,在编译这种静态库工程时,你根本不会遇到链接错误。即使有错,错误也只会在使用这个lib的EXE或者DLL工程中暴露出来。 
现在介绍另外一种类型的lib,它不是obj文件的集合,即里面不含有实际的实现,它只是提供动态链接到DLL所需要的信息。这种lib可以在编译一个DLL工程时由编译器生成。涉及到DLL,问题开始复杂起来,我不指望在本文中能把DLL的原理说清楚,这不是本文的目标,我介绍操作层面的东西。

简单的说,一个DLL工程和一个EXE工程的差别有两点: 
1 EXE的入口函数是main或者WinMain,而DLL的入口函数是DllMain 
2 EXE的入口函数标志着一段处理流程的开始,函数退出后,流程处理就结束了,而DLL的入口函数对系统来说,只是路过,加载DLL的时候路过一次,卸载DLL的时候又路过一次[2],你可以在DLL入口函数中做流程处理,但这通常不是DLL的目的,DLL的目的是要导出函数供其它DLL或EXE使用。你可以把DLL和EXE的关系理解成前面的main.cpp和hello.cpp的关系,有类似,实现手段不同罢了。

先看如何写一个DLL以及如何导出函数,读者应该先尝试用VC创建一个新的动态链接库工程,创建时选项不选空工程就可以了,这样你能得到一个示例,以便开始在这个例子基础上工作。

看看你创建的例子中的头文件有类似这样的语句:

#ifdef DLL_EXPORTS
#define DLL_API __declspec(dllexport)
#else
#define DLL_API __declspec(dllimport)
#endif 

这就是函数的导出与使用导出函数的全部奥妙了。你的DLL工程已经在工程设置中定义了一个宏DLL_EXPORTS,因此你的函数声明只要前面加DLL_API就表示把它导出,而DLL的使用者由于没有定义这个宏,所以它包含这个头文件时把你的函数看作导入的。通过模仿这个例子,你就可以写一系列的标记为导出的函数了。

导出函数还有另一种方法,是使用DEF文件,DEF文件的作用,在现在来说只是起到限定导出函数名字的作用,这里,我们要引出第二种[4]使用DLL的方法:称为显示加载,通过Windows API的LoadLibrary和GetProcAddress这两个函数来实现[5],这里GetProcAddress的参数需要一个字符串形式的函数名称,如果DLL工程中没有使用DEF文件,那么很可能你要使用非常奇怪的函数名称(形如:?fnDll@@YAHXZ)才能正确调用,这是因为C++中的函数重载机制把函数名字重新编码了,如果使用DEF文件,你可以显式指定没编码前的函数名。 
有了这些知识,你可以开始写一些简单的DLL的应用,但是我可以百分之百的肯定,你会遇到崩溃,而之前的非DLL的版本则没有问题。假如你通过显式加载来使用DLL,有可能会是调用约定不一致而引起崩溃,所谓调用约定就是函数声明前面加上__stdcall __cdecl等等限定词,注意一些宏如WINAPI会定义成这些限定词之一,不理解他们没关系,但是记住一定要保持一致,即声明和定义时一致,这在用隐式加载时不成问题,但是显示加载由于没有利用头文件,就有可能产生不一致。 
调用约定并不是我真正要说的,虽然它是一种可能。我要说的是内存分配与释放的问题。请看下面代码:

void foo(string& str)
{ str = "hello";
}
int main()
{ string str; foo(str); printf("%s\n", str.c_str()); return 0;
} 

当函数foo和main在同一个工程中,或者foo在静态库中时,不会有问题,但是如果foo是一个DLL的导出函数时,请不要这么写,它有可能会导致崩溃[6]。崩溃的原因在于“一个模块中分配的内存在另一个模块中释放”,DLL与EXE分属两个模块,例子中foo里面赋值操作导致了内存分配,而main中return语句之后,string对象析构引起内存释放。
我不想穷举全部的这类情况,只请大家在设计DLL接口时考虑清楚内存的分配释放问题,请遵循谁分配,谁释放的原则来进行。 
如果不知道该怎么设计,请抄袭我们常见的DLL接口--微软的API的做法,如:

CreateDC 
ReleaseDC

的成对调用,一个函数分配了内存,另外一个函数用来释放内存。

回到我们有可能崩溃的例子中来,怎么修改才能避免呢? 
这可以做为一个练习让读者来做,这个练习用的时间也许会比较长,如果你做好了,那么你差不多就出师了。一时想不到也不用急,我至少见过两个有五年以上经验的程序员依然犯这样的错误。

注[1]:为了说明的需要,我这里使用直接声明的方式,实际工程中是应该使用头文件的。 
注[2]: 还有线程创建与销毁也会路过DLL的入口,但是这对新手来说意义不大。 
注[3]:DEF文件格式很简单,关于DEF文件的例子,可以通过新建一个ATL COM工程看到。 
注[4]:第一种方法和使用静态库差不多,包含头文件,链接库文件,然后就像是使用普通函数一样,称为隐式加载。
注[5]:具体调用方法请参阅MSDN。 
注[6]:之所以说有可能是因为,如果两个工程的设置都是采用动态连接到运行库,那么分配释放其实都在运行库的DLL中进行,那么这种情况便不会发生崩溃

下面进一步去解释一下静态链接与动态链接~

所谓静态就是link的时候把里面需要的东西抽取出来安排到你的exe文件中,以后运行你的exe的时候不再需要lib。

所谓动态就是exe运行的时候依赖于dll里面提供的功能,没有这个dll,你的exe无法运行。   
     lib,   dll,   exe都算是最终的目标文件,是最终产物。而c/c++属于源代码。源代码和最终目标文件中过渡的就是中间代码obj,实际上之所以需要中间代码,是你不可能一次得到目标文件。比如说一个exe需要很多的cpp文件生成。而编译器一次只能编译一个cpp文件。这   
  样编译器编译好一个cpp以后会将其编译成obj,当所有必须要的cpp都编译成obj以后,再统一link成所需要的exe,应该说缺少任意一个obj都会导致exe的链接失败。

1.obj里存的是编译后的代码跟数据,并且有名称,所以在连接时会出现未解决的外部符号一说。当连成exe后便不存在名称的概念了,只有地址。静态lib就是一堆obj的组合。

2.编译器会默认链接一些常用的库,其它的需要你自己指定。

在程序的编译过程中,如果出现如下错误“unresolved symbol _some_funtion@1234”,通常是因为找不到引用过的外部函数对应的.lib文件,或者是.c.cpp源文件。

如果在c++工程中使用用c编写的.lib文件,需要做如下引用:

extern “C”{#include “headfile.h”
}

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

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

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

(2)lib文件是必须在编译期就连接到应用程序中的,而dll文件是运行期才会被调用的。有dll,则一定有对应的lib;有lib,不一定要有dll。

有两种lib文件:

静态库lib,它包含函数的二进制代码.程序link时,被复制到output文件。这个lib文件是静态编译出来的,索引和实现都在其中。这时不需要dll。静态编译的lib文件有好处:给用户安装时就不需要再挂动态库了。但也有缺点,就是导致应用程序比较大,而且失去了动态库的灵活性,在版本升级时,同时要发布新的应用程序才行。

动态库lib,它包含函数的描述和在DLL中的位置,也就是说,它为存放函数实现的dll提供索引功能,为了找到dll中的函数实现的入口点,程序link时,根据函数的位置生成函数调用的jump指令。库中的函数和数据并不复制到可执行文件中,因此在应用程序的可执行文件中,存放的不是被调用的函数代码,而是DLL中所要调用的函数的内存地址,这样当一个或多个应用程序运行是再把程序代码和被调用的函数代码链接起来,从而节省了内存资源。

DLL(dynamic link library)其实也是一种可执行文件格式。跟 .exe 文件不同的是,.dll 文件不能直接执行,他们通常由 .exe 在执行时装入,内含有一些资源以及可执行代码等。其实 Windows 的三大模块就是以 DLL 的形式提供的(Kernel32.dll,User32.dll,GDI32.dll),里面就含有了 API 函数的执行代码。为了使用 DLL 中的 API 函数,我们必须要有 API 函数的声明(.H)和其导入库(.LIB),函数的原型声明不难理解,那么导入库又是做什么用的呢?我们暂时先这样理解:导入库是为了在 DLL 中找到 API 的入口点而使用的。

一、开发和使用dll需注意三种文件

1、   dll头文件    (.h)

它是指dll中说明输出的类或符号原型或数据结构的.h文件。当其它应用程序调用dll时,需要将该文件包含入应用程序的源文件中。

2、   dll的导入库文件(.lib)

它是dll在编译、链接成功后生成的文件。主要作用是当其它应用程序调用dll时,需要将该文件引入应用程序。否则,dll无法引入。

3、   dll文件(.dll)

它是应用程序调用dll运行时,真正的可执行文件。dll应用在编译、链接成功后,.dll文件即存在。开发成功后的应用程序在发布时,只需要有.exe文件和.dll文件,不必有.lib文件和dll头文件。

程序库/模版库(LIB)

对于一些常用的函数,如printf、strcpy等,把他们编成库函数,由使用者调用,减少重复劳动和出错的可能,但编译后代码长度并没有变小。

动态链接库(DLL)     
当多个进程都需要调用某个函数时,为了节省内存空间把这些函数编成动态连接库,由多个进程动态共享。

选择使用LIB还是DLL 
要考虑应用中具体情况,比如说多少进程共享一个DLL合适,效率如何等等,更具实际做出权衡。另外,DLL也有其缺点,例如不同版本DLL的兼容性不可能做到完美。

如果同时有lib文件和dll文件生成的话,lib文件应该是dll文件的一个索引一样的东西。dll文件就是动态库功能的具体实现了。

如果你想使用lib文件,就必须: 
1     包含一个对应的头文件告知编译器lib文件里面的具体内容 
2     设置lib文件允许编译器去查找已经编译好的二进制代码

lib与dll的关系(详解静态链接库和动态链接库)相关推荐

  1. c语言库文件是dll还是lib,C语言之静态链接库与动态链接库(2)

    我们发现,无论是静态链接库还是动态链接库,最后都有lib文件,那么两者区别是什么呢? 其实,两个是完全不一样的东西.静态库对应的lib文件叫静态库,动态库对应的lib文件叫导入库.实际上静态库本身就包 ...

  2. 静态链接库与动态链接库详解

    以下内容源于C语言中文网相关内容的学习整理,如有侵权请告知删除. 一.库文件的简介 库文件,其等价为压缩包文件.该文件内部通常包含不止一个目标文件(也就是二进制文件),每个目标文件存储的代码,并非完整 ...

  3. 【四、静态库与动态库(共享库)】揭开链接库的神秘面纱:手把手教你制作静态链接库与动态链接库

    前言 不管是在 Windows 下开发,还是在 Linux 下开发,我们都会经常性的使用一些库文件,这些库文件的特点就是,我们可以看到接口的原型并通过这些接口来调用这个函数的功能,但是我们无法查看这个 ...

  4. cnsl是什么意思_VS2010下创建静态链接库和动态链接库

    VS2010下创建静态链接库和动态链接库 类封装成dll如果你的工作长期与某个领域相关,比如说长期做直接体绘制 (DVR)方面的开发,那么你可能经常使用自己的传递函数类,如果每一个工程你都把传递函数类 ...

  5. Linux下的静态链接库和动态链接库编程

    Linux下的静态链接库和动态链接库编程 参考: Linux下的静态链接库和动态链接库编程 - Histring - 博客园 Mac下静态库和动态库的创建和使用_C/C++_vincent2610的专 ...

  6. 静态链接库和动态链接库(转)

    我们可以创建一种文件里面包含了很多函数和变量的目标代码,链接的时候只要把这个文件指示给链接程序就自动地从文件中查找符合要求的函数和变量进行链接,整个查找过程根本不需要我们操心. 这个文件叫做 &quo ...

  7. UNIX环境高级编程(三)—— 静态链接库与动态链接库

    动态链接库一般不开放源码,需要建工程才能使用(其中提供的接口和 API): 0. .a/.o/.so What are .a and .so files? .o 就相当于 windows 里的 obj ...

  8. 35.静态链接库和动态链接库

    35.1.函数库的前世今生 (1)函数库就是一些事先写好的函数的集合,因为函数是模块化的,因此可以被复用:我们写好了某个函数,可以被反复使用,譬如A写好了某个函数然后共享出来,当B有相同的需求时就不需 ...

  9. C++ 创建静态链接库和动态链接库

    上篇文章演示了如何使用C++ 编译的静态链接库和动态链接库,本篇文章主要介绍如何创建静态链接库和动态链接库,本文使用的工具是visual studio 2019 企业版,需要安装对应的C++sdk,可 ...

最新文章

  1. java SocketChannel and ServerSocketChannel
  2. 文字检测与识别资料整理
  3. 【Python】数据科学家提高效率的 40 个 Python 技巧
  4. canvas1:简单介绍、开始使用、画直线+虚线
  5. Android list转xml
  6. qt中颜色对话框弹出时应用程序输出栏出现QWindowsWindow::setGeometry: Unable to set geometry 180x30+345+311 (frame: 202x8
  7. Hive 中的Mapper Reducer个数 决定因素
  8. wpf项目无法使用针式打印机_针式打印机的常见故障和解决方法2
  9. THREEJS - 模型的任意视角展示
  10. jetty jndi mysql_jetty配置jndi数据源
  11. [重庆邮电大学俱乐部] 成都普创技术总监方锋:从校园人到职业人
  12. Maven笔记 - 第十章
  13. java重构工具_Piranha(陈旧代码自动重构工具)
  14. linux usr/bin/和 usr/local/bin之间的关系,什么是软链接?
  15. 【Excel 教程系列第 16 篇】打印 Excel 表格时,如何设置每页都有表头
  16. ESP8266-Arduino编程实例-PCF8575IO扩展器驱动
  17. 日本某地发生了一件谋杀案,警察通过排查确定杀人凶手必为4个嫌疑犯的一个。
  18. 用计算机术语赞美老师,赞美老师的诗句 形容老师辛苦的诗句
  19. 做一个海纳百川的方外之人------我的极乐世界观
  20. 华为路由 鸿蒙,华为鸿蒙系统适配流程介绍

热门文章

  1. 树莓派/图像/人脸识别
  2. 计算机工程与网络学术会议,2015第五届计算机工程与网络国际学术会议
  3. wu-database-lazy-starter(懒人数据库操作-核心增量式更新)
  4. 基于FPGA的FFT
  5. js重新渲染或重新加载div
  6. MSP430 5xx/6xx Watchdog看门狗编程实例
  7. 【树莓派智能门锁】电机锁控制电源测试【2】
  8. 数据结构之二叉树 一
  9. 基于51单片机密码锁数码管显示步进电机开锁设计
  10. python求和1到100_python等差数列求和公式前 100 项的和实例