引言
我们在使用 target 相关指令时,它们多有一个共同类型的参数 PRIVATE、INTERFACE、PUBLIC 那么这几个参数到底是什么意思呢?本文将为大家揭晓。
传播机制
为了说明清楚,我们以一个 demo 开始,首先给出项目结构:
1 | test |
库分析
libA
1
2
3
4
5
6
7
8
void libA();在 libA 对外提供的头文件中,引入了 libB 库的头文件 libB.h。同时声明了一个函数
void libA()
我们在 libA.c 中实现该函数:1
2
3
4
5
6
7
8
void libA()
{
printf("this info from libA and the private data is %s\n", PRIVATE_HEADER_A);
}以上代码中,我们引入了 privateHeaderA.h 头文件:
1
2
3
4
5
6内容很简单,只是定义一个宏而已。该头文件不对外暴露,一会我们通过 CMakeLists.txt 文件就可以看到。我们在 CMakeLists.txt 中定义该库的内容:
1
2
3
4
5
6
7
8
9
10
11add_library(libA STATIC libA.c)
target_include_directories(libA
PUBLIC include
PRIVATE .
)
target_link_libraries(libA
PUBLIC libB
)
target_compile_definitions(libA PRIVATE -DTEST_LIB_A)- 1 行,我们添加一个静态库 libA。
- 3 行,我们指定 libA 的链接头文件,其中 include 下的头文件定义为 PUBLIC 作为对外开放的头文件,而当前路径下的头文件(privateHeaderA.h)定义为 PRIVATE 也就是不对外开放。
- 8 行,libA 链接了 libB,这里定义为 PUBLIC,为什么是 PUBLIC 是因为 libA 对外提供的头文件 libA.h 中引入了 libB 的头文件 libB.h,所以,必须把 libB 传递给需要链接到 libA 的那一端。否则会提示无此头文件。
- 11 行,给 libA 库添加一个宏,并将它定义为 PRIVATE,稍后测试该宏的可见性。
libB
我们看一下 libB 的头文件内容:
1
2
3
4
5
6
void libB();在 libB 的头文件中,我们只声明了一个函数
libB()
。我们看下libB()
的实现:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
void libB()
{
printf("this info from libB and it's private data is %s\n", PRIVATE_HEADER_B);
libC();
puts("libB can access marco from C");
}从以上代码中我们看出,libB.c 引入了私有的 privateHeaderB.h 头文件以及 libC.h 头文件,并在代码里调用了 libC.h 中的函数
libC()
,并在接下来测试了 libC 定义的 PUBLIC 宏。从以上代码我们可以分析出,libB.c 隐藏了调用库 libC 的细节。privateHeaderB.h 也十分简单,仅仅定义一个宏:1
2
3
4
5
6我们看下它的 CMakeLists.txt 文件:
1
2
3
4
5
6
7
8
9
10add_library(libB STATIC libB.c)
target_include_directories(libB
PUBLIC include
PRIVATE .
)
target_link_libraries(libB PRIVATE libC)
target_compile_definitions(libB PUBLIC -DTEST_LIB_B)我们着重看下 8 行,libB 以 PRIVATE 的方式链接了 libC,因为 libB 中只是 libB.c 源码使用了 libC 的代码,libB 对外暴露的头文件并没有暴露 libC 的相关信息,所以 libB 没有必要向外部传递 libC 的信息,所以这里用 PRIVATE 关键字。另外 10 行,向 libB 添加了 TEST_LIB_B 宏,并设置可见性为 PUBLIC。
libC
我们接下来看下 libC 库,该库不依赖任何库,只是单纯的提供功能。我们看下它的头文件:
1
2
3
4
5
6
void libC();对外的头文件只是提供一个
libC()
函数。以下是它的实现:1
2
3
4
5
6
7
8
9
10
11
12
13
14
void libC()
{
printf("this info from libC and it's private data is %s\n", PRIVATE_HEADER_C);
sub();
puts("libC's test macro has been opened");
}这里
libC()
函数调用了它子模块中的一个函数sub()
,同时添加一个宏判断。privateHeaderC.h 头文件依旧很简单:1
2
3
4
5
6sub.h 内容也只是声明一个函数:
1
2
3
4
5
6
void sub();sub.c 的实现:
1
2
3
4
5
6
7
void sub()
{
puts("the info from sub function of libC");
}最后我们看下它的 CMakeLists.txt 文件:
1
2
3
4
5
6
7
8add_library(libC STATIC libC.c subModule/sub.c)
target_include_directories(libC
PUBLIC include
PRIVATE . subModule
)
target_compile_definitions(libC PUBLIC -DTEST_LIB_C)我们把 libC.c、subModule\sub.c 共同编译成库 libC,暴露 include 下的头文件,将当前路径以及 subModule 下的头文件隐藏掉。最后我们定义了一个 PUBLIC 级别的宏给库 libC。
libD
libD 是一个特殊的库,它只有头文件,没有源码。
1
2
3
4
5
6在它的头文件里,我们自定义个宏 LIB_D 。我们看下它的 CMakeLists.txt 文件:
1
2
3
4
5
6
7add_library(libD INTERFACE)
target_include_directories(libD
INTERFACE libD.h
)
target_compile_definitions(libD INTERFACE -DTEST_LIB_D)注意添加库时,我们这里指定了 INTERFACE 关键字,同时在链接头文件时,也指定了 INTERFACE。在最后我们定义的宏也必须是 INTERFACE。也就是说,当库的可见级别为 INTERFACE 时,所有的链接可见性都必须是 INTERFACE。这其实是说的通的,因为只有向外暴露接口时才会是 INTERFACE。如果保函源码了,那它就得是 PUBLIC 级别,否则就只能是 PRIVATE 了。
main 函数
既然库都已经准备好了,我们最后的重点就是 main 函数了。首先我们先看下它的 CMakeLists.txt 文件:
1 | cmake_minimum_required(VERSION 3.21) |
我们只需要关注第 8 行,我们给 test 可执行目标以 PRIVATE 级别链接了 libA 和 libD。也就是说 test 不会暴露任何信息给它的依赖方。我们看下 main.c:
1 |
|
输出
1
2
3
4
5
6
7
8
9this info from libA and the private data is PRIVATE_HEADER_A
this info from libB and it's private data is PRIVATE_HEADER_B
this info from libC and it's private data is PRIVATE_HEADER_C
the info from sub function of libC
libC's test macro has been opened
libB can access marco from C
libD is LIB_D is only a header
can access marco from B
can access marco from D
分析
- 因为链接了 libA,所以
libA()
函数可以直接调用,输出第 1 行内容。 - 由于 libA 对外提供的头文件 libA.h 引入了 libB.h 头文件,同时 libA 以 PUBLIC 方式链接库 libB,所以,main.c 同样可以调用
libB()
,并输出第 2 行。 - 由于
libB()
函数内部调用了 libC 库的函数libC()
而libC()
函数有调用了sub()
,同时libC()
又进行了宏判断。所以输出了 3 ~ 5 行内容。libB 对 libC 中定义的 PUBLIC 宏进行了定义测试,导致输出了第 6 行内容。 - main 链接了 libD,所以可以读取 libD 头文件中的宏信息,于是输出第 7 行。
- 接下来是宏输出,因为 libA 的宏可见性为 PRIVATE,所以 main 不可见。libB 的宏设置为 PUBLIC,而 libB 又是通过 libA 暴露给 main 的,所以第 8 行得以输出。虽然 libC 的宏也是 PUBLIC,但是它被 libB 隐藏掉了。那么 main 自然是访问不了 libC 的宏的,但是 libB 可以访问。最后 libD 中定义的 INTERFACE 宏可以顺利被 main 访问,输出第 9 行。
总结
根据以上测试,如果我们定义库 A 以及库 B。
- 如果库 A 只有在自己的源码中使用了库 B 的功能且库 A 对外提供的头文件不包含任何库 B 中的信息时,我们用 PRIVATE 进行链接库 B。
- 如果库 A 在它对外暴露的头文件中包含了库 B 中的头文件,同时库 A 的源码里也引入了库 B 中的实现,那么库 A 必须以 PUBLIC 方式链接库 B(当其他库链接库 A 时,将库 B 暴露给它)。
- 如果库 A 只在它对外提供的头文件中引入了库 B 的头文件,库 A 的代码并没有引入(也就是库 A 的源码也没有引入它对外提供的头文件)或者库 B 本身就是一个 head-only 库,那么必须使用 INTERFACE 进行链接。