有时候,在插件或者其他的模块中,想要定义一个全局对象,供内部或者外部的模块访问,就像 UE 内置的 GEngine
/GConfig
这些。
首先需要把 C++ 中 声明 和定义 的概念区分开,我之前有篇文章介绍:C++ 中 declaration 与 define 的区别。
因为我们想要访问的 全局对象,应该只有一个实例,而不是每个使用模块都包含了一个定义的实例,所以只应该在有一个定义,其他模块引用时只是一个声明,声明它在外部模块中定义,应该去其他模块中访问该实例。
以 Windows 为例,UE 的模块,会编译成 DLL,如果我们想要访问某个模块的符号,就是要访问它的 DLL 中的符号。在 UE 中,导出符号使用 XXXX_API
封装,同样在之前的笔记中有记录:
以 A、B 两个模块为例:A_API
在 A 模块中被定义为 DLLEXPORT
,在 B 模块中被定义为DLLIMPORT
,所以,A 中使用A_API
定义一个符号,会控制它编译出的 A.dll 中该符号导出,在外部模块 B 中使用该符号时,就是导入。
理解起来或许有点绕,以代码来理解:
1 | // A.h |
在 B 中访问它:
1 | // B.cpp |
在 B 中包含 A.h,在 B 中,A.h 中的 extern A_API int32* GTestPtr;
就变成了以下代码:
1 | extern DLLIMPORT int32* GTestPtr; |
在编译 B 模块时,DLLIMPORT
会指导链接器 (linker) 去外部的模块查找该符号,从而实现在 B 中访问 A 中定义的全局对象。
错误用法 1:
1 | // A.h |
如果在 A 中这么声明,在 B 中访问 GTestPtr 则会有符号未定义的链接错误。
错误用法 2:
1 | // A.h |
则每个包含 A.h 的翻译单元都包含了一个 GTestPtr
的符号实例,并不是全局唯一的。