Loading

编译生成 C++ 库文件

 

我们介绍 Windows 和 Ubuntu 下生成和使用库文件的操作。

 

lib

lib 是 Windows 下的静态库,它具有以下特点:

  1. 运行不存在
  2. 静态库源码被链接到调用程序中
  3. 目标程序的归档

静态库是将代码嵌入到使用程序中,多个程序使用时会有多份代码,所以代码体积会增大。动态库的代码只需要存在一份,其它程序通过函数地址使用,所以代码体积小。静态库发生变化后,新的代码需要重新链接嵌入到执行程序中。

 

要创建静态库,首先在 VS 中建立库项目,这样会产生一些默认框架,如果不想要的话可以先生成控制台项目,然后设置“项目属性-配置类型”为静态库即可;然后直接添加源代码和头文件,右击项目选择“生成”即可产生 .lib 文件

 

生成静态库后,将 .lib 文件和头文件复制到指定文件目录下导入

#include "../lib/head.h" 					// 导入静态库头文件
#pragma comment(lib, "../lib/clib.lib") 	 // 使用 pragma 关键字设置库路径

需要注意,如果使用项目创建静态库文件,之后新建项目测试库文件时,要将新建的项目设为启动项目,否则程序会将库文件错认为可执行文件,导致报错

 

有时我们需要使用 C 语言的静态库,但由于 C++ 编译器会对函数进行“换名”,而 C 编译器则不会,则当调用 C 语言静态库时会导致函数名无法识别,这时就需要使用 extern 声明

extern "C" int add(int, int); 			// 以 C 编译器方式编译函数声明
#pragma comment(lib, "../lib/clib.lib")  // 使用 pragma 关键字设置库路径

 

dll

dll 是 Windows 下的动态库,它具有以下特点:

  1. 运行时独立存在
  2. 源码不会链接到执行程序
  3. 使用时加载(使用动态库必须使动态库执行)

动态库变化后,如果库中函数的定义(或地址)未变化,其它使用 DLL 的程序不需要重新链接。

 

动态库数据存放

  • DLL 文件中存放函数名及其相对于文件首地址的相对地址,还有函数源码信息
  • LIB 文件中存放函数名及其编号,还有 DLL 文件名

需要注意:使用动态库需要有 .dll 文件和对应的 .lib 文件,其中 .lib 不是静态库。

 

创建动态库

  • 创建动态库项目
  • 添加库程序
  • 导出库程序:提供给库使用者函数信息
    • 声明导出:使用 _declspec(dllexport) 导出函数地址
      • 注意:动态库编译链接后,也会生成 LIB 文件作为动态库函数映射使用,通过 LIB 文件可以链接到动态库
    • 模块定义文件 .def 导出不改名的函数
//声明导出动态库中的函数
//库文件
_declspec(dllexport) int add(int a, int b)
{
    return a + b;
}
//声明导出会导出改名的函数

//模块定义文件导出函数 .def
LIBRARY dll_name 	 //导出的库名
EXPORTS 			//库导出表
func_name1	@1		//导出的函数1
func_name2	@2		//导出的函数2
func_name3	@3		//导出的函数3
   	...
//通过这种方式导出函数,能够得到不改名的函数

 

链接动态库

隐式链接:操作系统负责使动态库执行

  • 头文件和函数原型:在函数原型声明前增加 _declspec(dllimport) ;如果声明在头文件中,需要使用 DLLCLASS_EXPORTS 宏方法
  • 导入动态库的 LIB 文件
  • 在程序中使用函数
  • dll 存放路径:
    • 与执行文件同目录(推荐)
    • 当前工作目录
    • Windows 目录
    • Windows/System32 目录
    • Windows/System
    • 环境变量 PATH 指定目录
// 导入动态库中的函数
_declspec(dllimport) int add(int, int);

// 通知链接器到 .lib 文件中获取函数编号和 .dll 文件名
#pragma comment(lib, "../lib/clib.lib")

 

显式链接:程序员负责使动态库执行

  • 定义函数指针类型 typedef
  • 加载动态库 LoadLibrary
  • 获取函数地址 GetProcAddress
  • 使用函数
  • 卸载动态库 FreeLibrary

我们需要通过以下函数实现

// 返回 DLL 实例句柄
HMODULE LoadLibrary(
    LPCTSTR lpFileName	// 动态库文件名或全路径
);

// 获取函数地址
FARPROC GetProcAddress(
    HMODULE hModule,	// DLL 句柄
    LPCSTR lpProcName	// 函数名
);

//卸载动态库
BOOL FreeLibrary(
    HMODULE hModule,	// DLL 句柄
);

最后给出显式链接动态库的示例:

//定义函数指针
typedef int (*ADD)(int, int);

int main()
{
    HINSTANCE hDll = LoadLibrary("CPPdll.dll");		// 加载动态库
    ADD myAdd = (ADD)GetProcAddress(hDll, "add");	// 这里使用模块文件导出方法,才能使用不改名的函数名为参数
    
    int sum = myAdd(5, 4);
    cout << "sum = " << sum << endl;
    
    FreeLibrary(hDll);							  // 卸载动态库
    return 0;
}

 

动态库中封装类

在类名前增加 _declspec(dllexport) 定义,导出类成员函数的相对地址,通常使用预编译开关切换类的导入导出定义,例如

#ifdef DLLCLASS_EXPORTS
#define EXT_CLASS _declspec(dllexport)
#else
#define EXT_CLASS _declspec(dllimport)
#endif

如果定义了这一宏,则定义导出,否则定义导入。通过这种方式,程序员在库文件中定义 DLLCLASS_EXPORTS 宏,就可以使用导出类;用户只需要包含头文件,就可以使用导入类

// 导入/导出类
class EXT_CLASS CMath
{
	//...
    int sum(int a, int b);
    int sub(int a, int b);
};

 

a

a 文件是 Ubuntu 下的静态库,其在主函数编译时就将库导入

# 生成 .o 文件
g++ swap.cpp -Iinclude -c

# 生成静态库 libSwap.a
ar rs libswap.a swap.o

# 链接静态库生成 main
g++ -o main.cpp -Lsrc -lswap main

# 运行main
./main

注意生成的静态库要有 lib 前缀,使用时去掉 lib 前缀

我们也可以生成 Windows 下的静态库文件

ar rs swap.lib swap.o

 

so

so 文件是 Ubuntu 下的动态库,它不会直接编译进主函数,需要在调用时添加。其中使用了 -fPIC 参数,这在 LAPACK 编译过程中提到,它表示以相对地址来实现代码,从而使其可以加载到任意位置

# 生成动态库 swap.so
g++ swap.cpp -Iinclude -fPIC -shared -o libswap.so
# 以上指令等价于
# gcc swap.cpp -Iinclude -c -fPIC
# gcc -shared -o libswap.so swap.o

# 链接动态库生成main
g++ -o main.cpp -Lsrc -lswap main

# 运行 main,要将动态库所在路径添加到参数
LD_LIBRARY_PATH=src ./main

同样可以生成 Windows 下的动态库文件

gcc -shared -o swap.dll swap.c
posted @ 2022-03-13 22:05  Bluemultipl  阅读(792)  评论(0编辑  收藏  举报