动态链接库

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哦)
  • 19
    点赞
  • 161
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值