在不使用模块定义文件的情况下导入本地定义的符号






4.75/5 (4投票s)
引言
起初,我使用模块定义文件来标识我希望链接器标记为从我的 Win32 动态链接库导出的符号。但最近,我放弃了这种做法,因为它会隐藏由 DumbBin.exe
生成的列表中的符号名称。我生成这些列表作为我的生产代码库技术文档的一部分,并经常参考它们来识别依赖关系和解决链接问题。
今天,我被提醒模块定义文件可以使导入本地定义的符号变得透明。然而,在我向这个导出超过 *六十* 个函数的库添加了 .DEF
文件之前,我试图寻找另一种方法来导入我在同一个库中导出的另一个例程中需要使用的两个本地定义的符号。
背景
以下是理解本文剩余部分所需的关键概念。
- 本地定义的符号是在 DLL 中导出的函数,并且在同一库中定义的另一个函数被(导入)调用。
- 使用模块定义 (
.DEF
) 文件的替代方法是在定义函数时指定__declspec(dllexport)
,并在声明(导入)函数时指定__declspec(dllimport)
。
第二种方法通常是通过在声明其导出函数的头文件中编写以下内容来完成的。
#if defined ( _BUILDING_WWKERNELLIBWRAPPER )
#define LIBSPEC_WWKERNELLIBWRAPPER_API __declspec(dllexport)
#else
#define LIBSPEC_WWKERNELLIBWRAPPER_API __declspec(dllimport)
#endif /* #if defined ( _BUILDING_WWKERNELLIBWRAPPER ) */
任何定义导出函数的源文件中的第一个非空白行如下。
#define _BUILDING_WWKERNELLIBWRAPPER
上述定义的 C++ 宏展开效果,它至少必须在将声明例程的头文件包含到编译流之前的代码中,当函数原型中出现 LIBSPEC_WWKERNELLIBWRAPPER_API
时,它会被替换为 __declspec(dllexport)
。
反之,由于在正常使用中 _BUILDING_WWKERNELLIBWRAPPER 是未定义的,当该函数在任何导入它的例程中声明时,LIBSPEC_WWKERNELLIBWRAPPER_API
变为 __declspec(dllimport)
,链接器会解析外部定义的符号。
除非你的库导入它自己的符号,否则这很有效。
打破规则
解决这个问题需要暂时暂停宏定义,该宏定义用于控制函数的装饰,指示它们是正在导出还是导入。就目前情况而言,该符号是 _BUILDING_CREATEGUIDSTRING_
。我所谓的暂停,是指当函数在需要导入它们的例程中声明时,必须暂时覆盖该定义,然后在剩余的声明中恢复它。
通过将宏名称作为带引号的字符串馈送给 #pragma push_macro
和 #pragma pop_macro
来实现暂停,这是一对非标准的预处理器指令,幸运的是,它们在最常用的两个 C++ 编译器 Microsoft Visual C++ 和 GCC 中都得到了实现。
需要调用(导入)库中另一个例程的例程定义了一个名称独特的预处理器符号,如下所示。
#define _BUILDING_CREATEGUIDSTRING_
碰巧我需要导入的例程声明在一个子公司头文件中,该文件嵌套在主库头文件中。因此,暂停和恢复被包装在主库头文件中的嵌套 #include
周围,如下所示。
#if defined (_BUILDING_CREATEGUIDSTRING_ )
#pragma push_macro ( "LIBSPEC_P6CSTRINGLIB1_API" )
#undef LIBSPEC_P6CSTRINGLIB1_API
#define LIBSPEC_P6CSTRINGLIB1_API __declspec(dllimport)
#endif /* #if defined (_BUILDING_CREATEGUIDSTRING_ ) */
#include <BinToHex_WW.H>
#if defined (_BUILDING_CREATEGUIDSTRING_ )
#pragma pop_macro ( "LIBSPEC_P6CSTRINGLIB1_API" )
#endif /* #if defined (_BUILDING_CREATEGUIDSTRING_ ) */
如果声明在一个整体头文件中,暂停和恢复将以相同的方式处理,但会围绕声明本身。
链接编辑器会发出两个警告,就像使用模块定义文件一样。
1>CreateGUIDStringA.obj : warning LNK4217: locally defined symbol _BinToHexA_WW imported in function _CreateGUIDStringA@4
1>CreateGUIDStringW.obj : warning LNK4217: locally defined symbol _BinToHexW_WW imported in function _CreateGUIDStringW@4
由于我期望这些警告,它们提醒我无害的循环依赖,所以我从未尝试抑制它们。更重要的是,我避免了重新创建模块定义文件,这是一个繁琐且容易出错的任务,它可能会迫使我重新链接数十个导入此库中的一个或多个其他符号的模块。相反,我在一个库头文件中做了一些小的改动,重新构建了库,然后继续下一个任务。
关注点
这绝不是我第一次遇到 #pragma
指令,也不是我第一次遇到操纵堆栈的预处理器指令。例如,#
pragma push
和 #pragma pop
是我老朋友了,不时使用它们来抑制可以安全忽略的警告。
发现 #pragma push_macro
和 #pragma pop_macro
是一个令人惊喜的发现,它使我能够快速解决一个原本很棘手的问题,而无需放弃 __declspec(dllexport)
和 __declspec(dllimport)
来控制入口点的导出和导入。
最后,感谢 Peter 在六年前在 Stack Overflow 上对“Can I redefine a C++ macro then define it back?” 的回答,网址是:http://stackoverflow.com/questions/1793800/can-i-redefine-a-c-macro-then-define-it-back。
历史
2016年1月11日,星期一 - 首次发布