动态链接库和静态链接库

链接库和程序一样是已编译的代码,不过不能够直接运行,是用来被其他程序使用的。

标准库中的函数也是以动态链接库的形式提供的。

不同操作系统不同编译器对于链接库的编写和使用方式有所区别。

使用链接库

复杂的程序不必每个功能都自己实现,可以使用链接库。

静态链接库

静态链接库的后缀一般是.lib,.so

在编写代码时,需要引入对应头文件,在链接时,静态链接库需要会和程序链接到一起。

在运行时,程序不需要静态链接库。

动态链接库

动态链接库的后缀一般是.dll,.o

在编写代码时,需要引入对应的头文件,在链接时,程序中使用到的函数等通过跳转对应到动态链接库中的代码。

在运行时,程序需要动态链接库存在。

动态链接库只要和头文件是兼容的,就可以重新编译发布,因此可以在不修改程序的情况下更新功能。

一般情况下,动态链接库使用的更多,优点也更明显。

C标准库大多是以动态链接库的形式提供。

编写链接库

编写链接库和一般程序相似,但有以下不同:

动态链接库示例

以下以Windows系统下动态链接库作为示例。

使用VS2019创建一个C++动态链接库项目,名称为learnc.libcalc

在链接库中

calc.h

#ifndef LEARNCLIBCALC_H
#define LEARNCLIBCALC_H

#ifdef LEARNCLIBCALC_EXPORTS

#define MYAPI   __declspec(dllexport)

#else

#define MYAPI   __declspec(dllimport)

#endif

MYAPI int lib_add(int a, int b);

#endif

calc.c

#include "calc.h"

int lib_add(int a, int b) {
    return a + b;
}

说明:

备注:

使用VS编译动态链接库,会产生.dll文件,.lib文件。

这里的.lib文件并非静态链接库,而只包含函数位置跳转信息,在编译时需要用到。

使用链接库

使用VS2019创建一个C++控制台项目,名称为learnc.libcalcuse

main.c

#include<stdio.h>
#include "../learnc.libcalc/calc.h"

int main()
{
    printf("%d\n", lib_add(100, 200));
}

使用时,引用头文件,和一般函数一致。

静态链接库

静态链接库的创建和动态链接库相似。

在使用VS时只是项目类型不同,VS可以在两种类型链接库中任意切换。

使用使用命令编译,需要传递不同参数。

函数的导出名称和模块定义文件

不同编译器对导出的函数名有自己的处理。使__cdecl导出的函数名和实际名称一致。使用__stdcall的函数名可以是_函数名2@8这样的形式。

为了明确函数的导出名称,可以使用模块定义文件.def来定义函数的导出名。

语法

LIBRARY 动态链接库名称
EXPORTS
函数名1=@n1
函数名2=@n2

使用def导出

calc.h

#ifndef LEARNCLIBCALC_H
#define LEARNCLIBCALC_H

int lib_add(int a, int b);

#endif

exports.def

LIBRARY learnc.libcalc
EXPORTS
lib_add

动态加载动态链接库

使用头文件和动态链接库的编译器链接的程序,运行时自动加载所需动态链接库。

通过代码加载动态链接库,加载其中的函数并执行,是动态加载。

不同操作系统动态加载动态链接库使用不同的函数。

Windows系统,位于windows.h

代码 功能
HMODULE Loadlibrary(LPTSTR libname) 加载动态链接库
FARPROC GetProcAddress(HMODULE lib,char* procname) 根据名称获取函数地址
BOOL FreeLibrary(HMODULE lib)) 释放加载的动态链接库

Linux系统,位于dlfcn.h

代码 功能
void* dlopen(char* libname,int flag) 加载动态链接库
RTLD_LAZY 加载方式,延迟加载
RTLD_NOW 加载方式,立即加载
void *dlsym(void* lib,char* name) 根据名称获取函数地址
int void dlclose(void* lib)) 释放加载的动态链接库

基本过程都是一样的,加载动态链接库文件,查找函数的入口,调用。

Windows系统下的动态加载示例

#include<stdio.h>
#include<windows.h>

typedef int (*cb)(int, int);

int main() {
    cb func = NULL;

    HMODULE hinst = LoadLibrary(TEXT("learnc.libcalc.dll"));

    if (hinst) {

        printf("模块地址:%p\n", hinst);

        func = (cb)GetProcAddress(hinst, "lib_add");

        printf("函数地址:%p\n", func);

        if (func) {
            printf("调用结果:%d\n", func(100, 200));
        }

        FreeLibrary(hinst);
    }
    return 0;
}

动态加载虽然不需要头文件,但是也要了解其中函数的参数和调用约定,否则无法成功调用。

优缺点对比