1、什么是库
库文件是一些预先编译好的函数的集合,那些函数都是按照可再使用的原则编写的。它们通常由一组互相关联的用来完成某项常见工作的函数构成,从本质上来说库是一种可执行代码的二进制形式,可以被操作系统载入内存执行。
库是别人编写好的现有的、成熟的,可以复用的代码,你可以使用但要记得遵守许可协议。库是一种软件组件技术,库里面封装了数据和函数。库的使用可以使程序模块化。
现实中每个程序都要依赖很多基础的底层库,不可能每个人的代码都从零开始,因此库的存在意义非同寻常。
2、库的分类
由于Windows和Linux的本质不同,因此二者的库的二进制是不兼容的。
Windows下的库有两种:静态链接库(.lib文件)和动态链接库(.dll文件)。
Linux下的库有两种:静态库(.a)和共享库(.so)。
Linux通常把库文件存放在/usr/lib或/lib目录下。
Linux库文件名由:前缀lib+库名+后缀3部分组成,其中动态链接库以.so最为后缀,静态链接库通常以.a作为后缀。
libxxxx.a,其中xxxx是该lib的名称;动态库的名字一般为libxxxx.so.major.minor,xxxx是该lib的名称,major是主版本号,minor是副版本号。
在程序中使用使用静态库和动态库时,他们载入的顺序是不同的。静态库的代码在编译时就拷贝的应用程序中,这样的优点是节省编译时间。动态链接库时程序在开始运行后调用库函数时才被载入。
3、Windows下的库与Linux下的库的异同
Linux 的共享库(.so)就象Windows的动态链接库(.dll),它里面包含有很多程序常用的函数。为了方便程序开发和减少程序的冗余,程序当中就不用包含每个常用函数的拷贝,只是在需要时调用共享库中常函数即可。这种方式我们称之为动态链接(Dynamically Linked)。而有时我们不希望叫程序去调用共享库的函数,而是把库函数代码直接链接进程序代码中,也就是说,在程序本身拥有一份共享库中函数的副本。 这种方式我们称之为静态链接(Statically Linked)。
.dll文件事实上和.exe文件一样,同属 PE 格式的执行文件。对于隐式的引用外部符号,需要把外部符号所在的位置写在PE头上。PE加载器将从PE头上找到依赖的符号表,并加载依赖的其它.dll文件。而在Linux 上并非如此,.so文件大多为elf执行文件格式。当它们需要的外部符号,可以不写明这些符号所在的位置。也就是说,通常.so文件本身并不知道它依赖的那些符号在哪些.so里面。这些符号是由调用dlopen的进程运行时提供的。
我们在Windows下做一个.dll文件时还需要携带一个.lib文件;而在Linux下一般只需要有相应的头文件就够了。对于编写新的.so,找不到的 符号可以就让它在那里,直到最终执行文件来把所有需要的符号联合到一起。windows 可以存在一个.dll对另一个.dll的隐式依赖;而Linux下一般不需要让.so和.so有隐式依赖关系。
4、Linux下的两种库
静态库(.a)和共享库(.so),二者的不同点在于代码被载入的时刻不同。
静态库在程序编译时会被连接到目标代码中,被载入可执行程序,程序运行时将不在需要该静态库,因此体积较大。动态库在程序编译时不会被连接到目标代码中,而是在程序运行时才被载入内存,在编译过程中仅简单的引用,因此在程序运行时还需要动态库存在,因此代码体积较小。
静态库是一个或者多个obj文件的打包,所以有人干脆把从obj文件生成lib的过程称为Archive,即合并到一起。比如你链接一个静态库,如果其中有错,它会准确的找到是哪个obj有错,即静态lib只是壳子。动态库一般会有对应的导入库,方便程序静态载入动态链接库,否则你可能就需要自己LoadLibary调入DLL文件,然后再手工GetProcAddress获得对应函数了。有了导入库,你只需要链接导入库后按照头文件函数接口的声明调用函数就可以了。
静态库对函数库的链接是放在编译时期完成的,程序在运行时与函数库再无瓜葛,移植方便。但是静态库浪费空间和资源,因为所有相关的对象文件与牵涉到的函数库被链接合成一个可执行文件。
动态库把对一些库函数的链接载入推迟到程序运行的时期。可以实现进程之间的资源共享。将一些程序升级变得简单,甚至可以真正做到链接载入完全由程序员在程序代码中控制。
动态链接库的迷你形式为“libxx.so”后缀名为“.so”,针对于实际库文件,每个共享库都有个特殊的名字“soname”。在程序启动后,程序通过这个名字来告诉动态加载器该载入哪个共享库。在文件系统中,soname仅是一个链接到实际动态库的链接。对于动态库而言,每个库实际上都有另一个名字给编译器来用。它是一个指向实际库镜像文件的链接文件。这个时候soname是没有版本号的。
4.1、如何创建静态库
静态库,也称为归档文件(archive),按照惯例它们的名字都以 .a 结尾。比如,标准C语言函数库 /usr/lib/libc.a 和 X11 函数库 /usr/x11/lib/libx11.a .
我们可以容易地创建和维护自已的静态库,只要使用 ar 程序和 gcc -c命令对函数进行分别编译就可以了。
静态库的创建和使用:
(1)在一个头文件种声明静态库所导出的函数。
(2)在一个源文件种实现静态库所导出的函数。
(3)编译源文件,生成可执行代码。
(4)将可执行代码所在的目标文件加入到某个静态库中,并将静态库拷贝到系统默认的存放库文件的目录下。
下面通过一个例子来说明:mylib.h种存放的是静态库提供给用户使用的函数的声明,mylib.c实现了mylib.h种声明的函数。
/*mylib.h*/
#ifndef _MYLIB_H_
#define _MYLIB_H_
void weclome(void);
void outString(const char *str);
#endif
/*mylib.c*/
#include "mylib.h"
#include
void welcome(void)
{
printf("welcome to libmylib\n");
}
void outString(const char *str)
{
if(str != NULL)
printf("%s\n
(1)编译mylib.c:产生要包含在库文件中的目标。这通过调用带有 -c 选项的 C 语言编译器来实现。-c 选项的作用是阻止编译器创建一个完整的程序。
gcc -o mylib.o -c mylib.c
(2)将目标文件加入到静态库中:创建并使用一个库文件。 我们用 ar 程序创建一个归档文件并将目标文件添加进去。这个程序之所以称为 ar,是因为它将若干个单独的文档归并到一个大的文件中以创建归档文档或集合。注意,我们也可以用 ar 程序来创建任何类型文件的归档文件 (请注意,ar 只是一个通用工具.)
ar rcs libmylib.a mylib.o
(3)将静态库copy到Linux的库目录(/usr/lib或者/lib)下:
cp libmylib.a /usr/lib/libmylib.a
编写调用库函数的测试程序test.c:
#include "mylib.h"
#include
int main(void)
{
printf("create and use library:\n");
welcome();
outString("it's successful\n");
return 0;
}
(4)使用静态库编译:
gcc -o testtest.c -l mylib
这里注意,编译时无需带上前缀和后缀。
(5)运行可执行程序
test: ./test
create and use library:
welcome to libmylib
it's successful
在Linxu下,可以使用ar命令来创建和修改静态库。
这些在linux下man ar一下就可以得到参数,这里说明几个常用的
d:从库中删除成员文件。
r:在库中加入成员文件,若存在,则替换。
c:创建一个库。
s:无论ar命令是否修改了库内容,都强制重新生成库符号表。
其他的命令可是使用man查看。
总结:
具体流程:
A、将写好的一个或多个.c文件编译成.o文件,例如:gcc - c xx.c -o xx.o
libxxxx.a
命令。
-lxxxx(-l+库名)或 gcc -o main main.c libxxxx.a(库文件)
gcc参数解析:
-shared:指定生成动态链接库
-static:指定生成静态链接库
-fPIC:表示编译为位置独立的代码,用于编译动态库。目标文件需要创建成位置无关码,概念上就是在可执行程序装载他们的时候,他们可以放在可执行程序的内存的任何地方。
-L:表示要连接的库在当前目录中。
-l:指定链接时需要的动态库。编译器查找动态链接库时有隐含的命名规则,即在给出的名字前面加上lib,后面加上.so来确定库的名称。
-Wall:生成所有警告信息。
-ggdb:此选项将尽可能的生成gdb的可以使用的调试信息。
-g:编译器在编译的时候产生调试信息。
-c:只激活预处理、编译和汇编,也就是把程序做成目标文件(.o文件)
-Wl,options:把参数传递给连接器ld。如果options中间有逗号,就将options分成多个选项,然后传递给链接程序。
4.2、如何创建共享库
库用于将相似函数打包在一个单元中。然后这些单元就可为其他开发人员所共享,并因此有了模块化编程这种说法— 即,从模块中构建程序。Linux支持两种类型的库,每一种库都有各自的优缺点。静态库包含在编译时静态绑定到一个程序的函数。动态库则不同,它是在加载应用程序时被加载的,而且它与应用程序是在运行时绑定的。
使用共享库的方法有两种:您既可以在运行时动态链接库,也可以动态加载库并在程序控制之下使用它们。本文对这两种方法都做了探讨。
静态库较适宜于较小的应用程序,因为它们只需要最小限度的函数。而对于需要多个库的应用程序来说,则适合使用共享库,因为它们可以减少应用程序对内存(包括运行时中的磁盘占用和内存占用)的占用。这是因为多个应用程序可以同时使用一个共享库;因此,每次只需要在内存上复制一个库。要是静态库的话,每一个运行的程序都要有一份库的副本。
GNU/linux 提供两种处理共享库的方法(每种方法都源于Sun Solaris)。您可以动态地将程序和共享库链接并让 Linux 在执行时加载库(如果它已经在内存中了,则无需再加载)。另外一种方法是使用一个称为动态加载的过程,这样程序可以有选择地调用库中的函数。使用动态加载过程,程序可以先加载一个特定的库(已加载则不必),然后调用该库中的某一特定函数(图 2 展示了这两种方法)。这是构建支持插件的应用程序的一个普遍的方法。我稍候将在本文探讨并示范该应用程序编程接口(API)。
Linux下的共享库类似windows下的dll,共命令约定如下:
静态库一般由字母lib 开头,并有 .a 的扩展名,而共享对象有两个不同的名称:soname 和 real name。
soname 包含前缀 "lib",然后紧跟库名,其次是".so"(后面紧跟另一个圆点),以及表明主版本号的数字。
soname 可以由前缀的路径信息来限定。realname 是包含库的已编译代码的真正文件名。
libxxxx.so.major.minor,其中,xxxx是库的名字,major是主版本号,minor 是次版本号或叫发布号,次版本号和其相应的圆点是可选的。
soname是记录在共享库中的,其它库使用这个共享库时,实际上只需要的提供soname,动态链接器会找到名称是soname的动态库给程序使用。
这种带版本号的共享库主要是为了你可以很方便的升级你的函数库,如果某个API改变了,创建库的程序会改变主版本号,然而,如果一个函数升级了某个函数库,而功能没有发生变化,这时只需要改变次版本号,由于只改变了次版本号,所以soname没有发生改变,这样就可以做到与旧的共享库保持兼容。
下面简要说明动态库的编写过程:
/* file libhello.h - for example use! */
void printhello();
库的代码很基本,在下一个清单中显示。
/* file libprint.c */
#include "stdio.h"
void printhello()
{
printf("hello opendba/n");
}
编译:
gcc -fPIC -c libhello.c
ld -shared -soname libhello.so.1 -olibhello.so.1.0 -lc libhello.o
-soname也可以用-h代替。
注意,gcc 命令行中的 -fPIC 选项。这是生成 Position-Independent Code 所必须要的。把这个命令翻译出来就是:生成可以在进程的进程空间的任何地方载入的代码。这对于共享对象是非常重要的。使用这个选项,使得必须执行重定位的数量降低到最少。一旦载入可执行程序使用的共享对象,就必须给它分配一些空间。必须给文本和数据部分配一些位置。如果它们不是以“位置独立”方式来构建,那么载入共享对象时,程序要做大量的重定位,这会影响到性能。
现在我们分析一下传给 ld 的选项。-shared选项表明输出的文件被认为是共享的库。通过 -soname name 选项,可以指定 soname 是什么。-o name 指定了共享对象的real name,也就是实际生成的动态库的文件名称。
为了让动态链接库为系统所共享,还需运行动态链接库的管理命令–ldconfig。
ldconfig 命令的用途,主要是在默认搜寻目录(/lib和/usr/lib)以及动态库配置文件/etc/ld.so.conf内所列的目录下,搜索出可共享的动态链接库(格式如前介绍,lib.so),进而创建出动态装入程序(ld.so)所需的连接和缓存文件.缓存文件默认为 /etc/ld.so.cache,此文件保存已排好序的动态链接库名字列表.
ldconfig -p 输出共享库soname实际对应的共享库的文件名称。
一个程序/shared库一般都要依赖其他的一些库,这可以用ldd来查看,它列出了依赖的库的soname,因为实际依赖是库的接口,而 soname正是反映了库的接口信息。linux使用ELF作为可执行程序和库的格式,这些依赖的库的soname保存在ELF的某个fileld里。当一个可执行程序执行时,ld.so负责把它所依赖的shared库加载到内存并链接,它按照以下顺序寻找shared库:
(1)在LD_LIBRARY_PATH环境变量指定的目录下
(2)ld.so.cache文件该shared库对应的文件
(3)/usr/lib和/lib目录下
环境变量:
LD_BIND_NOW — 正常来讲,函数在呼叫之前是不会让程式寻找(looked up)的.设定这个旗号会使得程式库一载入,所有的寻找(lookups)便会发生,同时也造成起始的时间(startup time)较慢.当你想测试程式,确定所有的连结都没有问题时,这项旗号就变得很有用.
LD_PRELOAD 可以设定一个档案,使其具有覆盖(overriding)函数定义的能力.例如,如果你要测试记忆体分配的方略(strategies),而且还想置换malloc,那麽你可以写好准备替换的副程式(routine),并把它编译成mallolc. ,然後:
$LD_PRELOAD=malloc.o; export LD_PRELOAD
$ some_test_program
LDELF_PRELOAD 与LD_AOUT_PRELOAD 很类似,但是仅适用於正确的二进位型态.如果设定了 LD something _PRELOAD 与LD_PRELOAD ,比较明确的那一个会被用到.
LD_LIBRARY_PATH 是一连串以分号隔离的目录名称,用来搜寻共享程式库.对ld而言,并没有 任何的影响;这项只有在执行期间才有影响.另外,对执行setuid与setgid的程式而言,这一项是无效的.而LD_ELF_LIBRARY_PATH与LD_AOUT_LIBRARY_PATH 这两种旗号可根据各别的二进位型式分别导向不同的搜寻路径.一般正常的运作下,不应该会用到LD_LIBRARY_PATH ;把需要搜寻的目录加到/etc/ld.so.conf/ 里;然後重新执行ldconfig.
LD_NOWARN 仅适用於a.out.一旦设定了这一项(LD_NOWARN=true; export LD_NOWARN ),它会告诉载入器必须处理fatal-warnings(像是次要版本不相容等)的警告讯息.
LD_WARN 仅适用於ELF.设定这一项时,它会将通常是致命讯息的"Can*t find library"转换成警告讯息.对正常的操作而言,这并没有多大的用处,可是对ldd就很重要了.
LD_TRACE_LOADED_OBJECTS 仅适用於ELF.而且会使得程式以为它们是由ldd所执行的:
$LD_TRACE_LOADED_OBJECTS=true /usr/bin/lynx
libncurses.so.1 => /usr/lib/libncurses.so.1.9.6
libc.so.5 => /lib/libc.so.5.2.18
总结:
具体流程:
A、将写好的一个或多个.c文件编译成.o文件,例如:gcc -fPIC -Wall -c xx.c
libxxxx.so
libxxxx.so libxxxx.so1,两者不能同名(此操作可以忽略)
-lxxxx
E、运行app报错:mytest not found
动态库无法加载原因:命令ldd app看到libmytest.so显示找不到此动态库。
在执行的时候如何定位动态库文件呢?当系统加载可执行代码时,能够知道其所依赖的库的名字,但是还需要知道绝对路径。此时就需要系统动态载入器,对于elf格式的可执行程序,是由ld-linux.so*来完成 的,他先后搜索elf文件的DT_RPATH段--------环境变量。
动态链接器ld_linux_x86_64.so查找动态库先后顺序: LD_LIBRARY_PATH,环境变量----> /etc/ld.so.cache文件列表----> /lib----->/usr/lib
当以上4个位置都检查不到那个动态库文件时候就会报错!
如果动态库安装在/lib或者/usr/lib下,那么ld默认能够找到,无需其他操作。如果安装在其他目录,需要将其添加到/etc/ld.so.conf.d/*.conf文件中,步骤如下:
新建并编辑/etc/ld.so.conf.d/my.conf文件,加入库文件所在目录的路径;运行ldconfig,该命令会重建/etc/ld.so.conf.d/my.conf文件。
为了让执行程序顺利找到动态库有以下三种方法:
方法一:将.so文件拷贝到/lib或者/usr/lib下(不推荐,可能会和已有的库名字冲突而覆盖掉原来文件)。
方法二:修改LD_LIBRARY_PATH环境变量(推荐)
临时修改(当前终端有效):export LD_LIBRARY_PATH = 动态库路径 : $LD_LIBRARY_PATH
永久修改:
用户级别(当前用户生效):修改~/.bashrc文件,完成后重启终端或者source ~/.bashrc,source可用.代替。
系统级别(所有用户生效):修改./etc/profile文件,安成后重启终端或者source ./etc/profile,source可用.代替。
方案三: 修改ld.so.cache列表
将动态库的绝对路径写入到/etc/ld.sl.conf中,完成后执行 ldconfif -v(-v参数展示终端输出内容)
4.3、查看库中的符号
有时候可能需要查看一个库中到底有哪些函数,nm命令可以打印出库中涉及到的所有符号,库既可以是静态的也可以是动态的。nm列出的符号有很多,常见的有三种
(1)一种是在库中被调用,但并没有在库中定义(表明需要其它库支持),用U表示
(2)一种是库中定义的函数,用T表示,这是最常见的
(3)一种是所谓的“弱态”符号,他们虽然在库中被定义,但是可能被其他库中的同名符号覆盖,用W表示。
nm libxxxx.h
ldd命令可以查看一个可执行程序依赖的共享库,例如:ldd /bin/lnlibc.so.6
--->/lib/libc.so.6(0x40021000)/lib/ld-linux.so.2
--->/lib/ld-linux.so.2(0x40000000)
可以看到ln命令依赖于libc库和ld-linux库。
4. 4、库的优缺点
静态链接库的优缺点
(1)代码装载速度快,执行速度略比动态链接库快;
(2)只需保证在开发者的计算机中有正确的.LIB文件,在以二进制形式发布程序时不需考虑在用户的计算机上.LIB文件是否存在及版本问题,可避免DLL地狱等问题。
(3)使用静态链接生成的可执行文件体积较大,包含相同的公共代码,造成浪费;
动态链接库的优缺点
(1)更加节省内存并减少页面交换;
(2)DLL文件与EXE文件独立,只要输出接口不变(即名称、参数、返回值类型和调用约定不变),更换DLL文件不会对EXE文件造成任何影响,因而极大地提高了可维护性和可扩展性;
(3)不同编程语言编写的程序只要按照函数调用约定就可以调用同一个DLL函数;
(4)适用于大规模的软件开发,使开发过程独立、耦合度小,便于不同开发者和开发组织之间进行开发和测试。
(2)使用动态链接库的应用程序不是自完备的,它依赖的DLL模块也要存在,如果使用载入时动态链接,程序启动时发现DLL不存在,系统将终止程序并给出错误信息。而使用运行时动态链接,系统不会终止,但由于DLL中的导出函数不可用,程序会加载失败;速度比静态链接慢。当某个模块更新后,如果新模块与旧的模块不兼容,那么那些需要该模块才能运行的软件,统统撕掉。这在早期Windows中很常见。
5、windows下的两种库
windows下的库有两种:
动态链接库dynamic link library。
静态链接库static link library。
共有两种链接方式:
动态链接使用动态链接库,允许可执行模块(.dll文件或.exe文件)仅包含在运行时定位DLL函数的可执行代码所需的信息。
静态链接使用静态链接库,链接器从静态链接库LIB获取所有被引用函数,并将库同代码一起放到可执行文件中。
关于lib和dll的区别如下:
(1)lib是编译时用到的,dll是运行时用到的。如果要完成源代码的编译,只需要lib;如果要使动态链接的程序运行起来,只需要dll。
(2)如果有dll文件,那么lib一般是一些索引信息,记录了dll中函数的入口和位置,dll中是函数的具体内容;如果只有lib文件,那么这个lib文件是静态编译出来的,索引和实现都在其中。使用静态编译的lib文件,在运行程序时不需要再挂动态库,缺点是导致应用程序比较大,而且失去了动态库的灵活性,发布新版本时要发布新的应用程序才行。
(3)动态链接的情况下,有两个文件:一个是LIB文件,一个是DLL文件。LIB包含被DLL导出的函数名称和位置,DLL包含实际的函数和数据,应用程序使用LIB文件链接到DLL文件。在应用程序的可执行文件中,存放的不是被调用的函数代码,而是DLL中相应函数代码的地址,从而节省了内存资源。DLL和LIB文件必须随应用程序一起发行,否则应用程序会产生错误。如果不想用lib文件或者没有lib文件,可以用WIN32 API函数LoadLibrary、GetProcAddress装载。
使用lib需注意两个文件:
(1).h头文件,包含lib中说明输出的类或符号原型或数据结构。应用程序调用lib时,需要将该文件包含入应用程序的源文件中。
(2).LIB文件,略。
使用dll需注意三个文件:
(1).h头文件,包含dll中说明输出的类或符号原型或数据结构的.h文件。应用程序调用dll时,需要将该文件包含入应用程序的源文件中。
(2).LIB文件,是dll在编译、链接成功之后生成的文件,作用是当其他应用程序调用dll时,需要将该文件引入应用程序,否则产生错误。如果不想用lib文件或者没有lib文件,可以用WIN32 API函数LoadLibrary、GetProcAddress装载。
(3).dll文件,真正的可执行文件,开发成功后的应用程序在发布时,只需要有.exe文件和.dll文件,并不需要.lib文件和.h头文件。
使用lib的方法:
静态lib中,一个lib文件实际上是任意个obj文件的集合,obj文件是cpp文件编译生成的。在编译这种静态库工程时,根本不会遇到链接错误;即使有错,也只会在使用这个lib的EXT文件或者DLL工程里暴露出来。
在VC中新建一个static library类型的工程Lib,加入test.cpp文件和test.h文件(头文件内包括函数声明),然后编译,就生成了Lib.lib文件。
别的工程要使用这个lib有两种方式:
(1)在project->link->Object/Library Module中加入Lib.lib文件(先查询工程目录,再查询系统Lib目录);或者在源代码中加入指令#pragma comment(lib, “Lib.lib”)。
(2)将Lib.lib拷入工程所在目录,或者执行文件生成的目录,或者系统Lib目录中。
(3)加入相应的头文件test.h。
使用DLL的方法:
使用动态链接中的lib,不是obj文件的集合,即里面不会有实际的实现,它只是提供动态链接到DLL所需要的信息,这种lib可以在编译一个DLL工程时由编译器生成。
创建DLL工程的方法(略)。
(1)隐式链接
第一种方法是:通过project->link->Object/LibraryModule中加入.lib文件(或者在源代码中加入指令#pragma comment(lib, “Lib.lib”)),并将.dll文件置入工程所在目录,然后添加对应的.h头文件。
#include "stdafx.h"
#include "DLLSample.h"
#pragma comment(lib, "DLLSample.lib") //你也可以在项目属性中设置库的链接
int main()
{
TestDLL(123); //dll中的函数,在DllSample.h中声明
return(1);
}
(2)显式链接
需要函数指针和WIN32 API函数LoadLibrary、GetProcAddress装载,使用这种载入方法,不需要.lib文件和.h头文件,只需要.dll文件即可(将.dll文件置入工程目录中)。
#include <iostream>
#include <windows.h> //使用函数和某些特殊变量
typedef void (*DLLFunc)(int);
int main()
{
DLLFunc dllFunc;
HINSTANCE hInstLibrary = LoadLibrary("DLLSample.dll");
if (hInstLibrary == NULL)
{
FreeLibrary(hInstLibrary);
}
dllFunc = (DLLFunc)GetProcAddress(hInstLibrary, "TestDLL");
if (dllFunc == NULL)
{
FreeLibrary(hInstLibrary);
}
dllFunc(123);
std::cin.get();
FreeLibrary(hInstLibrary);
return(1);
}
LoadLibrary函数利用一个名称作为参数,获得DLL的实例(HINSTANCE类型是实例的句柄),通常调用该函数后需要查看一下函数返回是否成功,如果不成功则返回NULL(句柄无效),此时调用函数FreeLibrary释放DLL获得的内存。
GetProcAddress函数利用DLL的句柄和函数的名称作为参数,返回相应的函数指针,同时必须使用强转;判断函数指针是否为NULL,如果是则调用函数FreeLibrary释放DLL获得的内存。此后,可以使用函数指针来调用实际的函数。
最后要记得使用FreeLibrary函数释放内存。
注意:应用程序如何找到DLL文件?
使用LoadLibrary显式链接,那么在函数的参数中可以指定DLL文件的完整路径;如果不指定路径,或者进行隐式链接,Windows将遵循下面的搜索顺序来定位DLL:
(1)包含EXE文件的目录
(2)工程目录
(3)Windows系统目录
(4)Windows目录
(5)列在Path环境变量中的一系列目录
5.1、动态库例程
mysocketclient.c
#define _CRT_SECURE_NO_WARNINGS
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include "itcastlog.h"
typedef struct _SCK_HANDLE
{
char version[64];
char ip[128];
int port;
unsigned char *p;
int plen;
}SCK_HANDLE; //动态库,内部的数据类型 ,不想让测试程序(上层应用知道)
//数据类型的封装
__declspec(dllexport)
int cltSocketInit(void **handle /*out*/)
{
int ret = 0;
SCK_HANDLE *hdl = NULL;
ITCAST_LOG(__FILE__, __LINE__, LogLevel[2], ret, "func cltSocketInit() Begin 22222:%d", ret);
hdl = (SCK_HANDLE *)malloc(sizeof(SCK_HANDLE));
if (hdl == NULL)
{
ret = -1;
ITCAST_LOG(__FILE__, __LINE__, LogLevel[4], ret, "func cltSocketInit() err:%d", ret);
return ret;
}
memset(hdl, 0, sizeof(SCK_HANDLE)); //把指针所指向的内存空间 赋值成 0;
strcpy(hdl->ip, "192.168.6.254");
hdl->port = 8081;
*handle = hdl;
ITCAST_LOG(__FILE__, __LINE__, LogLevel[2], ret, "func cltSocketInit() End:%d \n", ret);
return ret;
}
//客户端发报文
__declspec(dllexport)
int cltSocketSend(void *handle /*in*/, unsigned char *buf /*in*/, int buflen /*in*/)
{
int ret = 0;
SCK_HANDLE *hdl = NULL;
if (handle==NULL || buf==NULL )
{
ret = -1;
ITCAST_LOG(__FILE__, __LINE__, LogLevel[4], ret, "func cltSocketSend() err:%d\n (handle==NULL || buf==NULL ) ", ret);
return ret;
}
hdl = (SCK_HANDLE *)handle;
hdl->p = (unsigned char *)malloc(buflen *sizeof(unsigned char));
if (hdl->p == NULL)
{
ret = -2;
ITCAST_LOG(__FILE__, __LINE__, LogLevel[4], ret, "func cltSocketSend() err: buflen:%d ", buflen);
ITCAST_LOG(__FILE__, __LINE__, LogLevel[4], ret, "func cltSocketSend() err:%d\n (unsigned char *)malloc(buflen *sizeof(unsigned char) ", ret);
return ret;
}
memcpy(hdl->p, buf, buflen);
hdl->plen = buflen;
return 0;
}
//客户端收报文
__declspec(dllexport)
int cltSocketRev(void *handle /*in*/, unsigned char *buf /*in*/, int *buflen /*in out*/)
{
int ret = 0;
SCK_HANDLE *hdl = NULL;
if (handle==NULL || buf==NULL ||buflen==NULL)
{
ret = -1;
ITCAST_LOG(__FILE__, __LINE__, LogLevel[4], ret, "func cltSocketRev() err:%d\n (handle==NULL || buf==NULL ) ", ret);
return ret;
}
hdl = (SCK_HANDLE *)handle;
memcpy(buf, hdl->p, hdl->plen);
*buflen = hdl->plen;
return ret;
}
//客户端释放资源
__declspec(dllexport)
int cltSocketDestory(void *handle/*in*/)
{
int ret = 0;
SCK_HANDLE *hdl = NULL;
if (handle==NULL )
{
ret = -1;
ITCAST_LOG(__FILE__, __LINE__, LogLevel[4], ret, "func cltSocketDestory() err:%d\n (handle==NULL || buf==NULL ) ", ret);
return ret;
}
hdl = (SCK_HANDLE *)handle;
if (hdl->p)
{
free(hdl->p);
}
free(hdl);
return ret;
}
//-----------------------第二套api函数--------------------------------*/
__declspec(dllexport)
int cltSocketInit2(void **handle)
{
return cltSocketInit(handle);
}
//客户端发报文
__declspec(dllexport)
int cltSocketSend2(void *handle, unsigned char *buf, int buflen)
{
return cltSocketSend(handle, buf, buflen);
}
//客户端收报文
__declspec(dllexport)
int cltSocketRev2(void *handle, unsigned char **buf, int *buflen)
{
int ret = 0;
SCK_HANDLE *hdl = NULL;
unsigned char *tmp = NULL;
if (handle==NULL || buf==NULL ||buflen==NULL)
{
ret = -1;
ITCAST_LOG(__FILE__, __LINE__, LogLevel[4], ret, "func cltSocketRev2() err:%d\n (handle==NULL || buf==NULL ) ", ret);
return ret;
}
hdl = (SCK_HANDLE *)handle;
tmp = (unsigned char *)malloc(hdl->plen);
if (tmp == NULL)
{
ret = -2;
ITCAST_LOG(__FILE__, __LINE__, LogLevel[4], ret, "func cltSocketRev2() err:%d\n (malloc err ) ", ret);
return ret;
}
memcpy(tmp, hdl->p, hdl->plen);
*buflen = hdl->plen;
*buf = tmp; //间接赋值
return ret;
}
__declspec(dllexport)
int cltSocketRev2_Free(unsigned char **buf)
{
if (buf == NULL)
{
return -1;
}
if (*buf != NULL)
{
free(*buf);
}
*buf = NULL; //*实参的地址 去间接的修改实参的值 重新初始化NULL
return 0;
}
//客户端释放资源
__declspec(dllexport)
int cltSocketDestory2(void **handle)
{
SCK_HANDLE *tmp = NULL;
if (handle==NULL)
{
return -1;
}
tmp = *handle;
if (tmp != NULL)
{
if (tmp->p)
{
free(tmp->p);
tmp->p = NULL;
}
free(tmp);
}
*handle = NULL; //*实参的地址 去间接的修改实参的值 重新初始化NULL
return 0;
}
socketclientdll.h
//written by wangbaoming1999@163.com
/*
下面定义了一套socket客户端发送报文接受报文的api接口
请写出这套接口api的调用方法
*/
#ifndef _INC_Demo01_H
#define _INC_Demo01_H
#ifdef __cplusplus
extern "C" {
#endif
//------------------第一套api接口---Begin--------------------------------//
//客户端初始化 获取handle上下
int cltSocketInit(void **handle /*out*/);
//客户端发报文
int cltSocketSend(void *handle /*in*/, unsigned char *buf /*in*/, int buflen /*in*/);
//客户端收报文
int cltSocketRev(void *handle /*in*/, unsigned char *buf /*in*/, int *buflen /*in out*/);
//客户端释放资源
int cltSocketDestory(void *handle/*in*/);
//------------------第一套api接口---End-----------------------------------//
//------------------第二套api接口---Begin--------------------------------//
int cltSocketInit2(void **handle);
//客户端发报文
int cltSocketSend2(void *handle, unsigned char *buf, int buflen);
//客户端收报文
int cltSocketRev2(void *handle, unsigned char **buf, int *buflen);
int cltSocketRev2_Free(unsigned char **buf);
//客户端释放资源
int cltSocketDestory2(void **handle);
//------------------第二套api接口---End--------------------------------//
#ifdef __cplusplus
}
#endif
#endif /* _INC_Demo01_H */
动态库测试
#define _CRT_SECURE_NO_WARNINGS
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include "socketclientdll.h"
#include "memwatch.h"
int main01()
{
int ret = 0;
void *handle = NULL;
char buf[128]; /*in*/
int buflen = 3;/*in*/
char outbuf[128]; /*in*/
int outbuflen = 3;/*in*/
strcpy(buf, "dsssssssssdswdfafd");
ret = cltSocketInit(&handle /*out*/);
if (ret != 0)
{
printf("func cltSocketInit() err:%d \n", ret);
return ret;
}
//客户端发报文
ret = cltSocketSend(handle /*in*/, buf /*in*/, buflen /*in*/);
if (ret != 0)
{
printf("func cltSocketSend() err:%d \n", ret);
return ret;
}
//客户端收报文
ret = cltSocketRev(handle /*in*/, outbuf /*in*/, &outbuflen /*in out*/);
if (ret != 0)
{
printf("func cltSocketRev() err:%d \n", ret);
return ret;
}
//客户端释放资源
cltSocketDestory(handle/*in*/);
system("pause");
return ret ;
}
int main()
{
int ret = 0;
void *handle = NULL;
char buf[128]; /*in*/
int buflen = 3;/*in*/
//char outbuf[128]; /*in*/ outbuf //常量指针
//int outbuflen = 3;/*in*/
char *pout = NULL;
int poutlen = 0;
strcpy(buf, "dsssssssssdswdfafd");
ret = cltSocketInit2(&handle);
//buflen = -133332;
//客户端发报文
ret = cltSocketSend2(handle, buf, buflen);
if (ret != 0)
{
return ret;
}
//客户端收报文
ret = cltSocketRev2(handle, &pout, &poutlen); //在动态库里面分配内存了...
if (ret != 0)
{
return ret;
}
/*
if (pout != NULL)
{
free(pout);
}
*/
cltSocketRev2_Free(&pout); //避免野指针 把outbuf所指向的内存释放,同时把outbuf变量赋值NULL
//ret = cltSocketRev2_Free(&pout);
//客户端释放资源
cltSocketDestory2(&handle);
system("pause");
return ;
}