文章

C++ 动态库导出

C++ 动态库导出

该文主要介绍 C++ 动态库导出。

C++ 动态库导出

1. C 导出函数

1
2
3
4
5
6
7
#ifdef _WIN32
	#define DLL_API __declspec(dllexport)
#else
	#define DLL_API __attribute__((visibility("default")))
#endif

DLL_API void foo();

2. C++ 导出函数

2.1. 名称修饰

C++ 具有名称修饰(Name Mangling) 机制,编译器会修改函数名以包含类名、参数类型等信息。

  • 支持函数重载:同名函数可以根据参数类型进行区分。
  • 支持不同的命名空间和类:防止不同作用域中的函数/变量产生符号冲突。
  • 支持 C++ 特性:如类成员函数、模板、命名空间等,它们都可能引入符号冲突,需要独特的符号名。
但是这样就会导致函数在导出的时候名称与自己编写的不一致,在动态加载动态库的时候导致不能根据指定名称导入函数。

2.2. 解决办法

为了避免名称修饰,C++ 提供 extern "C" 关键字,使导出的符号保持 C 语言的命名规则

1
2
3
4
5
6
7
8
9
#ifdef _WIN32
	#define DLL_API __declspec(dllexport)
#else
	#define DLL_API __attribute__((visibility("default")))
#endif

extern "C" {
	DLL_API void foo();
}

2.3. MSVC导出问题

这样默认导出可以使导出函数与声明函数名称一致,但是如果再加上调用约定1,就又发生名称修饰。

这个解决办法就是编写 *.def 文件,指定导出函数名称。可以使用 .def 文件来控制 Windows 目标(如 DLL)的导出符号。.def 文件可以显式指定哪些函数要导出,而不需要使用 __declspec(dllexport)

2.4. 示例

dll.h 导出函数定义

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
#ifndef __DLL_H__
#define __DLL_H__

#if defined(WIN32) || defined(_WIN32)
    #define DLL_CALL __stdcall
    #ifdef DLL_EXPORT
        #define DLL_API __declspec(dllexport)
    #else
        #define DLL_API __declspec(dllimport)
    #endif
#elif defined(__linux__)
    #define DLL_CALL __attribute__((stdcall))
    #define DLL_API  __attribute__((visibility("default")))
#else
    #define DLL_CALL __attribute__((stdcall))
    #define DLL_API
#endif

int Add1(int a, int b);

DLL_API int Add2(int a, int b);

int DLL_CALL Add3(int a, int b);

DLL_API int DLL_CALL Add4(int a, int b);

#ifdef __cplusplus
extern "C" {
#endif

int Add5(int a, int b);

DLL_API int Add6(int a, int b);

int DLL_CALL Add7(int a, int b);

DLL_API int DLL_CALL Add8(int a, int b);

DLL_API int DLL_CALL Add9(int a, int b);

DLL_API int DLL_CALL Add10(int a, int b);

#ifdef __cplusplus
}
#endif

#endif

dll.cpp 导出函数实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
#include "dll.h"

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

DLL_API int Add2(int a, int b)
{
    return a + b;
}

int DLL_CALL Add3(int a, int b)
{
    return a + b;
}

DLL_API int DLL_CALL Add4(int a, int b)
{
    return a + b;
}

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

DLL_API int Add6(int a, int b)
{
    return a + b;
}

int DLL_CALL Add7(int a, int b)
{
    return a + b;
}

DLL_API int DLL_CALL Add8(int a, int b)
{
    return a + b;
}

DLL_API int DLL_CALL Add9(int a, int b)
{
    return a + b;
}

DLL_API int DLL_CALL Add10(int a, int b)
{
    return a + b;
}

dll.def 导出函数指定

1
2
3
4
5
6
LIBRARY "dll"

; @可以指定导出的 Ordinal
EXPORTS
    Add9
    Add10 @1

编译后导出函数如下图所示:

其中 Add1 Add3 Add5 Add7 函数没有导出;Add2 Add4 Add8 有导出,但是函数名称不一致;Add6 Add9 Add10 导出函数与定义函数名称一致;Add10Ordinal 为 1。

  • 使用了 DLL_API 是有导出函数
  • Add6 默认调用约定导出名称一致
  • Add9 Add10 使用 def 文件指定导出名称一致

参考

  1. https://learn.microsoft.com/zh-cn/cpp/build/reference/gd-gr-gv-gz-calling-convention?view=msvc-170 ↩︎

本文由作者按照 CC BY 4.0 进行授权