Introduction
In the beginning, I used Module Definition Files to identify symbols that I wanted the linker to mark as exported from my Win32 dynamic link libraries. More recently, however, I have abandoned the practice because it hides the symbol names from the listings generated by DumbBin.exe
, which I generate as part of the technical documentation of my production code libraries, and to which I regularly refer to identify dependencies and solve linkage problems.
Today, I was reminded that module definition files make importing a locally defined symbol transparent. Nevertheless, before I added a .DEF
file to this library, which exports over sixty functions, I sought another way to import the two locally defined symbols that I needed to use in another routine exported by the same library..
Background
Following are the key concepts that must be understood for the remainder of this article to make sense.
- A locally defined symbol is a function that is exported by a DLL, and called (imported) by another function defined in the same library.
- The alternative to using a module definition (
.DEF
) file is specifying __declspec(dllexport)
when a function is defined, and __declspec(dllimport)
when it is declared (imported),
The second feat is usually accomplished by writing somethng like the followng into the header that declares the functions exported by it.
#if defined ( _BUILDING_WWKERNELLIBWRAPPER )
#define LIBSPEC_WWKERNELLIBWRAPPER_API __declspec(dllexport)
#else
#define LIBSPEC_WWKERNELLIBWRAPPER_API __declspec(dllimport)
#endif /* #if defined ( _BUILDING_WWKERNELLIBWRAPPER ) */
The first nonblank line in any source file that defines an exported function is the following.
#define _BUILDING_WWKERNELLIBWRAPPER
The effect of the above definition, which must, at a minimum, precede the #include
directive that brings the header in which the routine is declared into the compilation stream, is that when LIBSPEC_WWKERNELLIBWRAPPER_API
appears in the function prototype, it is replaced by __declspec(dllexport)
.
Coinversely, since _BUILDING_WWKERNELLIBWRAPPER
is undefined in normal usage, when the function is declared in any routine that imports it, LIBSPEC_WWKERNELLIBWRAPPER_API
becomes __declspec(dllimport)
, and the linker resolves the externally defined symbol.
Unless your library improts its own symbols, this works great.
Bending the Rules
Solving this problem requires a temporary suspension of the preprocessor macro that controls the decoration of functions, designating whether they are being exported or imported. For the case at hand, that symbol is _BUILDING_CREATEGUIDSTRING_
. By suspension, I mean that the definition must be temporarily overridden when the functions are declared in the routine that needs to import them, then reinstated for the remaining declarations.
The suspension is achieved by feeding the macro name, as a quoted string, to #pragma push_macro
and #pragma pop_macro
, a pair of nonstandard preprocessor directive that are, thankfully, implemented by the two most widely used C++ compilers, Microsoft Visual C++ and GCC.
The routine that needs to call (import) another routine in the library defines a distinctively named preprocessor symbol, as shown below.
#define _BUILDING_CREATEGUIDSTRING_
It happens that the routines that I need to import are declared in a subsidiary header that is nested in the main library header. Hence, the suspension and reinstatement are wrapped around the nested #include
in the main library header, as shown below.
#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_ ) */
Had the declarations been in a monolithic header, the suspension and reinstatement would be handled in the same way, but would be wrapped around the declarations, themselves.
The linkage editor emits two warnings, as it would if you used a module definition file.
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
Since I expect the warnings, which remind me of the harmless circular dependency, I have never investigated suppressing them. It's more important that I avoided recreating the module definition file, a tedious, error prone task that might have forced me to re-link dozens of modules that import one or more other symbols from this library. Instead, I made a couple of small changes in one library header, rebuilt the library, and progressed to the next task.
Points of Interest
This is hardly my first encounter with #pragma
directives, nor is it my first encounter with preprocessor directives that manipulate a stack. For example, #
pragma push
and #pragma pop
are old friends, used from time to time to suppress warnings that can be safely ignored.
Discovering #pragma push_macro
and #pragma pop_macro
was a pleasant surprise, which enabled me to quickly solve an otherwise difficult problem, without abandoning __declspec(dllexport)
and __declspec(dllimport)
to control exporing and importing of entry points.
In closing, thanks are due to Peter for the answer he gave on Stack Overflow six years ago, in answer to, "Can I redefine a C++ macro then define it back?" at http://stackoverflow.com/questions/1793800/can-i-redefine-a-c-macro-then-define-it-back.
History
Monday, 11 January 2016 - Initial publication