2018-08-06 01:18:52 WODECZW 阅读数 130
  • C语言嵌入式Linux编程第3期:程序的编译、链接和运行

    本课程为《C语言嵌入式Linux编程》第3期,主要对程序的编译、链接及运行机制进行分析。同时对静态库链接、动态链接的过程、插件原理、内核模块运行机进行探讨,后对嵌入式系统比较难理解的u-boot重定位、u-boot加载内核、内核解压缩、重定位过程进行分析,加深对程序的编译链接原理的理解。

    1212 人正在学习 去看看 王利涛

http://www.cnblogs.com/skynet/p/3372855.html

这是参考的博客,讲的挺详细的

然后我又看了关于动态库,静态库的其他内容博客,懂了编译的大部分流程和原理,但是有一个疑惑没有想明白

 

g++ -L(动态库所在路径)  -l(编译所需的动态库名称) main.cpp -o demo 

 

编译好后还需要把刚刚编译时的路径写到系统运行时加载动态库的路径里

 

看到这样的编译时

我会想编译的时候二进制文件已经知道了需要调用的函数在(二进制)动态库内部的相对位置

也就是说已经加载完动态库文件,已经知道去哪里加载动态库文件,即通过-L已经知道动态库路径在哪里了

 

 

那为什么等到程序运行时还要让编译好的二进制文件不知道动态库的路径在哪里,明明编译时可以记下来,也就是记录到编译好的

二进制文件里,为什么g++的设计者要忘掉之前得到的变量,这不是很奇怪吗?为什么要绕个远路再去其他地方(Linux系统会加载的路径,比如/usr/lib /lib 或者 /etc/ld.so.cache)找需要的动态库

 

 

和几个同学讨论了完我终于想明白了

如果不把路径忘记,那就只能把路径写进可执行二进制文件里,那么如果我更改了动态库的绝对位置,那么这个程序就一定运行不了了,g++设计者为了避免这种(开发中几乎一定会发生的)情况发生才特意如此设计,

因为通常我们开发一个工程软件,在工程文件夹里写完函数库,制作完动态库(此时在工程文件夹,现在就移动到系统第三方库里还要删来删去,需要权限太麻烦),运行,调试后觉得没问题才会开始把动态库放到系统专门防止第三方动态库的路径里,也就是说我们几乎一定会在制作可执行文件后移动动态库,所以往往第一次(-L)提到的路径和第二次所需的路径是不一样的,所以需要写下两次路径,g++设计者是从开发流程想才这么设计的

 

 

以上均为个人猜想(因为感觉翻遍了中文的博客也找不到说这个问题的)

 

如果觉得有道理点个赞~~~~~~~~~~~

2016-08-12 10:14:42 lihuajie1003 阅读数 823
  • C语言嵌入式Linux编程第3期:程序的编译、链接和运行

    本课程为《C语言嵌入式Linux编程》第3期,主要对程序的编译、链接及运行机制进行分析。同时对静态库链接、动态链接的过程、插件原理、内核模块运行机进行探讨,后对嵌入式系统比较难理解的u-boot重定位、u-boot加载内核、内核解压缩、重定位过程进行分析,加深对程序的编译链接原理的理解。

    1212 人正在学习 去看看 王利涛

linux下动态链接库的制作:

so可以供多个进程使用,不同进程调用同一个so文件,

so文件源程序不需要main函数,有也不会被执行。

下面通过一个简单例子,来学习.so文件的制作跟使用。

/* 
 * max.h 
 * 
 */  
  
/*条件编译*/  
#ifndef MAX_H_  
#define MAX_H_  
  
#ifdef __cpluscplus  
extern "C"{  
#endif  
   int max(int a,int b);  
#endif /* MAX_H_ */  

/* 
 * max.cpp
 * 
*/
#include "max.h"
int max(int a,int b)  
{  
    return a>b?a:b;  
}  

链接库的制作:

编译c程序:
gcc -shared -fpic XXX.c -o XXX.so

编译c++程序:
g++ -shared -fpic XXX.cpp -o XXX.so

/* 
 * main.cpp 
 * 
 */  
#include <stdio.h>  
#include "max.h"  
int main(void)  
{  
    printf("call max function results is: %d\n",max(20,12));  
    return 0;  
}  
/* 使用libmax.so库 
 * gcc -o  main main.c -L. -lmax 
 * 
 *-L.: 在当前路径下寻找.so文件 
 *-lmax: 要链接这个libmax.so文件 
 * 
 * */  

编译main.cpp

       g++ -o main main.cpp -lmax

此时需要注意的是应该把libmax.so 放到/usr/lib目录中否则会出现错误

error while loading shared libraries: libmax.so: cannot open shared object file: No such file or directory

      ./main 结果出现20说明动态链接库制作正确。

如果要是在QT中调用,需要加上头文件和.so文件的绝对路径

INCLUDEPATH + =" "
LIBS += ""


$ g++ hello.cpp  -fPIC -shared -o libhello.so

$ g++ main.cpp -L. -lhello -o main

交叉编译步骤一致:

(1)  $arm-linux-g++ hello.cpp -fPIC -shared -o libarmhello.so

(2)  $arm-linux-g++ main.cpp -L. -larmhello -o armhello

交叉编译的时候出现错误:fatal error:opencv2/imgproc/imgproc.hpp:no such file or directory

解决方法:把交叉编译好的opencv下的include中的opencv2拷贝到交叉编译器中的include下

我的交叉编译好的opencv和opencv2路径是在:/opt/arm/include  

/opt/arm/include  文件下的opencv2 复制到/opt/fsl-linaro-toolchain/arm-fsl-linux-gnueabi/include即可。


2015-08-17 15:40:01 littlewhite1989 阅读数 7098
  • C语言嵌入式Linux编程第3期:程序的编译、链接和运行

    本课程为《C语言嵌入式Linux编程》第3期,主要对程序的编译、链接及运行机制进行分析。同时对静态库链接、动态链接的过程、插件原理、内核模块运行机进行探讨,后对嵌入式系统比较难理解的u-boot重定位、u-boot加载内核、内核解压缩、重定位过程进行分析,加深对程序的编译链接原理的理解。

    1212 人正在学习 去看看 王利涛

不同版本的动态库可能会不兼容,如果程序在编译时指定动态库是某个低版本,运行是用的一个高版本,可能会导致无法运行。Linux上对动态库的命名采用libxxx.so.a.b.c的格式,其中a代表大版本号,b代表小版本号,c代表更小的版本号,我们以Linux自带的cp程序为例,通过ldd查看其依赖的动态库

 $ ldd /bin/cp                                                                                                                                                                                        
linux-vdso.so.1 =>  (0x00007ffff59df000)
libselinux.so.1 => /lib64/libselinux.so.1 (0x00007fb3357e0000)
librt.so.1 => /lib64/librt.so.1 (0x00007fb3355d7000)
libacl.so.1 => /lib64/libacl.so.1 (0x00007fb3353cf000)
libattr.so.1 => /lib64/libattr.so.1 (0x00007fb3351ca000)
libc.so.6 => /lib64/libc.so.6 (0x00007fb334e35000)
libdl.so.2 => /lib64/libdl.so.2 (0x00007fb334c31000)
/lib64/ld-linux-x86-64.so.2 (0x00007fb335a0d000)
libpthread.so.0 => /lib64/libpthread.so.0 (0x00007fb334a14000)

左边是依赖的动态库名字,右边是链接指向的文件,再查看libacl.so相关的动态库

  $ ll /lib64/libacl.so*                                                                                                                                                                               
lrwxrwxrwx. 1 root root    15 1月   7 2015 /lib64/libacl.so.1 -> libacl.so.1.1.0
-rwxr-xr-x. 1 root root 31280 12月  8 2011 /lib64/libacl.so.1.1.0

我们发现libacl.so.1实际上是一个软链接,它指向的文件是libacl.so.1.1.0,命名方式符合我们上面的描述。也有不按这种方式命名的,比如

$ ll /lib64/libc.so*                                                                                                                                                                                  
lrwxrwxrwx 1 root root 12 8月  12 14:18 /lib64/libc.so.6 -> libc-2.12.so

不管怎样命名,只要按照规定的方式来生成和使用动态库,就不会有问题。而且我们往往是在机器A上编译程序,在机器B上运行程序,编译和运行的环境其实是有略微不同的。下面就说说动态库在生成和使用过程中的一些问题

动态库的编译

我们以一个简单的程序作为例子

// filename:hello.c
#include <stdio.h>

void hello(const char* name)
{
    printf("hello %s!\n", name);
}

// filename:hello.h
void hello(const char* name);

采用如下命令进行编译

gcc hello.c -fPIC -shared -Wl,-soname,libhello.so.0 -o libhello.so.0.0.1

需要注意的参数是-Wl,soname(中间没有空格),-Wl选项告诉编译器将后面的参数传递给链接器,
-soname则指定了动态库的soname(简单共享名,Short for shared object name)

现在我们生成了libhello.so.0.0.1,当我们运行ldconfig -n .命令时,当前目录会多一个软连接

 $ ll libhello.so.0                                                                                                                                                                                   
lrwxrwxrwx 1 handy handy 17 8月  17 14:18 libhello.so.0 -> libhello.so.0.0.1

这个软链接是如何生成的呢,并不是截取libhello.so.0.0.1名字的前面部分,而是根据libhello.so.0.0.1编译时指定的-soname生成的。也就是说我们在编译动态库时通过-soname指定的名字,已经记载到了动态库的二进制数据里面。不管程序是否按libxxx.so.a.b.c格式命名,但Linux上几乎所有动态库在编译时都指定了-soname,我们可以通过readelf工具查看soname,比如文章开头列举的两个动态库

 $ readelf -d /lib64/libacl.so.1.1.0                                                                                                                                                                   

Dynamic section at offset 0x6de8 contains 24 entries:
Tag        Type                         Name/Value
0x0000000000000001 (NEEDED)             Shared library: [libattr.so.1]
0x0000000000000001 (NEEDED)             Shared library: [libc.so.6]
0x000000000000000e (SONAME)             Library soname: [libacl.so.1]

这里省略了一部分,可以看到最后一行SONAME为libacl.so.1,所以/lib64才会有一个这样的软连接

再看libc-2.12.so文件,该文件并没有采用我们说的命名方式

 $ readelf -d /lib64/libc-2.12.so                                                                                                                                                                      

Dynamic section at offset 0x18db40 contains 27 entries:
Tag        Type                         Name/Value
0x0000000000000001 (NEEDED)             Shared library: [ld-linux-x86-64.so.2]
0x000000000000000e (SONAME)             Library soname: [libc.so.6]

同样可以看到最后一行SONAME为libc.so.6,即便该动态库没有按版本号的方式命名,但仍旧有一个软链指向该动态库,而该软链的名字就是soname指定的名字

所以关键就是这个soname,它相当于一个中间者,当我们的动态库只是升级一个小版本时,我们可以让它的soname相同,而可执行程序只认soname指定的动态库,这样依赖这个动态库的可执行程序不需重新编译就能使用新版动态库的特性

可执行程序的编译

还是以hello动态库为例,我们写一个简单的程序

// filename:main.c
#include "hello.h"

int main()
{
    hello("handy");
    return 0;
}

现在目录下是如下结构

├── hello.c
├── hello.h
├── libhello.so.0 -> libhello.so.0.0.1
├── libhello.so.0.0.1
└── main.c

libhello.so.0.0.1是我们编译生成的动态库,libhello.so.0是通过ldconfig生成的链接,采用如下命令编译main.c

 $ gcc main.c -L. -lhello -o main                                                                                                                                                                            
/usr/bin/ld: cannot find -lhello

报错找不到hello动态库,在Linux下,编译时指定-lhello,链接器会去寻找libhello.so这样的文件,当前目录下没有这个文件,所以报错。建立这样一个软链,目录结构如下

├── hello.c
├── hello.h
├── libhello.so -> libhello.so.0.0.1
├── libhello.so.0 -> libhello.so.0.0.1
├── libhello.so.0.0.1
└── main.c

让libhello.so链接指向实际的动态库文件libhello.so.0.0.1,再编译main程序

gcc main.c -L. -lhello -o main

这样可执行文件就生成了。通过以上测试我们发现,在编译可执行程序时,链接器会去找它依赖的libxxx.so这样的文件,因此必须保证libxxx.so的存在

用ldd查看其依赖的动态库

 $ ldd main                                                                                                                                                                                            
        linux-vdso.so.1 =>  (0x00007fffe23f2000)
        libhello.so.0 => not found
        libc.so.6 => /lib64/libc.so.6 (0x00007fb6cd084000)
        /lib64/ld-linux-x86-64.so.2 (0x00007fb6cd427000)

我们发现main程序依赖的动态库名字是libhello.so.0,既不是libhello.so也不是libhello.so.0.0.1。其实在生成main程序的过程有如下几步

  • 链接器通过编译命令-L. -lhello在当前目录查找libhello.so文件
  • 读取libhello.so链接指向的实际文件,这里是libhello.so.0.0.1
  • 读取libhello.so.0.0.1中的SONAME,这里是libhello.so.0
  • 将libhello.so.0记录到main程序的二进制数据里

也就是说libhello.so.0是已经存储到main程序的二进制数据里的,不管这个程序在哪里,通过ldd查看它依赖的动态库都是libhello.so.0

而为什么这里ldd查看main显示libhello.so.0为not found呢,因为ldd是从环境变量$LD_LIBRARY_PATH指定的路径里来查找文件的,我们指定环境变量再运行如下

 $ export LD_LIBRARY_PATH=. && ldd main                                                                                                                                                                
    linux-vdso.so.1 =>  (0x00007fff7bb63000)
    libhello.so.0 => ./libhello.so.0 (0x00007f2a3fd39000)
    libc.so.6 => /lib64/libc.so.6 (0x00007f2a3f997000)
    /lib64/ld-linux-x86-64.so.2 (0x00007f2a3ff3b000)

可执行程序的运行

现在测试目录结果如下

├── hello.c
├── hello.h
├── libhello.so -> libhello.so.0.0.1
├── libhello.so.0 -> libhello.so.0.0.1
├── libhello.so.0.0.1
├── main
└── main.c

这里我们把编译环境和运行环境混在一起了,不过没关系,只要我们知道其中原理,就可以将其理清楚

前面我们已经通过ldd查看了main程序依赖的动态库,并且指定了LD_LIBRARY_PATH变量,现在就可以直接运行了

 $ ./main                                                                                                                                                                                              
hello Handy!

看起来很顺利。那么如果我们要部署运行环境,该怎么部署呢。显然,源代码是不需要的,我们只需要动态库和可执行程序。这里新建一个运行目录,并拷贝相关文件,目录结构如下

├── libhello.so.0.0.1
└── main

这时运行会main会发现

 $ ./main                                                                                                                                                                                              
./main: error while loading shared libraries: libhello.so.0: cannot open shared object file: No such file or directory

报错说libhello.so.0文件找不到,也就是说程序运行时需要寻找的动态库文件名其实是动态库编译时指定的SONAME,这也和我们用ldd查看的一致。通过ldconfig -n .建立链接,如下

├── libhello.so.0 -> libhello.so.0.0.1
├── libhello.so.0.0.1
└── main

再运行程序,结果就会符合预期了

从上面的测试看出,程序在运行时并不需要知道libxxx.so,而是需要程序本身记载的该动态库的SONAME,所以main程序的运行环境只需要以上三个文件即可

动态库版本更新

假设动态库需要做一个小小的改动,如下

// filename:hello.c
#include <stdio.h>

void hello(const char* name)
{
    printf("hello %s, welcom to our world!\n", name);
}

由于改动较小,我们编译动态库时仍然指定相同的soname

gcc hello.c -fPIC -shared -Wl,-soname,libhello.so.0 -o libhello.so.0.0.2

将新的动态库拷贝到运行目录,此时运行目录结构如下

├── libhello.so.0 -> libhello.so.0.0.1
├── libhello.so.0.0.1
├── libhello.so.0.0.2
└── main

此时目录下有两个版本的动态库,但libhello.so.0指向的是老本版,运行ldconfig -n .后我们发现,链接指向了新版本,如下

├── libhello.so.0 -> libhello.so.0.0.2
├── libhello.so.0.0.1
├── libhello.so.0.0.2
└── main

再运行程序

 $ ./main                                                                                                                                                                                              
hello Handy, welcom to our world!

没有重新编译就使用上了新的动态库, wonderful!

同样,假如我们的动态库有大的改动,编译动态库时指定了新的soname,如下

gcc hello.c -fPIC -shared -Wl,-soname,libhello.so.1 -o libhello.so.1.0.0

将动态库文件拷贝到运行目录,并执行ldconfig -n .,目录结构如下

├── libhello.so.0 -> libhello.so.0.0.2
├── libhello.so.0.0.1
├── libhello.so.0.0.2
├── libhello.so.1 -> libhello.so.1.0.0
├── libhello.so.1.0.0
└── main

这时候发现,生成了新的链接libhello.so.1,而main程序还是使用的libhello.so.0,所以无法使用新版动态库的功能,需要重新编译才行

最后

在实际生产环境中,程序的编译和运行往往是分开的,但只要搞清楚这一系列过程中的原理,就不怕被动态库的版本搞晕。简单来说,按如下方式来做

  • 编译动态库时指定-Wl,-soname,libxxx.so.a,设置soname为libxxx.so.a,生成实际的动态库文件libxxx.so.a.b.c,
  • 编译可执行程序时保证libxx.so存在,如果是软链,必须指向实际的动态库文件libxxx.so.a.b.c
  • 运行可执行文件时保证libxxx.so.a.b.c文件存在,通过ldconfig生成libxxx.so.a链接指向libxxx.so.a.b.c
  • 设置环境变量LD_LIBRARY_PATH,运行可执行程序

EOF

2017-09-25 13:29:51 lengye7 阅读数 474
  • C语言嵌入式Linux编程第3期:程序的编译、链接和运行

    本课程为《C语言嵌入式Linux编程》第3期,主要对程序的编译、链接及运行机制进行分析。同时对静态库链接、动态链接的过程、插件原理、内核模块运行机进行探讨,后对嵌入式系统比较难理解的u-boot重定位、u-boot加载内核、内核解压缩、重定位过程进行分析,加深对程序的编译链接原理的理解。

    1212 人正在学习 去看看 王利涛

+++原理+++
linux调用库的方式有三种:
1、静态链接库
2、动态链接库
3、动态加载库
       动态链接库(共享库)和动态加载库文件没有任何区别,唯一区别是:动态链接库是程序运行一开始就要加载库,动态加载库是程序在运行到函数需要这个函数的实现的时候才加载库。
       所以动态加载库就需要一组函数来控制什么时候加载库。这些函数是dlopen() dlerror() dlsym() dlclose()
示例代码:
int (*add) (int a, int b);   //声明函数指针,用来接收dlsym返回来的函数,就是接收要用到的函数
void *phandle = dlopen("libtest.so", RTLD_LAZY);
add = dlsym(phandle, "add");   //参数add是库里定义的add函数,dlsym返回函数地址
printf("%d\n", add(2, 5));
编译的时候:
gcc  main.c  -o  main  -ldl   //要加上参数-ldl。dl库是dlopen等那三个打开库使用的函数。这时候再编译自己的程序的时候就不用像动态链接库那样-L./ -ltest,链接自己的test库了,因为程序中需要这个库的时候就打开这个库使用了。 


上面第2 和第3 就是动态库的两种使用方式:(1)编译的时候链接动态库 (2)运行的时候加载动态库
我们调用动态链接库有两种方法:一种是编译的时候,指明所依赖的动态链接库,这样loader可以在程序启动的时候,来所有的动态链接映射到内存中;一种是在运行过程中,通过dlopen和dlfree的方式加载动态链接库,动态将动态链接库加载到内存中。
这两种方式,从编程角度来讲,第一种是最方便的,效率上影响也不大,在内存使用上有些差别。
第一种方式,一个库的代码,只要运行过一次,便会占用物理内存,之后即使再也不使用,也会占用物理内存,直到进程的终止。
第二中方式,库代码占用的内存,可以通过dlfree的方式,释放掉,返回给物理内存。
这个差别主要对于那些寿命很长,但又会偶尔调用各种库的进程有关。如果是这类进程,建议采用第二种方式调用动态链接库


linux gcc 生成链接库
1、生成动态链接库
+++原理+++
参考文章:  http://download.csdn.net/detail/u014132659/9673886  是一个上传的文档
动态链接库是程序运行是加载的库。


       动态链接库是目标文件的集合, 目标文件在动态链接库中的组织方式是按照特殊方式形成的,库中函数和变量的地址是相对的,不是绝对地址,其真实地址在调用动态库的程序加载时形成的。


       安装库文件就是把库文件放在系统目录下,能让程序找见就可以。系统默认的目录是/lib和/usr/lib或者还有其他的。也可以不放在系统默认路径下,自己在链接库配置文件中配置一个路径。


      链接库的配置文件,即配置程序运行时查找动态库的路径,配置文件是 /etc/ld.so.conf。系统文件 /lib 和 /usr/lib 是默认的查找的目录,不用配置。其余的自己添加的路径可以配置进去。


       动态链接库管理命令ldconfig,运行命令会生成一个在动态库的别名,就是命令里写的 -soname 的名字。重点是这个命令会生成 /etc/ld.so.cache 文件,这个是运行程序时查找动态库的时候需要的。具体这个文件是写了什么东西,还不清楚。


参考文章:http://www.cnblogs.com/xuxm2007/archive/2010/08/10/1796254.html
动态库配置文件 和 LD_LIBRARY_PATH 都是解决程序在运行是查找加载动态库问题的。在编译程序的时候,链接过程还是需要加 -L 和 -l参数,指定动态库的具体位置,链接器需要。(一定不要混淆)。一个是编译是查找动态库,具体是链接阶段,一个是运行是加载动态库。


---命令---  具体示例看上面上传的文档
gcc -Wall -fpic -o libtest.o -c test.c  //-fpic是预处理结果,可以自己man看一下,不用也可以
gcc -shared -Wl,-soname,libtest.so -o libtest.so.0 libtest.o  //-shared参数,-soname生成别名用的,真实名字是.so.0,有版本号
gcc -o main main.c -L./ -ltest
nm lictest.so  //查看库提供的函数,T标记
2、生成静态链接库
+++原理+++
生成静态链接库需要用ar程序
---命令---
gcc -c test.c    //生成test.o
ar -rcs libtest.a test.o   //生成静态库
nm libtest.a   //查看库提供的函数
gcc -o main main.c libtest.a   //使用静态库
或者
gcc -o main main.c -L./ -static -ltest
2016-05-11 23:04:03 daxingshen 阅读数 767
  • C语言嵌入式Linux编程第3期:程序的编译、链接和运行

    本课程为《C语言嵌入式Linux编程》第3期,主要对程序的编译、链接及运行机制进行分析。同时对静态库链接、动态链接的过程、插件原理、内核模块运行机进行探讨,后对嵌入式系统比较难理解的u-boot重定位、u-boot加载内核、内核解压缩、重定位过程进行分析,加深对程序的编译链接原理的理解。

    1212 人正在学习 去看看 王利涛
#参考:
#    http://www.abc188.com/info/html/wangzhanyunying/jianzhanjingyan/20080417/70810.html
#    http://os.51cto.com/art/201001/176625.htm
#    http://os.51cto.com/art/201001/176618.htm

[概述]本文是我在学习linux下动态链接库的总结,主要内容为:为什么要使用动态链接库、动态链接库的制作、使用。

0 为什么要使用动态链接库 
    linux下的库文件有:静态库和动态库两种。静态库文件的制作和使用在前文中已简要总结。LINUX系统中动态链接库,类似于 WINDOWS中以.dll为后缀的文件(dll即Dynamic Link Library)。
    静态库与动态库的区别。如果程序是在编译时加载库文件的,就是使用了静态库。如果是在运行时加载目标代码,就成为动态库。换句话说,如果是使用静态库,则静态库代码在编译时就拷贝到了程序的代码段,程序的体积会膨胀。如果使用动态库,则程序中只保留库文件的名字和函数名,在运行时去查找库文件和函数体,程序的体积基本变化不大。
    静态库的原则是“以空间换时间”,增加程序体积,减少运行时间;
    动态库则是“以时间换空间”,增加了运行时间,但减少了程序本身的体积。

1 动态链接库的制作 
    在LINUX系统下,创建动态链接库是件非常简单的事情。只要在编译函数库源程序时加上-shared选项即可,这样所生成的执行程序即为动态链接库。从某种意义上来说,动态链接库也是一种执行程序。按一般规则,动态库的命名应为lib*.so.*。其中第一个*为动态库的名字,第二个*为版本号,比如libc.so.6。编译生成动态库的命令: gcc -o libmy.so -fpic -shared getdate.c gettime.c
    下面制作动态库的例子中有3个文件: adatetime.h, getdate.c, gettime.c。adatetime.h为动态链接库的头文件; getdate.c和gettime.c分别实现getDate()和getTime()函数。
1.1 编写动态库头文件adatetime.h,代码如下:
[cpp] view plain copy
/* 
 * adatetime.h 
 */  
#ifndef _DATETIME_H  
#define _DATETIME_H  
typedef struct      /*日期结构*/  
{  
    int year;  
    int month;  
    int day;  
}DATETYPE;  
typedef struct      /*时间结构*/  
{  
    char hour;  
    char min;  
    char sec;  
}TIMETYPE;  
/*函数原型说明*/  
int getDate(DATETYPE *d);       /*取当前日期*/  
int getTime(TIMETYPE *t);       /*取当前时间*/  
#endif  


1.2 编写getdate.c和gettime.c,代码如下:
[cpp] view plain copy
/* 
 * getdate.c: 定义获取系统日期的函数getDate() 
 */  
#include<time.h>  
#include "adatetime.h"  
int getDate( DATETYPE *d )  
{  
    long ti;  
    struct tm *tm;  

    time(&ti);  
    tm = localtime(&ti);  
    d -> year = tm -> tm_year + 1900;  
    d -> month = tm -> tm_mon + 1;  
    d -> day = tm -> tm_mday;  
}  
/* 
 * gettime.c: 定义获取系统时间的函数getTime() 
 */  
#include<time.h>  
#include "adatetime.h"  
int getTime( TIMETYPE *t )  
{  
    long ti;  
    struct tm *tm;  

    time(&ti);  
    tm = localtime(&ti);  
    t -> hour = tm -> tm_hour;  
    t -> min = tm -> tm_min;  
    t -> sec = tm -> tm_sec;  
}  


1.3 编译生成动态库
(1) 编写维护文件makefile-lib,内容如下
[cpp] view plain copy
#makefile-lib: 动态库实例维护文件  
all: libmy.so  
SRC = getdate.c gettime.c  
TGT = $(SRC:.c=.o)  
$(SRC): adatetime.h  
    @touch $@  
%.o: %.c  
    cc -c $?  
#生成动态链接库(libmy.so)  
libmy.so: $(TGT)  
    cc -s -fpic -shared -o $@ $(TGT)        #-fpic选项,增加动态库的可重定位性  

注意: 每行维护代码必须以TAB(跳格键)开始,不是的话make时将出错。
编写维护文件的目的,在于方便程序员维护程序,尤其是维护比较大的工程项目。
(2) 运行命令make -f makefile-lib, 则生成名为libmy.so的动态库文件。

1.4 共享动态库文件
    /etc/ld.so.conf文档中,存放着可被LINUX共享的动态链接库所在目录的名字(系统目录/lib,/usr/lib除外)。我们可以把自己制作的动态库放到/lib或/usr/lib;也可以新建用户自己的库文件夹:/home/uname/lib,然后把此文件夹路径写入/etc/ld.so.conf中(并把自己的动态库复制到该文件夹下)。
#ldconfig        //刷新缓存文档/etc/lb.so.cache,使共享设置生效
    然后就可以使用动态库文件libmy.so了。


2 动态库文件的使用 
2.1 重要的dlfcn.h头文件 
    LINUX下使用动态链接库,源程序需要包含dlfcn.h头文件,此文件定义了调用动态链接库的函数的原型。下面详细说明一下这些函数。

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

2.1.2 dlopen
原型为: void *dlopen (const char *filename, int flag);
dlopen用于打开指定名字(filename)的动态链接库,并返回操作句柄。
filename: 如果名字不以/开头,则非绝对路径名,将按下列先后顺序查找该文件。
(1) 用户环境变量中的LD_LIBRARY值;
(2) 动态链接缓冲文件/etc/ld.so.cache
(3) 目录/lib,/usr/lib
flag表示在什么时候解决未定义的符号(调用)。取值有两个:
    1) RTLD_LAZY : 表明在动态链接库的函数代码执行时解决。
    2) RTLD_NOW : 表明在dlopen返回前就解决所有未定义的符号,一旦未解决,dlopen将返回错误。
dlopen调用失败时,将返回NULL值,否则返回的是操作句柄。

2.1.3 dlsym : 取函数执行地址
原型为: void *dlsym(void *handle, char *symbol);
dlsym根据动态链接库操作句柄(handle)与符号(symbol),返回符号对应的函数的执行代码地址。由此地址,可以带参数执行相应的函数。
如程序代码: void (*add)(int x,int y); /* 说明一下要调用的动态函数add */
add=dlsym("xxx.so","add"); /* 打开xxx.so共享库,取add函数地址 */
add(89,369); /* 带两个参数89和369调用add函数 */

2.1.4 dlclose : 关闭动态链接库
原型为: int dlclose (void *handle);
dlclose用于关闭指定句柄的动态链接库,只有当此动态链接库的使用计数为0时,才会真正被系统卸载。 

2.2 在程序中使用动态链接库函数
2.2.0
动态库的分为隐式调用和显式 调用两种调用方法。
隐式调用的使用使用方法和静态库的调用差不多,具体方法如下:
    gcc -c -I/home/weil/inc main.c 
    gcc -o main1 -L/home/ weil /lib main.o libmytime.so   //这里是*.so
      在这种调用方式中,需要维护动态链接库的 配置文件/etc/ld.so.conf 来让动态链接库为系统所使用,通常将动态链接库所在目录名追加到动态链接库配置文件中。否则在执行相关的可执行文 件的时候就会出现载入动态链接库失败的现象。 在编译所引用的动态库时,可以在gcc采用 –l或-L选项或直接引用所需的动态链接库方式进行编译。
      在Linux里面,可以采用ldd命令来检查程序依赖共享库。
显式调用 :
    gcc -o main -ldl main.c 
    用gcc编译对应的源文件生成可执行文件,-ldl选项,表示生成的对象模块需要使用共享库。

    在示例程序ady.c中调用动态库libmy.so中的getDate()和getTime()函数,用来显式系统的当前时间。
2.2.1 编写ady.c (本例中使用显示调用的方法)
[cpp] view plain copy
/* 
 * ady.c: 动态链接库应用示范程序 
 */  
#include<stdio.h>  
#include<dlfcn.h>  
#include<string.h>  
#define SOFILE "libmy.so"  
#include "adatetime.h"  
int main()  
{  
    DATETYPE d;  
    TIMETYPE t;  
    void *dp;  
    char *error;  
    printf("A sample for loading SO!/n");  
    dp = dlopen(SOFILE, RTLD_LAZY);   /* 打开动态链接库 */  
    if(dp == NULL)  
    {  
        fputs(dlerror(), stderr);  
        return -1;  
    }  
    int (*getDate)();   /*定义指针函数用于获取动态库中getDate()的入口地址*/  
    getDate = dlsym(dp, "getDate");  

    error=dlerror(); /* 检测错误 */  
    if ( error ) /* 若出错则退出 */  
    {  
        fputs(error,stderr);  
        return -1;  
    }  

    getDate(&d); /* 调用此共享函数 */  
    printf("Current Date: %04d-%02d-%02d/n",d.year,d.month,d.day);  

    int (*getTime)();  
    getTime=dlsym(dp,"getTime"); /* 定位取时间函数 */  
    error=dlerror(); /* 检测错误 */  
    if (error) /* 若出错则退出 */  
    {  
        fputs(error,stderr);  
        return -1;  
    }  
    getTime(&t); /* 调用此共享函数 */  
    printf("Current Time: %02d:%02d:%02d/n",t.hour,t.min,t.sec);  

    dlclose(dp); /* 关闭共享库 */  
    return 0;  
}   

2.2.2 编写维护文件makefile
[ruby] view plain copy
#makefile: 示例程序维护文件  
all: ady  
DYSRC = ady.c  
DYTGT = $(DYSRC:.c=.o)  
%.o:%.c  
    cc -c $?  
#动态库示例  
ady:$(DYTGT)  
    cc -rdynamic -s -o $@ $(DYTGT) -ldl  


2.3 编译、运行测试程序
#make
#./ady
可显式系统当前日期、时间。

linux动态连接库

阅读数 1063

没有更多推荐了,返回首页