C/C++程序库深入分析

Andrew Hunt/David Thomas程序员修炼之道

在计算机科学中,库(library)是用于开发软件的子程序集合。库和可执行文件的区别是,库不是独立程序,他们是向其他程序提供服务的代码。

库是写好的现有的,成熟的,可以复用的代码。现实中每个程序都要依赖很多基础的底层库,不可能每个人的代码都从零开始,因此库的存在意义非同寻常。

动态库和静态库

库链接是指把一个或多个库包括到程序中,有两种链接形式:静态链接和动态链接,相应的,前者链接的库叫做静态库,后者的叫做动态库。


动态库和静态库编译过程

静态链接

静态链接是由链接器在链接时将库的内容加入到可执行程序中的做法。

当程序与静态库连接时,库中目标文件所含的所有将被程序使用的函数的机器码被复制到最终的可执行文件中。这就会导致最终生成的可执行代码量相对变多,占用的磁盘空间和内存空间也会变多。

在Windows中静态库是以.lib为后缀的文件,在Linux中静态库是以.a为后缀的文件。

动态链接

动态链接,在可执行文件装载时或运行时,由操作系统的装载程序加载库。

与共享库连接的可执行文件只包含它需要的函数的引用表,而不是所有的函数代码,只有在程序执行时, 那些需要的函数代码才被拷贝到内存中。这样就使可执行文件比较小, 节省磁盘空间。不过由于运行时要去链接库会花费一定的时间,执行速度相对会慢一些。

在Windows中动态库(dynamic link library)是.dll为后缀的文件,在Linux中动态库(共享库shared library)是以.so为后缀的文件。

动态库和静态库对比

静态链接的最大缺点是生成的可执行文件太大,另一个问题是静态库对程序的更新、部署和发布页会带来麻烦。如果静态库更新了,所以使用它的应用程序都需要重新编译、发布给用户(对于玩家来说,可能是一个很小的改动,却导致整个程序重新下载,全量更新)。

动态链接的最大缺点是可执行程序依赖分别存储的库文件才能正确执行。如果库文件被删除、移动、重命名或者被替换为不兼容的版本,那么可执行程序就可能工作不正常。

动态库在程序编译时并不会被连接到目标代码中,而是在程序运行是才被载入。不同的应用程序如果调用相同的库,那么在内存里只需要有一份该共享库的实例,规避了空间浪费问题。动态库在程序运行是才被载入,也解决了静态库对程序的更新、部署和发布页会带来麻烦。应用程序只需要更新动态库即可实现程序的更新。

Windows下使用VC++创建动态库

Windows下创建动态库有两种方式:使用_declspec(dllexport)和_declspec(dllimport)关键字、指定.def文件。

声明_declspec(dllexport)和_declspec(dllimport)关键字
声明函数为_declspec(dllexport),说明该函数为dll导出函数;声明函数为_declspec(dllimport)说明该函数从dll中导出。

#include <stdio.h>
#ifdef __cplusplus
extern "C" {
#endif
 __declspec(dllexport) void test()
 {
  printf("dll test\n");
 }
#ifdef __cplusplus
}
#endif

使用eXeScope加载该dll,查看导出表可以看出如下信息:

序列 地址 名字
00000001 10011127 test

指定.def文件

.def指定函数,并告知编译器不要以修饰后的函数名作为导出函数名,而以指定的函数名导出函数。

1、需要创建一个Module-Definition File(.def)文件,添加导出函数名

EXPORTS
test

注意:如果是将.txt文件改成.def文件,则需要在Visual Studio里设置:Project Property Pages→Configuration Properties→Linker→Input→Module Definition File中添加该文件

2、添加test函数代码

#include <stdio.h>
#ifdef __cplusplus
extern "C" {
#endif
  void test()
 {
  printf("dll test\n");
 }
#ifdef __cplusplus
}
#endif

使用eXeScope加载该dll,查看导出表可以看出如下信息:

序列 地址 名字
00000001 10011127 test

如果只添加.def文件,而不添加导出函数名,那么导出函数表存在,但是函数表内是空的。

如何选择?
如果导出函数调用方式采用cdecl,可以不用.def文件;如果要采用stdcall调用方式,又不想函数名被修饰,那么久采用.def文件。有关调用方式的讲解见后文。

如何调用DLL动态链接库

在Windows平台下有两种调用方式:显式调用和隐式调用。以下面的例子来阐述该问题:

// test.h
#ifdef __cplusplus
extern "C" {
#endif
 __declspec(dllexport) void test();
#ifdef __cplusplus
}
#endif
// test.cpp
#include "test.h"
#include <stdio.h>
__declspec(dllexport) void test()
{
 printf("dll test\n");
}

显式调用

显式调用通过LoadLibrary来载入动态链接库,再通过GetProcAddress函数来获取导出函数地址。

#include <windows.h>
int main()
{
 typedef void(*TESTFUNC)(void);
 TESTFUNC pTestFunc = NULL;
 HINSTANCE hInstance = ::LoadLibrary(L"dlltest.dll");
 if (!hInstance)
 {
  return -1;
 }
 
 pTestFunc = (TESTFUNC)GetProcAddress(hInstance, "test");
 if (pTestFunc)
 {
  pTestFunc();
 }
 return 0;
}

隐式调用

隐式调用通过#pragma comment(lib, “xx.lib”)的方式,将xx.lib这直接加入到工程中来链接,然后通过#include相关头文件就可以直接使用导出函数。

1、设置Include Directories和Library Directories

如果使用Visual Studio,则在Project Property Pages→Configuration Properties→VC++ Directories中设置这两个选项,这两个选项分别用来包括”test.h”所在目录和”test.lib”所在目录。

2、设置库依赖

如果使用Visual Studio,则在Project Property Pages→Configuration Properties→Linker→Input→Additional Dependencies中添加“test.lib”库。或者是直接使用pragma:

#pragma comment(lib, "test.lib")

3、调用代码如下:

#include "test.h"
#pragma comment(lib, "test.lib")
int main()
{
 test();
 return 0;
}

有关动态库的一些概念

extern C 和 Name-Mangling
Name Mangling就是一种规范编译器和链接器之间用于通信的符号表表示方法的协议,其目的在于按照程序的语言规范,使符号具备足够多的语义信息以保证链接过程准确无误的进行。

然而,C++标准并没有规定Name-Mangling的方案,这就导致了不同编译器使用了不同的方案,进而编译出来的obj文件并非通用。C标准规定了C语言Name-Mangling的规范,任何一个支持C语言的编译器,编译出来的obj文件可以共享。我们来看C和C++编译后的结果:

#include <stdio.h>
__declspec(dllexport) void test()
{
 printf("dll test\n");
}
我们分别用C++编译器和C编译器来编译这段代码,使用eXeScope加载该dll,查看导出表可以看出如下信息:

1、C++:

序列 地址 名字
00000001 10011078 ?test@YAXXXZ
2、C

序列 地址 名字
00000001 10011127 test

对于不同的C++编译器,其所得到的函数导出名称很可能不一样,在显示调用这些导出函数时就可能遇到问题,而C编译不会出现这样的问题。

使用extern C声明包裹代码,可以在C++编译器里以C编译器的方式编译代码。

#include <stdio.h>
#ifdef __cplusplus
extern "C" {
#endif
 __declspec(dllexport) void test()
 {
  printf("dll test\n");
 }
#ifdef __cplusplus
}
#endif
这段代码在C编译器和C++编译器中编译的结果都是C风格导出函数。

序列 地址 名字
00000001 10011127 test

调用约定

C和C++的默认调用方式为cdecl,在导出函数的时候,使用cdecl和__stdcall调用方式,导出的函数名字也是不一样的。见下例:

#include <stdio.h>
#ifdef __cplusplus
extern "C" {
#endif
 __declspec(dllexport) void __cdecl/__stdcall test()
 {
  printf("dll test\n");
 }
#ifdef __cplusplus
}
#endif
我们分别将test函数的调用约定声明为cdecl和stdcall,其结果如下:

1、__cdecl

序列 地址 名字
00000001 10011127 test
2、__stdcall

序列 地址 名字
00000001 10011097 _test@0
建议使用__cdecl调用方式,这样在显式调用导出函数时只需要GetProcess(hinstance, “test”)即可。

为什么需要用C语言封装动态库
在我们平时开发动态库时,即使我们的源码是C++语言,但是在导出的时候通常也会封装成C语言风格,这是为什么呢?

DLL是对应C语言的动态链接技术,如果我们的库只给C++语言使用,不会涉及到多语言调用,那么用C++封装动态库也是可以的。但是如果我们的库需要给VB、C#等语言调用时,C++语言封装的动态库需要通过各种手段才能正常使用,而用C语言封装的动态库只需要显示调用即可。

时间: 2024-12-20 23:15:16

C/C++程序库深入分析的相关文章

深入分析黑客心理 社会应加强心理治疗

随着网络的发展,黑客也越来越多起来,黑客之多原因有三: 1. 为了达到某种目的,或经济利益或商界竞争对手使黑客以这种特殊的手段去得到某种利益和达到某种政治目的; 2. 为了打击报复和宣泄个人情感,恶意搞破坏,而一次次上瘾后,而不能自拔,从此走上破坏公众网络正常秩序的道路; 3. 以不断制造出新的黑客病毒为生,因为有了这些病毒,生生不息带动了杀毒经济,从此养活了一批人. 随着中国对黑客立法打击的出台,我们看到,黑客任意破坏公众或私人的电脑不再是没有法律保障,关键是如何抓住破坏者黑客的证据,虽然我们

聊聊并发(一)深入分析Volatile的实现原理

本文属于作者原创,原文发表于InfoQ:http://www.infoq.com/cn/articles/ftf-java-volatile 引言 在多线程并发编程中synchronized和Volatile都扮演着重要的角色,Volatile是轻量级的synchronized,它在多处理器开发中保证了共享变量的"可见性".可见性的意思是当一个线程修改一个共享变量时,另外一个线程能读到这个修改的值.它在某些情况下比synchronized的开销更小,本文将深入分析在硬件层面上Inter

Backbone.js系列教程九:Backbone实用程序库

利用Backbone.noConflict()存储和创建一个特殊的(就是自定义命名空间)引用到Backbone 当Backbone被浏览器解析时,Backbone做的第一件事就是存储一个引用到包含在全局作用域(也就是window.Backbone)中 的Backbone的值.这是因为Backbone重写或占据了这个命名空间,希望给开发者机会去存储在Backbone被解析之前使用的初值.这就是 Backbone.noConflict()起作用的时候了.调用Backbone.noConflict()

带有递进逻辑的多选题如何进行深入分析

文章描述:带有递进逻辑的多选题如何进行深入分析 引子        使用调研问卷的定量研究中,为了更全面地了解研究内容.更广泛地收集信息,经常会用到多选题,但由于多选题多指向性的特点,除了频数表和交叉表(只能与单选题做交叉),较少用到其他的分析方法,损失了很多有用的信息.其实,如果调研时能善用多选题,并在分析时选取适当的方法,就能够充分利用多选题包含的信息,得到更有价值的结论.        前两篇文章分别谈到调研问卷中带有分类性质的多选题.带有求和性质的多选题如何进行分析,本文将侧重说一下带有

用户研究:带有求和性质的多选题如何进行深入分析

文章描述:调研问卷中多选题的分析方法探讨. 引子         使用调研问卷的定量研究中,为了更全面地了解研究内容.更广泛地收集信息,经常会用到多选题,但由于多选题多指向性的特点,除了频数表和交叉表(只能与单选题做交叉),较少用到其他的分析方法,损失了很多有用的信息.其实,如果调研时能善用多选题,并在分析时选取适当的方法,就能够充分利用多选题包含的信息,得到更有价值的结论.         上一篇文章谈到调研问卷中带有分类性质的多选题如何进行分析,本文将侧重说一下带有求和性质的多选题如何进行深

.NET自动字符编码识别程序库 NChardet

编码|程序 什么是NChardet      NChardet是mozilla自动字符编码识别程序库chardet的.NET实现,它移植自jchardet,chardet的java版实现,可实现对给定字符流的编码探测.  NChardet是如何工作的      NChardet通过逐个比较输入字符来猜测编码:由于是猜测,所以可能会有不能完全识别的情况:如果输入字符不能确定正确的编码,那么NChardet会给出一组可能的编码值.  如何使用NChardet     要使用NChardet来探测编码

深入分析对手是网站优化制胜关键

无论网站建设还是网站优化,如果更好的开展互联网营销,搜索引擎都是必走一步.因为现在90%的用户习惯了诸如百度.谷歌等搜索引擎,而且相对冗繁的域名来说,通过搜索引擎查询网站要方便快捷很多.如何才能在搜苏引擎上,比较靠前位置占有一席之地,也是很多网站建设人员经常思考的问题.网站建设制作符合搜索引擎的相应标准自不待说,但是网站优化绝对不是那么简单的事情,因为网站只要能被搜索引擎收录,说明其已经基本符合了标准,但是排名先后却是大有学问的事情了. 为什么竞争对手的网站能排在你网站的前面呢?这是个值得思考的

深入分析MFC文档视图结构(项目实践)

文档视图结构(Document/View Architecture)是MFC的精髓,也是Observer模式的具体实现框架之一,Document/View Architecture通过将数据和其表示分开,提供了很好的数据层次和表现层次的解耦.然而,虽然我们使用MFC AppWizard就可以很轻松地获得一个支持Document/View Architecture的MFC程序框架,Document/View Architecture本身的复杂性加上VC产生的一系列代码足够让我们眼花缭乱,一时陷入云

深入分析驴子系列(1)

一直在看驴子的代码,网上进行深入分析的文章不多,也许 这和驴子的代码量太大, 代码质量不高也许有关系.但更多的也许是不想分享,舍不得分享.其实,它本身就是开 源的 不分享人家慢慢看也能看懂 .由于时间关系 我会陆续把分析的文章帖上来,与各 位网友分享,也希望大家拍砖 进行讨论 也把你的心得分享出来..系列分析文章如果没 有特别注明 以easy mule 0.47为准 CListenSocket 类 的作用就是 监听 等待客户 端的socket 到来 并维护到来的套接字 把accept进来的套接字