C语言动态链接库
1. 概述
在实际编程中,我们可以把完成某项功能的函数放在一个动态链接库里,然后提供给其他程序调用。
1.1 静态库和动态库
- 静态库:这类库的名字一般是libxxx.a,在使用静态库的情况下,在编译链接可执行文件时,链接器从静态库中复制这些函数和数据,并把它们和应用程序的其他模块组合起来创建最终的可执行文件(.exe)。当发布产品时,只需要发布这个可执行文件,并不需要发布被使用的静态库。
- 动态库:是一种不可执行的二进制程序文件,它允许程序共享执行特殊任务所必需的代码和其他资源。Windows平台上动态链接库的后缀名是”.dll”,Linux平台上的后缀名是“.so”。Linux上动态库一般是libxxx.so;相对于静态函数库,动态函数库在编译的时候并没有被编译进目标代码中,你的程序执行到相关函数时才调用该函数库里的相应函数,因此动态函数库所产生的可执行文件比较小。由于函数库没有被整合进你的程序,而是程序运行时动态的申请并调用,所以程序的运行环境中必须提供相应的库。动态函数库的改变并不影响你的程序,所以动态函数库的升级比较方便。
1.2 动态链接库的优点
- 复用性:DLL的编制与具体的编程语言以及编译器无关,不同语言编写的程序只要按照函数调用约定就可以调用同一个DLL函数
- 可扩展性:DLL文件与EXE文件独立,只要接口不变,升级程序只需更新DLL文件不需要重新编译应用程序
- 节省内存:如果多个应用程序使用同一个dll,该dll的页面只需要存入内存一次,所有的应用程序都可以共享它的页面,从而节省内存
2. 生成动态链接库
2.1 windows版本
下面以codeblocks编译器为例,其他编译器也就是建立dll工程不一样:
File->New->Projects->Dynamic Link library->Go
项目的命名就是最后dll的名字,新建main.c和main.h
main.c
#include "main.h"
#include <stdio.h>
/* 输入年月日计算一年中第几天 */
int Day_of_year(int year, int month, int day)
{
int sum,leap;
switch(month) // 先计算某月以前月份的总天数
{
case 1:sum=0;break;
case 2:sum=31;break;
case 3:sum=59;break;
case 4:sum=90;break;
case 5:sum=120;break;
case 6:sum=151;break;
case 7:sum=181;break;
case 8:sum=212;break;
case 9:sum=243;break;
case 10:sum=273;break;
case 11:sum=304;break;
case 12:sum=334;break;
default:printf("data error");break;
}
sum=sum+day; // 再加上某天的天数
if(year%400==0||(year%4==0&&year%100!=0)) {// 判断是不是闰年
leap=1;
} else {
leap=0;
}
if(leap==1&&month>2) { // *如果是闰年且月份大于2,总天数应该加一天
sum++;
}
return sum;
}
main.h
#ifndef __MAIN_H__
#define __MAIN_H__
#include <windows.h>
#ifdef __cplusplus
#define EXPORT extern "C" __declspec (dllexport)
#else
#define EXPORT __declspec (dllexport)
#endif // __cplusplus
EXPORT int Day_of_year(int year, int month, int day);
#endif // __MAIN_H__
编译成功后在bin\Debug目录下生成3个文件:dll.dll,libdll.a,libdll.def
2.2 Linux版本
main.c
#include "main.h"
#include <stdio.h>
/* 输入年月日计算一年中第几天 */
int Day_of_year(int year, int month, int day)
{
int sum,leap;
switch(month) // 先计算某月以前月份的总天数
{
case 1:sum=0;break;
case 2:sum=31;break;
case 3:sum=59;break;
case 4:sum=90;break;
case 5:sum=120;break;
case 6:sum=151;break;
case 7:sum=181;break;
case 8:sum=212;break;
case 9:sum=243;break;
case 10:sum=273;break;
case 11:sum=304;break;
case 12:sum=334;break;
default:printf("data error");break;
}
sum=sum+day; // 再加上某天的天数
if(year%400==0||(year%4==0&&year%100!=0)) {// 判断是不是闰年
leap=1;
} else {
leap=0;
}
if(leap==1&&month>2) { // *如果是闰年且月份大于2,总天数应该加一天
sum++;
}
return sum;
}
main.h
#ifndef __MAIN_H__
#define __MAIN_H__
int Day_of_year(int year, int month, int day);
#endif
在命令行下输入 gcc -shared -fPIC main.c -o libday.so
即可生成一个名为libday.so的动态链接库
3. 调用动态链接库
3.1 windows版本
3.1.1 隐式调用
新建工程,把上面生成的dll.dll和libdll.a(不可缺)拷贝到新工程的bin\Debug目录下
main.c
#include <stdio.h>
#include "main.h"
int main()
{
printf("day = %d\n", Day_of_year(2015,10,1) );
system("pause");
return 0;
}
main.h 保持一样
#ifndef __MAIN_H__
#define __MAIN_H__
#include <windows.h>
#ifdef __cplusplus
#define EXPORT extern "C" __declspec (dllexport)
#else
#define EXPORT __declspec (dllexport)
#endif // __cplusplus
EXPORT int Day_of_year(int year, int month, int day);
#endif // __MAIN_H__
Project - Build options - Linker settings - Add 选择 bin\Debug\libdll.a - 确定,然后再编译即可
3.1.2 显示调用(推荐)
新建工程,把dll.dll拷贝到新工程的bin\Debug目录下
main.c
#include <stdio.h>
#include <windows.h>
typedef int(*Getday)(int, int, int); //定义函数类型
HINSTANCE hDll; //DLL句柄
Getday getday;
int main()
{
hDll = LoadLibrary("dll.dll"); //加载 dll
getday = (Getday)GetProcAddress(hDll, "Day_of_year");//通过指针获取函数方法
printf("day = %d\n", getday(2015, 10, 1) );//调用函数
FreeLibrary(hDll);//释放Dll句柄
system("pause");
return 0;
}
编译就可以使用,当dll程序升级时,只需要替换dll,而不用重新编译exe
3.2 Linux版本
linux版本动态链接库没有windows版本那么多文件,只有一个so文件
3.2.1 隐式调用
test.c
#include <stdio.h>
#include "main.h"
int main()
{
printf("day = %d\n", Day_of_year(2015, 10, 1));
}
在命令行输入 gcc -o test test.c -L./ libday.so
然后执行编译生成的可执行文件 ./test
注意加上导出函数头文件main.h,-L指定动态链接库的搜索路径
3.2.2 显式调用(推荐)
test.c
#include <stdio.h>
#include <dlfcn.h> // 显式加载需要用到的头文件
int main()
{
void *pdlHandle = dlopen("./libday.so", RTLD_LAZY); // RTLD_LAZY 延迟加载
char *pszErr = dlerror();
if( !pdlHandle || pszErr )
{
printf("Load lib.so failed!\n");
return 1;
}
int (*Day_num)() = dlsym(pdlHandle, "Day_of_year"); // 定位动态链接库中的函数
if( !Day_num )
{
pszErr = dlerror();
printf("Find symbol failed!%s\n", pszErr);
dlclose(pdlHandle);
return 1;
}
printf("day = %d\n", Day_num(2015, 10, 1)); // 调用动态链接库中的函数
dlclose(pdlHandle); // 系统动态链接库引用数减1
return 0;
}
在命令行输入 gcc -o test test.c -ldl
然后执行编译生成的可执行文件 ./test
优点:不必在编译时就确定要加载哪个动态链接库,可以在运行时再确定。
3.2.3 调试案例
1)错误一
/tmp/ccMpgzNu.o: In function `main':
test.c:(.text+0x13): undefined reference to `dlopen'
test.c:(.text+0x1c): undefined reference to `dlerror'
test.c:(.text+0x53): undefined reference to `dlsym'
test.c:(.text+0x63): undefined reference to `dlerror'
test.c:(.text+0x89): undefined reference to `dlclose'
test.c:(.text+0xc7): undefined reference to `dlclose'
collect2: error: ld returned 1 exit status
解决方案:
- 头文件添加:#include <dlfcn.h>
- 编译选项加上- ldl,即 gcc -o test test.c -ldl ,网上有gcc -ldl -o test test.c,这种方式也是会报这个错误的
2)错误二
error while loading shared libraries: libtiger.so: cannot open shared object file: No such file or direct
我的这段代码里则会打印Load lib.so failed!
解决方案:
- 在程序代码里配置路径void *pdlHandle = dlopen(“libday.so”, RTLD_LAZY);
将动态链接库配上路径,如 ./libday.so表示可执行文件与链接库同一路径 - 将动态链接库的目录放到程序搜索路径中,可以将库的路径加到环境变量
export LD_LIBRARY_PATH=pwd
:$LD_LIBRARY_PATH(pwd带反撇号的哈) - 拷贝libday.so到绝对目录 /lib 下(但是要超级用户才可以,因此要使用sudo哦)