您的位置:首页 > 其它

.lib和.dll.exe文件的区别和联系,以及导出dll的两种方法

2012-07-29 17:55 489 查看

注:本文内容均收集于网络后总结。

1 .lib和.dll.exe的区别

(1).动态链接库(Dynamic Link Library,缩写为DLL)是一个可以被其它应用程序共享的程序模块,其中封装了一些可以被共享的例程和资源。动态链接库文件的扩展名一般是dll,也有可能是drv、sys和fon,它和可执行文件(exe)非常类似..dll是在你的程序运行的时候才连接的文件,因此它是一种比较小的可执行文件格式,.dll还有其他的文件格式如.ocx等,所有的.dll文件都是可执行。DLL中虽然包含了可执行代码却不能单独执行,而应由Windows应用程序直接或间接调用。
动态链接是相对于静态链接而言的。所谓静态链接是指把要调用的函数或者过程链接到可执行文件中,成为可执行文件的一部分。换句话说,函数和过程的代码就在程序的exe文件中,该文件包含了运行时所需的全部代码。当多个程序都调用相同函数时,内存中就会存在这个函数的多个拷贝,这样就浪费了宝贵的内存资源。

而动态链接所调用的函数代码并没有被拷贝到应用程序的可执行文件中去,而是仅仅在其中加入了所调用函数的描述信息(往往是一些重定位信息)。仅当应用程序被装入内存开始运行时,在Windows的管理下,才在应用程序与相应的DLL之间建立链接关系。当要执行所调用DLL中的函数时,根据链接产生的重定位信息,Windows才转去执行DLL中相应的函数代码.

(2).lib是在你的程序编译连接的时候就连接的文件,因此你必须告知编译器连接的lib文件在那里。一般来说,与动态连接文件相对比,lib文件也被称为是静态连接库。当你把代码编译成这几种格式的文件时,在以后他们就不可能再被更改。如果你想使用lib文件,就必须:

①.包含一个对应的头文件告知编译器lib文件里面的具体内容

②.设置lib文件允许编译器去查找已经编译好的二进制代码

如果你想从你的代码分离一个dll文件出来代替静态连接库,仍然需要一个lib文件。这个lib文件将被连接到程序告诉操作系统在运行的时候你想用到什么dll文件,一般情况下,lib文件里有相应的dll文件的名字和一个指明dll输出函数入口的顺序表。如果不想用lib文件或者是没有lib文件,可以用WIN32 API函数LoadLibrary、GetProcAddress。事实上,我们可以在Visual C++ IDE中以二进制形式打开lib文件,大多情况下会看到ASCII码格式的C++函数或一些重载操作的函数名字。
一般我们最主要的关于lib文件的麻烦就是出现unresolved symble 这类错误,这就是lib文件连接错误或者没有包含.c、.cpp文件到工程里,关键是如果在C++工程里用了C语言写的lib文件,就必需要这样包含:

extern "C"

{

#include "myheader.h"

}

这是因为C语言写的lib文件没有C++所必须的名字破坏,C函数不能被重载,因此连接器会出错。
(3).看看别人是怎么说明三者的区别的

(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文件必须随应用程序一起发行,否则应用程序将会产生错误。

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

1、 dll头文件

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

2、 dll的引入库文件

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

3、 dll文件(.dll)

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

动态链接库 (DLL) 是作为共享函数库的可执行文件。动态链接提供了一种方法,使进程可以调用不属于其可执行代码的函数。函数的可执行代码位于一个 DLL 中,该 DLL 包含一个或多个已被编译、链接并与使用它们的进程分开存储的函数。DLL 还有助于共享数据和资源。多个应用程序可同时访问内存中单个 DLL 副本的内容。

动态链接与静态链接的不同之处在于:动态链接允许可执行模块(.dll 文件或 .exe 文件)仅包含在运行时定位 DLL 函数的可执行代码所需的信息。在静态链接中,链接器从静态链接库获取所有被引用的函数,并将库同代码一起放到可执行文件中。

使用动态链接代替静态链接有若干优点。DLL 节省内存,减少交换操作,节省磁盘空间,更易于升级,提供售后支持,提供扩展 MFC 库类的机制,支持多语言程序,并使国际版本的创建轻松完成。

lib与dll文件最大区别在调用方面

dll可以静态陷入

lib与DLL

从这一章起,我讲述的内容将特定于windows平台。其实这篇文章也可看作是我在windows下的开发经验总结,因为以后我决定转unix了。

前面有一章说编译与链接的,说得很简略,其实应该放到这一章一块儿来说的。许多单讲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,就只有这么多内容了,真的很简单,现在我们介绍另外一种类型的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文件,那么很可能你要使用非常奇怪的函数名称(形如:)才能正确调用,这是因为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中进行,那么这种情况便不会发生崩溃

dll和.lib都是程序集合,便于代码重用。都是二进制的文件。

.dll也叫动态链接库,与程序链接的方式为运行时链接(run-time linked),为PE(portable executable)格式,也就是程完整的程序。.exe、.dll、.fon、.mod、.drv、.ocx等等都是动态链接库。如.exe为系统调用的函数集合。.dll不存在同名引用,且有导出表,与导入表。

.lib也叫静态链接库,在编译时与程序链接(link-time linked),将“嵌入”到程序中。会有冗余(程序文件代码的冗余和运行时内存存储的冗余),当两个lib相链接时地址会重新建立同。在使用.lib之前,要在程序源代码中引用lib对应的头文件.h,这些头文件告诉编译器.lib中有什么。

在生成.dll时,通常会生成一个.lib。这个.lib将被编译到程序文件中,在程序运行的时候,告诉操作系统将要加载的.dll。这个.lib包括对应.dll的文件名、顺序表(ordinal table包含.dll暴露出的函数的进入点),在程序运行的时候,通过顺序表实现函数的跳转。

如果不想使用或者找不到该.lib,可以用LoadLibrary () Win32 API和GetLibrary () Win32 API。(暂时不知道怎样处理:))

VC IDE为了实现程序调试,会生成.PDB(程序数据库,二进制),里面包含源文件调用的文件信息和行信息。这样就可以逐行调试了。(不是很明白:))

打开.lib,查看其ascii码,可以看到如@@My_Function1123的函数名,这些名称在编译时被编译器运用mangling mechanism(混淆机制?不知道怎么翻译)进行了名称的mangling。

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

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

extern “C”{ #include “headfile.h”} dll---com组件dll| || |__常规dll___win32 dll| |___mfc dll| |___extended dll (所有dll不参与编译)lib|_____与obj文件类似的未编译过符号文件,与obj文件区别是声明了转入转出函数

引用一篇很好的文章

对比Windows和Linux两系统的动态库

http://www.linux-cn.com/html/test/20070411/2287.html

摘要:动态链接库技术实现和设计程序常用的技术,在Windows和Linux系统中都有动态库的概念,采用动态库可以有效的减少程序大小,节省空间,提高效率,增加程序的可扩展性,便于模块化管理。但不同操作系统的动态库由于格式 不同,在需要不同操作系统调用时需要进行动态库程序移植。本文分析和比较了两种操作系统动态库技术,并给出了把Visual C++编制的动态库移植到Linux上的方法和经验。

1、引言

动态库(Dynamic Link Library abbr,DLL)技术是程序设计中经常采用的技术。其目的减少程序的大小,节省空间,提高效率,具有很高的灵活性。采用动态库技术对于升级软件版本更加容易。与静态库(Static Link Library)不同,动态库里面的函数不是执行程序本身的一部分,而是根据执行需要按需载入,其执行代码可以同时在多个程序中共享。

在Windows和Linux操作系统中,都可采用这种方式进行软件设计,但他们的调用方式以及程序编制方式不尽相同。本文首先分析了在这两种操作系统中 通常采用的动态库调用方法以及程序编制方式,然后分析比较了这两种方式的不同之处,最后根据实际移植程序经验,介绍了把VC++编制的Windows动态 库移植到Linux下的方法。

2、动态库技术

2.1 Windows动态库技术

动态链接库是实现Windows应用程序共享资源、节省内存空间、提高使用效率的一个重要技术手段。常见的动态库包含外部函数和资源,也有一些动态库只包 含资源,如Windows字体资源文件,称之为资源动态链接库。通常动态库以.dll,.drv、.fon等作为后缀。相应的windows静态库通常 以.lib结尾,Windows自己就把一些主要的系统功能以动态库模块的形式实现。

Windows动态库在运行时被系统加载到进程的虚拟空间中,使用从调用进程的虚拟地址空间分配的内存,成为调用进程的一部分。DLL也只能被该进程的线 程所访问。DLL的句柄可以被调用进程使用;调用进程的句柄可以被DLL使用。DLL模块中包含各种导出函数,用于向外界提供服务。DLL可以有自己的数 据段,但没有自己的堆栈,使用与调用它的应用程序相同的堆栈模式;一个DLL在内存中只有一个实例;DLL实现了代码封装性;DLL的编制与具体的编程语 言及编译器无关,可以通过DLL来实现混合语言编程。DLL函数中的代码所创建的任何对象(包括变量)都归调用它的线程或进程所有。

根据调用方式的不同,对动态库的调用可分为静态调用方式和动态调用方式。

(1)静态调用,也称为隐式调用,由编译系统完成对DLL的加载和应用程序结束时DLL卸载的编码(Windows系统负责对DLL调用次数的计数),调 用方式简单,能够满足通常的要求。通常采用的调用方式是把产生动态连接库时产生的.LIB文件加入到应用程序的工程中,想使用DLL中的函数时,只须在源 文件中声明一下。 LIB文件包含了每一个DLL导出函数的符号名和可选择的标识号以及DLL文件名,不含有实际的代码。Lib文件包含的信息进入到生成的应用程序中,被调 用的DLL文件会在应用程序加载时同时加载在到内存中。

(2)动态调用,即显式调用方式,是由编程者用API函数加载和卸载DLL来达到调用DLL的目的,比较复杂,但能更加有效地使用内存,是编制大型应用程序时的重要方式。在Windows系统中,与动态库调用有关的函数包括:

①LoadLibrary(或MFC 的AfxLoadLibrary),装载动态库。

②GetProcAddress,获取要引入的函数,把符号名或标识号转换为DLL内部地址。

③FreeLibrary(或MFC的AfxFreeLibrary),释放动态链接库。

在windows中创建动态库也非常方便和简单。在Visual C++中,可以创建不用MFC而直接用C语言写的DLL程序,也可以创建基于MFC类库的DLL程序。每一个DLL必须有一个入口点,在VC++ 中,DllMain是一个缺省的入口函数。DllMain负责初始化(Initialization)和结束(Termination)工作。动态库输出 函数也有两种约定,分别是基于调用约定和名字修饰约定。DLL程序定义的函数分为内部函数和导出函数,动态库导出的函数供其它程序模块调用。通常可以有下 面几种方法导出函数:

①采用模块定义文件的EXPORT部分指定要输入的函数或者变量。

②使用MFC提供的修饰符号_declspec(dllexport)。

③以命令行方式,采用/EXPORT命令行输出有关函数。

在windows动态库中,有时需要编写模块定义文件(.DEF),它是用于描述DLL属性的模块语句组成的文本文件。

2.2 Linux共享对象技术

在Linux操作系统中,采用了很多共享对象技术(Shared Object),虽然它和Windows里的动态库相对应,但它并不称为动态库。相应的共享对象文件以.so作为后缀,为了方便,在本文中,对该概念不进 行专门区分。Linux系统的/lib以及标准图形界面的/usr/X11R6/lib等目录里面,就有许多以so结尾的共享对象。同样,在Linux 下,也有静态函数库这种调用方式,相应的后缀以.a结束。Linux采用该共享对象技术以方便程序间共享,节省程序占有空间,增加程序的可扩展性和灵活 性。Linux还可以通过LD-PRELOAD变量让开发人员可以使用自己的程序库中的模块来替换系统模块。

同Windows系统一样,在Linux中创建和使用动态库是比较容易的事情,在编译函数库源程序时加上-shared选项即可,这样所生成的执行程序就 是动态链接库。通常这样的程序以so为后缀,在Linux动态库程序设计过程中,通常流程是编写用户的接口文件,通常是.h文件,编写实际的函数文件, 以.c或.cpp为后缀,再编写makefile文件。对于较小的动态库程序可以不用如此,但这样设计使程序更加合理。

编译生成动态连接库后,进而可以在程序中进行调用。在Linux中,可以采用多种调用方式,同Windows的系统目录(..\system32等)一 样,可以把动态库文件拷贝到/lib目录或者在/lib目录里面建立符号连接,以便所有用户使用。下面介绍Linux调用动态库经常使用的函数,但在使用 动态库时,源程序必须包含dlfcn.h头文件,该文件定义调用动态链接库的函数的原型。

(1)_打开动态链接库:dlopen,函数原型void *dlopen (const char *filename, int flag);

dlopen用于打开指定名字(filename)的动态链接库,并返回操作句柄。

(2)取函数执行地址:dlsym,函数原型为: void *dlsym(void *handle, char *symbol);

dlsym根据动态链接库操作句柄(handle)与符号(symbol),返回符号对应的函数的执行代码地址。

(3)关闭动态链接库:dlclose,函数原型为: int dlclose (void *handle);

dlclose用于关闭指定句柄的动态链接库,只有当此动态链接库的使用计数为0时,才会真正被系统卸载。

(4)动态库错误函数:dlerror,函数原型为: const char *dlerror(void); 当动态链接库操作函数执行失败时,dlerror可以返回出错信息,返回值为NULL时表示操作函数执行成功。

在取到函数执行地址后,就可以在动态库的使用程序里面根据动态库提供的函数接口声明调用动态库里面的函数。在编写调用动态库的程序的makefile文件时,需要加入编译选项-rdynamic和-ldl。

除了采用这种方式编写和调用动态库之外,Linux操作系统也提供了一种更为方便的动态库调用方式,也方便了其它程序调用,这种方式与Windows系统 的隐式链接类似。其动态库命名方式为“lib*.so.*”。在这个命名方式中,第一个*表示动态链接库的库名,第二个*通常表示该动态库的版本号,也可 以没有版本号。在这种调用方式中,需要维护动态链接库的配置文件/etc/ld.so.conf来让动态链接库为系统所使用,通常把动态链接库所在目录名 追加到动态链接库配置文件中。如具有X window窗口系统发行版该文件中都具有/usr/X11R6/lib,它指向X
window窗口系统的动态链接库所在目录。为了使动态链接库能为系统所共享,还需运行动态链接库的管理命令./sbin/ldconfig。在编译所引 用的动态库时,可以在gcc采用 –l或-L选项或直接引用所需的动态链接库方式进行编译。在Linux里面,可以采用ldd命令来检查程序依赖共享库。

3、两种系统动态库比较分析

Windows和Linux采用动态链接库技术目的是基本一致的,但由于操作系统的不同,他们在许多方面还是不尽相同,下面从以下几个方面进行阐述。

(1)动态库程序编写,在Windows系统下的执行文件格式是PE格式,动态库需要一个DllMain函数作为初始化的人口,通常在导出函数的声明时需 要有_declspec(dllexport)关键字。Linux下的gcc编译的执行文件默认是ELF格式,不需要初始化入口,亦不需要到函数做特别声 明,编写比较方便。

(2)动态库编译,在windows系统下面,有方便的调试编译环境,通常不用自己去编写makefile文件,但在linux下面,需要自己动手去编写makefile文件,因此,必须掌握一定的makefile编写技巧,另外,通常Linux编译规则相对严格。

(3)动态库调用方面,Windows和Linux对其下编制的动态库都可以采用显式调用或隐式调用,但具体的调用方式也不尽相同。

(4)动态库输出函数查看,在Windows中,有许多工具和软件可以进行查看DLL中所输出的函数,例如命令行方式的dumpbin以及VC++工具中 的DEPENDS程序。在Linux系统中通常采用nm来查看输出函数,也可以使用ldd查看程序隐式链接的共享对象文件。

(5)对操作系统的依赖,这两种动态库运行依赖于各自的操作系统,不能跨平台使用。因此,对于实现相同功能的动态库,必须为两种不同的操作系统提供不同的动态库版本。

4、动态库移植方法

如果要编制在两个系统中都能使用的动态链接库,通常会先选择在Windows的VC++提供的调试环境中完成初始的开发,毕竟VC++提供的图形化编辑和 调试界面比vi和gcc方便许多。完成测试之后,再进行动态库的程序移植。通常gcc默认的编译规则比VC++默认的编译规则严格,即使在VC++下面没 有任何警告错误的程序在gcc调试中也会出现许多警告错误,可以在gcc中采用-w选项关闭警告错误。

下面给出程序移植需要遵循的规则以及经验。

(1)尽量不要改变原有动态库头文件的顺序。通常在C/C++语言中,头文件的顺序有相当的关系。另外虽然C/C++语言区分大小写,但在包含头文件 时,Linux必须与头文件的大小写相同,因为ext2文件系统对文件名是大小写敏感,否则不能正确编译,而在Windows下面,头文件大小写可以正确 编译。

(2)不同系统独有的头文件。在Windows系统中,通常会包括windows.h头文件,如果调用底层的通信函数,则会包含winsock..h头文 件。因此在移植到Linux系统时,要注释掉这些Windows系统独有的头文件以及一些windows系统的常量定义说明,增加Linux都底层通信的 支持的头文件等。

(3)数据类型。VC++具有许多独有的数据类型,如__int16,__int32,TRUE,SOCKET等,gcc编译器不支持它们。通常做法是需 要把windows.h和basetypes.h中对这些数据进行定义的语句复制到一个头文件中,再在Linux中包含这个头文件。例如把套接字的类型为 SOCKET改为int。

(4)关键字。VC++中具有许多标准C中所没有采用的关键字,如BOOL,BYTE,DWORD,__asm等,通常在为了移植方便,尽量不使用它们,如果实在无法避免可以采用#ifdef 和#endif为LINUX和WINDOWS编写两个版本。

(5)函数原型的修改。通常如果采用标准的C/C++语言编写的动态库,基本上不用再重新编写函数,但对于系统调用函数,由于两种系统的区别,需要改变函 数的调用方式等,如在Linux编制的网络通信动态库中,用close()函数代替windows操作系统下的closesocket()函数来关闭套接 字。另外在Linux下没有文件句柄,要打开文件可用open和fopen函数,具体这两个函数的用法可参考文献[2]。

(6)makefile的编写。在windows下面通常由VC++编译器来负责调试,但gcc需要自己动手编写makefile文件,也可以参照 VC++生成的makefile文件。对于动态库移植,编译动态库时需要加入-shared选项。对于采用数学函数,如幂级数的程序,在调用动态库是,需 要加入-lm。

(7)其它一些需要注意的地方

①程序设计结构分析,对于移植它人编写的动态库程序,程序结构分析是必不可少的步骤,通常在动态库程序中,不会包含界面等操作,所以相对容易一些。

②在Linux中,对文件或目录的权限分为拥有者、群组、其它。所以在存取文件时,要注意对文件是读还是写操作,如果是对文件进行写操作,要注意修改文件或目录的权限,否则无法对文件进行写。

③指针的使用,定义一个指针只给它分配四个字节的内存,如果要对指针所指向的变量赋值,必须用malloc函数为它分配内存或不把它定义为指针而定义为变 量即可,这点在linux下面比windows编译严格。同样结构不能在函数中传值,如果要在函数中进行结构传值,必须把函数中的结构定义为结构指针。

④路径标识符,在Linux下是“/”,在Windows下是“\”,注意windows和Linux的对动态库搜索路径的不同。

⑤编程和调试技巧方面。对不同的调试环境有不同的调试技巧,在这里不多叙述。

5、结束语

本文系统分析了windows和Linux动态库实现和使用方式,从程序编写、编译、调用以及对操作系统依赖等方面综合分析比较了这两种调用方式的不同之 处,根据实际程序移植经验,给出了把VC++编制的Windows动态库移植到Linux下的方法以及需要注意的问题,同时并给出了程序示例片断,实际在 程序移植过程中,由于系统的设计等方面,可能移植起来需要注意的方面远比上面复杂,本文通过总结归纳进而为不同操作系统程序移植提供了有意的经验和技巧。

2. 导出(输出)dll的两种方法
(1).DLL的Export和Import介绍

DLL的export是指将DLL中的函数和数据输出到其它程式中,以供其使用。DLL的import是指使用DLL的程式引入DLL中的函数和数据。
DLL的export
DLL中包含有一个表,称为export table(以下简称ET),其中包含了DLL中可以被外部程式使用的所有函数和数据的名字。只有记录在ET中的函数和数据才可以被外部程式所使用(如果没有.DEF文件的话),其它所有没有记录在ET中的函数和数据都被视为是DLL私有的。因此,要将DLL中的函数和数据export只有两个方法:
l 为DLL创建一个.DEF文件(模块定义文件),并在build该DLL时使用这个.DEF文件。使用这种方法使你可以将函数按序号export。
l 在DLL中想要export的函数和数据定义前添加_declspec(dllexport)关键字(对于函数和变量定义,加在最前面;对于class定义,加在class关键字后),这样该函数和数据就会被添加到ET中。使用这种方法函数将按名字export。
Windows下,无论使用上述的哪一种方法,都必须要将export函数声明为_stdcall。
关于C和C++的兼容问题
如果要写C和C++兼容的DLL,因为在C和C++下使用了不同的名字修饰规则以及不同的调用约定,所以,如果DLL是用C编写和编译的,则在用于C++模块时,函数的声明前应加上extern “C”关键字,以告诉LINKER使用C外部连接(即按照C名字修饰规则在外部模块中寻找函数);反之,如果DLL是用C++编写和编译的,则在用于C模块时,函数的声明前要加上extern “C++”关键字。VC++通过_cplusplus宏来标识C++程式。如果是C++程式,VC编译器就会为你定义_cplusplus宏。所以在DLL中可以使用如下的技术来解决兼容问题:
#ifdef _cplusplus
extern “C” {
#endif
// 将所有的函数声明放在这里
#ifdef _cplusplus
}
#endif
.DEF文件
.DEF文件是包含了DLL模块信息的文本文件。其语法结构如下:
LIBRARY DLL file name
DESCRIPTION “descriptions”
EXPORTS
Function names @nums
LIBRARY为关键字,后面紧跟关联的DLL文件名;DESCRIPTION后为可选的描述字符串,除了增加可读性外没什么用处;EXPORTS后是export函数的列表,首先是函数名,然后是@符号,后紧跟一十进制数,为该函数的标号,范围从1到DLL可export函数的总数。注意,这里的名字是经过名字修饰后的函数名字,如果是DLL是用C++写的话,那么就很郁闷了。
如果是扩展DLL(extension DLL),并且通过.DEF文件export,那么必须在头文件中添加如下的语句:
#undef AFX_DATA

#define AFX_DATA AFX_EXT_DATA

// 头文件中的其它内容

 #undef AFX_DATA

#define AFX_DATA

这些语句确保一些MFC中内部使用的变量被export到外部程式中。例如:在class中通过DECLARE_DYNAMIC获得的CRuntimeClass变量。否则DLL将会无法正确地编译和连接,或外部程式无法正确连接到该DLL。
DLL的import
外部程式的一个源文件要使用DLL中的函数和数据,就像要使用外部模块中的函数和数据一样,必须首先给出函数和数据的声明;对于class则要给出类的定义。这就称为import。对于VC编译器,Import DLL的函数和数据的语法与一般的声明类似,但要在前面加上_declspec(dllimport)关键字(对于函数和变量声明,加在最前面;对于class定义,加在class关键字后)。如果是函数,则该关键字是可选的,但使用该关键字有可能会导致编译器产生较高效的代码。但对于变量和class,则必须使用该关键字。
通过使用以下的技术,可以编写在.LIB文件和外部程序源文件通用的头文件:
#ifdef _EXPORTING

#define CLASS_DECLSPEC    __declspec(dllexport)

#else

#define CLASS_DECLSPEC    __declspec(dllimport)

#endif

编译器提供的_EXPORTING宏可以用于标式该源文件来自DLL文件还是外部程式。
(2)._declspec(dllexport)与_declspec(dllimport)

写WIN32程序的人,做过DLL,都会很清楚__declspec(dllexport)的作用,它就是为了省掉在DEF文件中手工定义导出哪些函数的一个方法。当然,如果你的DLL里全是C++的类的话,你无法在DEF里指定导出的函数,只能用__declspec(dllexport)导出类。但是,MSDN文档里面,对于__declspec(dllimport)的说明让人感觉有点奇怪,先来看看MSDN里面是怎么说的:

不使用 __declspec(dllimport) 也能正确编译代码,但使用 __declspec(dllimport) 使编译器可以生成更好的代码。编译器之所以能够生成更好的代码,是因为它可以确定函数是否存在于 DLL 中,这使得编译器可以生成跳过间接寻址级别的代码,而这些代码通常会出现在跨 DLL 边界的函数调用中。但是,必须使用 __declspec(dllimport) 才能导入 DLL 中使用的变量。

初看起来,这段话前面的意思是,不用它也可以正常使用DLL的导出库,但最后一句话又说,必须使用 __declspec(dllimport) 才能导入 DLL 中使用的变量这个是什么意思??

那我就来试验一下,假定,你在DLL里只导出一个简单的类,注意,我假定你已经在项目属性中定义了 SIMPLEDLL_EXPORT

SimpleDLLClass.h

#ifdef SIMPLEDLL_EXPORT

#define DLL_EXPORT __declspec(dllexport)

#else

#define DLL_EXPORT

#endif

class DLL_EXPORT SimpleDLLClass

{

public:

SimpleDLLClass();

virtual ~SimpleDLLClass();

virtual getValue() { return m_nValue;};

private:

int m_nValue;

};

SimpleDLLClass.cpp

#include "SimpleDLLClass.h"

SimpleDLLClass::SimpleDLLClass()

{

m_nValue=0;

}

SimpleDLLClass::~SimpleDLLClass()

{

}

然后你再使用这个DLL类,在你的APP中include SimpleDLLClass.h时,你的APP的项目不用定义 SIMPLEDLL_EXPORT 所以,DLL_EXPORT 就不会存在了,这个时候,你在APP中,不会遇到问题。这正好对应MSDN上说的__declspec(dllimport)定义与否都可以正常使用。但我们也没有遇到变量不能正常使用呀。 那好,我们改一下SimpleDLLClass,把它的m_nValue改成static,然后在cpp文件中加一行

int SimpleDLLClass::m_nValue=0;

如果你不知道为什么要加这一行,那就回去看看C++的基础。 改完之后,再去LINK一下,你的APP,看结果如何, 结果是LINK告诉你找不到这个m_nValue。明明已经定义了,为什么又没有了?? 肯定是因为我把m_nValue定义为static的原因。但如果我一定要使用Singleton的Design Pattern的话,那这个类肯定是要有一个静态成员,每次LINK都没有,那不是完了? 如果你有Platform SDK,用里面的Depend程序看一下,DLL中又的确是有这个m_nValue导出的呀。

再回去看看我引用MSDN的那段话的最后一句。 那我们再改一下SimpleDLLClass.h,把那段改成下面的样子:

#ifdef SIMPLEDLL_EXPORT

#define DLL_EXPORT __declspec(dllexport)

#else

#define DLL_EXPORT __declspec(dllimport)

#endif

再LINK,一切正常。原来dllimport是为了更好的处理类中的静态成员变量的,如果没有静态成员变量,那么这个__declspec(dllimport)无所谓。

_declspec(dllexport)与_declspec(dllimport)都是DLL内的关键字,即导出与导入。他们是将DLL内部的类与函数以及数据导出与导入时使用的。主要区别在于,dllexport是在这些类、函数以及数据的申明的时候使用。用过表明这些东西可以被外部函数使用,即(dllexport)是把DLL中的相关代码(类,函数,数据)暴露出来为其他应用程序使用。使用了(dllexport)关键字,相当于声明了紧接在(dllexport)关键字后面的相关内容是可以为其他程序使用的。而dllimport关键字是在外部程序需要使用DLL内相关内容时使用的关键字。当一个外部程序要使用DLL内部代码(类,函数,全局变量)时,只需要在程序内部使用(dllimport)关键字声明需要使用的代码就可以了,即(dllimport)关键字是在外部程序需要使用DLL内部相关内容的时候才使用。(dllimport)作用是把DLL中的相关代码插入到应用程序中。

  _declspec(dllexport)与_declspec(dllimport)是相互呼应,只有在DLL内部用dllexport作了声明,才能在外部函数中用dllimport导入相关代码。实际上,在应用程序访问DLL时,实际上就是应用程序中的导入函数与DLL文件中的导出函数进行链接。而且链接的方式有两种:隐式迎接和显式链接。

  隐式链接是指通过编译器提供给应用程序关于DLL的名称和DLL函数的链接地址,面在应用程序中不需要显式地将DLL加载到内存,即在应用程序中使用dllimport即表明使用隐式链接。不过不是所有的隐式链接都使用dllimport。

  显式链接刚同应用程序用语句显式地加载DLL,编译器不需要知道任何关DLL的信息。

(3).在VC中不用MFC如何制作dll,导出dll以供其他模块(dll.或exe)使用

方法一:使用export 和 import
在VC中建立一个Console Application,建立2个文件:Dll.h 和 Dll.cpp
Dll.h
#ifdef MYLIBAPI

#else

#define MYLIBAPI extern "C" _declspec (dllimport)

#end if
MYLIBAPI int Add (int iLeft, int iRight)

MYLIBAPI int Sub (int iLeft, int iRight)
Dll.cpp
#define MYLIBAPI extern "C" _declspec (dllexport)
#include "Dll.h"
int Add (int iLeft, int iRight)

{

return iLeft + iRight ;

}
int Sub (int iLeft, int iRight)

{

return iLeft - iRight ;

}
保存文件。

在Project->setting->link 最下面加上 “/dll”, "/"之前一定要与前一项

有空格。

然后编译,就可以在debug 或 release下面找到dll 和 lib 文件了

使用的时候包含dll.h文件

方法二:使用def文件

建立一个console application, 建立2个文件dll.h 和 dll.cpp
Dll.h
int Add (int iLeft, int iRight) ;

int Sub (int iLeft, int iRight) ;
Dll.cpp
#include "Dll.h"
int Add (int iLeft, int iRight)

{

return iLeft + iRight ;

}
int Sub (int iLeft, int iRight)

{

return iLeft - iRight ;

}
然后再当前目录下面建立一个.def文件,文件名最好和要输出的dll名字一样,扩展名

为.def, 里面写上:
LIBRARY dllname.dll

EXPORTS

Add @1

Add @2

然后将这个文件添加到工程中,

在link中设置 /dll, 然后编译

在debug或release中就可以找到dll和lib了

使用的时候加上dll.h文件

参考文献:
(1)http://hi.baidu.com/benzhan/item/a38c4bc84e07030dac092ffb
(2)http://blog.csdn.net/Albert_1030/archive/2008/10/15/3078120.aspx
(3) http://blog.sina.com.cn/s/blog_4b9b714a0100gzip.html
(4)http://dev.21tx.com/2005/02/15/11088.html
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: