我正在重构一个 5.5k 行的 C++ DLL,将其拆分为多个较小的 DLL。不幸的是,很多代码是捆绑在一起的,在拆分 GiantDLL 的过程中,我引入了几个循环引用。
更具体地说,
//In emergeOSLib.h:
DLL_EXPORT std::wstring ELGetProcessIDApp(DWORD processID, bool fullName);
//In a couple of functions in emergeOSLib.cpp:
ELMessageBox(GetDesktopWindow(), messageText, (WCHAR*)TEXT("Emerge Desktop"), ELMB_OK|ELMB_ICONERROR|ELMB_MODAL);
//In emergeUtilityLib.h:
DLL_EXPORT int ELMessageBox(HWND hwnd, std::wstring messageText, std::wstring messageTitle, DWORD msgFlags);
//In a function in emergeUtilityLib.cpp:
out << ELGetProcessIDApp(GetCurrentProcessId(), false) << TEXT(": ") << debugText << std::endl;
瞧,一个循环引用。我很确定还有更多,这只是我现在正在处理的一个。
我做了一些研究,发现前向声明似乎是可行的方法:
Resolve header include circular dependencies
Circular Dependency in C++
第二个链接甚至建议前向声明更可取到#include
。
此时我有两个问题。首先,如何前向声明另一个 DLL 中的函数?我的理解是编译器仍然无法找到前向声明的函数(因为它没有将第二个 DLL 编译为第一个 DLL 的一部分)并且会报错。其次,#include
语句或前向声明哪个通常被认为是更好的做法?
最佳答案
前向声明 vs #include
只有助于声明循环。但是你有定义循环。如果您将所有定义链接在一起,多 channel 链接器会解决这个问题……但您没有。
基本上有两种方法可以做到这一点。首先,您可以将 ELGetProcessIDApp
更改为实用程序库中的函数指针。最初它指向实用程序库中的某个 stub 值,当操作系统支持库加载时,它会用特定于操作系统的实现覆盖该函数指针。
或者,您可以在 DLL 实际存在之前使用链接器定义文件构建导入库。这将通过创建一组循环依赖的 DLL 来解决您的链接器问题。它可以工作,但它也可能导致令人惊讶的加载顺序效果并表现出全局初始化顺序失败。
或者,只使用单个 DLL,并将重构限制为拆分源文件。
关于C++ DLL - 打破循环依赖,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/19698889/