UC成长之路3
一、动态库的制作和使用
动态库的命名:lib库名.so
- 将要加入库文件的源码文件编译成与位置无关的目标文件,
gcc -c -fPIC *.c
pmath$ls
add.c add.o mul.c mul.o pmath.h
pmath$rm *.o
pmath$ls
add.c mul.c pmath.h
pmath$gcc -c -fPIC *.c
pmath$ls
add.c add.o mul.c mul.o pmath.h
- 将这些目标文件打包成动态库文件,
gcc -shared -o libpmath.so *.o
pmath$gcc -shared -o libpmath.so *.o
pmath$ls
add.c add.o libpmath.so mul.c mul.o pmath.h
- 使用动态库链接生成可执行文件
dongtai$gcc main.o -Lpmath -lpmath
dongtai$ls
a.out main.c main.o pmath
error:
dongtai$./a.out
./a.out: error while loading shared libraries: libpmath.so: cannot open shared object file: No such file or directory
动态链接:代码加载到内存执行的时候才发生的链接
链接器:就是执行gcc main.o -Lpmath -lpmath
命令
加载器:就是执行./a.out
可执行文件时夹在程序
发生错误的原因: -L -l 告诉链接器
动态库的位置,但加载器
不知道动态库的位置
使用命令:ldd a.out
查看加载动态库情况,就是查看程序需要的动态库
dongtai$ldd a.out
linux-vdso.so.1 => (0x00007ffcdffb4000)
libpmath.so => not found
libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007f4fc4ea1000)
/lib64/ld-linux-x86-64.so.2 (0x00007f4fc526b000)
解决方案:告诉加载器动态库的位置,两种方法:
- 使用这个环境变量:LD_LIBRARY_PATH告诉加载器搜索动态库的路径
dongtai$LD_LIBRARY_PATH=$LD_LIBRARY_PATH:pmath
dongtai$export LD_LIBRARY_PATH
dongtai$./a.out
6+2=8
hello beijing
- 使用默认的路径,就是将动态库文件移动到加载器的默认路径下
加载器的默认路径是: /usr/lib 或 /lib
连接器的默认路径也是:/usr/lib 或 /lib
小结: 动态链接和静态链接
- 静态链接静态库文件的时候,将目标文件的代码合并拷贝,可执行文件比较大,可执行文件不依赖静态库文件
- 动态链接动态库文件的时候,生成的可执行文件中,使用到的动态库文件的函数是未定义引用的,函数的地址不确定,在执行的时候才确定函数的地址,可执行文件依赖与动态库文件,可执行文件的大小比静态库链接生成的可执行文件小。
二、动态加载
代码执行的时候,自动加载
在程序执行的过程中,根据需要加载相应的动态库函数。
系统提供了函数接口:
- dlopen(3)
man 3 dlopen
DLOPEN(3) Linux Programmer's Manual DLOPEN(3)
NAME
dlclose, dlopen, dlmopen - open and close a shared object
SYNOPSIS
#include <dlfcn.h>
void *dlopen(const char *filename, int flags);
//功能:加载指定的动态库文件
//参数:
//filename:指定了要加载的动态库文件的名字
//flag:1)RTLD_LAZY:懒加载 or 2)RTLD_NOW:立即加载
//返回值:NULL错误 or 非NULL成功
int dlclose(void *handle);
//功能:动态库的应用计数减1
//参数:
//handle:dlopen(3)的返回值
//返回值:成功0, 错误非0
char *dlerror(void);
//功能:获取最近的dlopen dlsym dlclose函数调用的错误信息
//参数:void
//返回值:一个字符串,描述了函数调用的错误信息
void *dlsym(void *handle, const char *symbol);
//功能:在动态库里找symbol指定的函数,将函数的地址返回
//参数:
//handle:dlopen(3)的返回值,指定了动态库
//symbol:指定符号名字,可认为是函数名
//返回值:NULL在动态库里没用这个函数,否则返回函数在内存的地址
Link with -ldl.
//gcc -ldl:dl是一个动态库名字
引用计数:使用共享库(动态库)进程的计数
举例说明:动态加载动态库文件libpmath.so
,这个动态库的位置是:/lib
, 代码参见:dynamic.c
- :
dynamic.c
#include <stdio.h>
#include <dlfcn.h>
int main(void)
{
//加載動態庫文件libpmath.so /lib
void *p = dlopen("libpmath.so", RTLD_NOW);
if(p==NULL){
printf("dlopen fails...\n");
return -1;
}
printf("dlopen success...\n");
//關閉動態庫的加載
dlclose(p);
return 0;
}
#include <stdio.h>
#include <dlfcn.h>
int main(void)
{
//f_t是變量,指針類型的
int (*f_t)(int, int);
//加載動態庫文件libpmath.so /lib
void *p = dlopen("libpmath.so", RTLD_NOW);
if(p==NULL){
printf("%s\n", dlerror());
return -1;
}
printf("dlopen success...\n");
//在動態庫找函數,並且返回函數的地址
void *f=dlsym(p,"p_add");
if(f==NULL){
printf("%s\n",dlerror());
return -1;
}
f_t = f;
//f變量空間裏是一個地址,是p_add函數的入口地址
printf("3+2=%d\n", f_t(3,2));
//關閉動態庫的加載
dlclose(p);
return 0;
}
上面的程序也可写成下面的形式
#include <stdio.h>
#include <dlfcn.h>
int main(void)
{
//使用typedef
typedef int (*f_t)(int, int);
void* handle = dlopen("libpmath.so", RTLD_NOW);
if(NULL == handle){
printf("%s\n", dlerror());
//printf("dlopen fail\n");
return -1;
}
printf("dlopen success...\n");
void* f = dlsym(handle, "add");
if(NULL == f){
printf("%s\n", dlerror());
dlclose(handle);
return -1;
}
printf("2+3=%d\n", ((f_t)f)(2, 3));
dlclose(handle);
return 0;
}
三、程序中的错误处理
举例说明:代码参见file.c
#include <stdio.h>
//從命令行中傳入參數
int main(int argc, char *argv[])
{
FILE *fp;
//打開文件,文件的名字從命令行的第一個參數傳入
fp=fopen(argv[1], "r");
if(fp==NULL){
printf("fopen fails...\n");
return -1;
}
printf("open file success...\n");
//關閉文件
fclose(fp);
return 0;
}
系统中有一个全局变量errno。记录了最近函数调用产生的错误编号
extern int errno
errno | description |
---|---|
number | string |
要使用errno 需要包含头文件#include <errno.h> |
#include <stdio.h>
#include <errno.h>
//從命令行中傳入參數
int main(int argc, char *argv[])
{
FILE *fp;
//打開文件,文件的名字從命令行的第一個參數傳入
fp=fopen(argv[1], "r");
if(fp==NULL){
printf("fopen fails...D=%d\n", errno);
return -1;
}
printf("open file success...\n");
//關閉文件
fclose(fp);
return 0;
}
只是获得errno的值,想要获取相应的描述信息。如何获取?
- perror(3)
#include <stdio.h>
void perror(const char *s);
//功能:输出一条系统错误的消息
//参数:
//s:字符串的首地址
//返回值:
eg:
#include <stdio.h>
#include <errno.h>
//從命令行中傳入參數
int main(int argc, char *argv[])
{
FILE *fp;
//打開文件,文件的名字從命令行的第一個參數傳入
fp=fopen(argv[1], "r");
if(fp==NULL){
//printf("fopen fails...D=%d\n", errno);
perror("fopen");
return -1;
}
printf("open file success...\n");
//關閉文件
fclose(fp);
return 0;
}
- strerror(3)
#include <string.h>
char *strerror(int errnum);
//功能:获取错误编号的错误描述信息
//参数:
//errnum:指定了错误的编号
//返回值:如果找到这个编号对应的错误描述信息,获取信息;如果错误编号没有对应的错误描述信息返回“Unknow error nnn”
eg:
#include <stdio.h>
#include <errno.h>
#include <string.h>
//從命令行中傳入參數
int main(int argc, char *argv[])
{
FILE *fp;
//打開文件,文件的名字從命令行的第一個參數傳入
fp=fopen(argv[1], "r");
if(fp==NULL){
//printf("fopen fails...D=%d\n", errno);
//perror("fopen");
printf("%s\n", strerror(errno));
return -1;
}
printf("open file success...\n");
//關閉文件
fclose(fp);
return 0;
}
附件:
我的目录结构
- dongtai
- main.c
- pmath
- add.c
- mul.c
- pmath.h
main.c
#include <pmath.h>
#include <stdio.h>
//框架
int main(void)
{
int a=6, b=2;
printf("%d+%d=%d\n", a, b, p_add(a,b));
//組件
printf("hello beijing\n");
return 0;
}
add.c
#include "pmath.h"
int p_add(int a, int b)
{
return a+b;
}
int p_sub(int a, int b)
{
return a-b;
}
mul.c
#include "pmath.h"
int p_mul(int a, int b)
{
return a*b;
}
int p_div(int a, int b)
{
return a/b;
}
pmath.h
#ifndef PMATH_H_
#define PMATH_H_
/*函數的定義*/
int p_add(int, int);
int p_sub(int, int);
int p_mul(int, int);
int p_div(int, int);
#endif