Reputation: 461
I have the following scenario in MSVC2017:
bool foo()
foo()
via GetProcAddress
In the static library, foo()
is defined as follows:
extern "C" __declspec(dllexport) bool foo()
{
return true;
}
Now, because foo()
is not used by the dynamic link library, its symbol is not exported and thus not findable when the application used GetProcAddress
.
I have tried:
#pragma comment(linker, "/include:foo")
and:
#pragma comment(linker, "/export:foo")
I can see the exported foo()
using Dependency Walker if I move the definition to the dynamic link library (not a viable solution) but I cannot seem to get the symbol exported when I keep the definition in the static library with the above linker switches. I presume this is because the symbol is still not used, and thus still not exported regardless?
I would like a solution for both MSVC on Windows and Clang on Linux. Thanks!
Upvotes: 8
Views: 9786
Reputation: 41147
You're doing something wrong (or at least not as you describe in the question). Of course, what you posted in your answer works as well, but that's only a workaround, as the "regular" way should work.
Here's a small example.
lib00.cpp:
extern "C" __declspec(dllexport) bool foo()
{
return true;
}
dll00.cpp:
extern "C" __declspec(dllexport) bool bar()
{
return false;
}
Output:
[cfati@CFATI-5510-0:e:\Work\Dev\StackOverflow\q056330888]> sopr.bat ### Set shorter prompt to better fit when pasted in StackOverflow (or other) pages ### [prompt]> "c:\Install\pc032\Microsoft\VisualStudioCommunity\2017\VC\Auxiliary\Build\vcvarsall.bat" x64 > nul [prompt]> [prompt]> dir /b dll00.cpp lib00.cpp [prompt]> [prompt]> cl /c /nologo /D_LIB /DSTATIC /Folib00.obj lib00.cpp lib00.cpp [prompt]> [prompt]> lib /nologo /out:lib00.lib lib00.obj [prompt]> [prompt]> cl /c /nologo /DDLL /Fodll00.obj dll00.cpp dll00.cpp [prompt]> [prompt]> link /nologo /dll /out:dll00.dll dll00.obj lib00.lib Creating library dll00.lib and object dll00.exp [prompt]> [prompt]> dir /b dll00.cpp dll00.dll dll00.exp dll00.lib dll00.obj lib00.cpp lib00.lib lib00.obj [prompt]> [prompt]> dumpbin /nologo /exports dll00.dll Dump of file dll00.dll File Type: DLL Section contains the following exports for dll00.dll 00000000 characteristics FFFFFFFF time date stamp 0.00 version 1 ordinal base 1 number of functions 1 number of names ordinal hint RVA name 1 0 00001000 bar Summary 2000 .data 1000 .gehcont 1000 .gxfg 1000 .pdata 9000 .rdata 1000 .reloc E000 .text [prompt]> [prompt]> :: ----- Re-link dll, instructing it to include foo ----- [prompt]> [prompt]> link /nologo /dll /include:foo /out:dll00.dll dll00.obj lib00.lib Creating library dll00.lib and object dll00.exp [prompt]> [prompt]> dumpbin /nologo /exports dll00.dll Dump of file dll00.dll File Type: DLL Section contains the following exports for dll00.dll 00000000 characteristics FFFFFFFF time date stamp 0.00 version 1 ordinal base 2 number of functions 2 number of names ordinal hint RVA name 1 0 00001000 bar 2 1 00001010 foo Summary 2000 .data 1000 .gehcont 1000 .gxfg 1000 .pdata 9000 .rdata 1000 .reloc E000 .text
Notes:
As noticed, I used command line, but the same commands (more arguments) are invoked by VStudio IDE
Adding /include:foo (2nd Link command) exports foo as well (as seen in the next DumpBin output):
Specifying this option is identical to adding #pragma comment(linker, "/include:foo")
(in dll.cpp - or any file that is being directly passed to the linker)
/export:foo is not necessary, as the function is already exported by __declspec(dllexport)
I didn't go through the end (the application), since foo being present in DumpBin output is enough (it's also visible from Dependency Walker)
You might not be doing things wrong after all. But bear in mind that it's not scalable (if you have hundreds of such symbols). Looking at [MS.Learn]: Overview of LIB, it provides the same options as Link in regards to exporting stuff. But they seem to be ignored.
When building a .lib, maybe one would like to specify all the symbols to be included at link time (either via option or via #pragma comment), when building the .lib, and not when linking. Apparently, they are ignored (I've tested it), unless stuff is specified in .obj files (or options) passed directly to the linker. This is because [MS.Learn]: Building an Import Library and Export File (emphasis is mine):
Note that if you create your import library in a preliminary step, before creating your .dll, you must pass the same set of object files when building the .dll, as you passed when building the import library.
So there's a difference when passing an .obj file to the linker:
Directly (command line): it is included in the .dll (or .exe)
Indirectly (part of a .lib passed via command line): it is not included in the .dll, it is only searched for symbols
This totally makes sense as a lib is just a collection (archive) of .obj files (on Nix the archiver is Ar (formerly known as RanLib)). An example:
Output:
[prompt]> del *.obj *.exp *.lib *.dll [prompt]> [prompt]> dir /b dll00.cpp lib00.cpp [prompt]> [prompt]> cl /c /nologo /D_LIB /DSTATIC /Folib00.obj lib00.cpp lib00.cpp [prompt]> [prompt]> cl /c /nologo /DDLL /Fodll00.obj dll00.cpp dll00.cpp [prompt]> [prompt]> :: Pass lib00.obj directly to linker [prompt]> [prompt]> link /nologo /dll /out:dll00.dll dll00.obj lib00.obj Creating library dll00.lib and object dll00.exp [prompt]> [prompt]> lib /nologo /out:lib00.lib lib00.obj [prompt]> [prompt]> dir Volume in drive E is SSD0-WORK Volume Serial Number is AE9E-72AC Directory of e:\Work\Dev\StackOverflow\q056330888 23/01/30 09:15 <DIR> . 23/01/30 09:15 <DIR> .. 23/01/30 09:12 72 dll00.cpp 23/01/30 09:14 106,496 dll00.dll 23/01/30 09:14 733 dll00.exp 23/01/30 09:14 1,790 dll00.lib 23/01/30 09:14 604 dll00.obj 23/01/30 09:07 71 lib00.cpp 23/01/30 09:15 822 lib00.lib 23/01/30 09:13 604 lib00.obj 8 File(s) 111,192 bytes 2 Dir(s) 51,727,843,328 bytes free [prompt]> [prompt]> dumpbin /nologo /exports dll00.dll Dump of file dll00.dll File Type: DLL Section contains the following exports for dll00.dll 00000000 characteristics FFFFFFFF time date stamp 0.00 version 1 ordinal base 2 number of functions 2 number of names ordinal hint RVA name 1 0 00001000 bar 2 1 00001010 foo Summary 2000 .data 1000 .gehcont 1000 .gxfg 1000 .pdata 9000 .rdata 1000 .reloc E000 .text [prompt]> [prompt]> :: Now do the same with the one from inside the .lib [prompt]> [prompt]> del lib00.obj [prompt]> [prompt]> lib lib00.lib /extract:lib00.obj Microsoft (R) Library Manager Version 14.16.27048.0 Copyright (C) Microsoft Corporation. All rights reserved. [prompt]> [prompt]> dir lib00.obj Volume in drive E is SSD0-WORK Volume Serial Number is AE9E-72AC Directory of e:\Work\Dev\StackOverflow\q056330888 23/01/30 09:16 604 lib00.obj 1 File(s) 604 bytes 0 Dir(s) 51,727,839,232 bytes free [prompt]> [prompt]> link /nologo /dll /out:dll00.dll dll00.obj lib00.obj Creating library dll00.lib and object dll00.exp [prompt]> [prompt]> dumpbin /nologo /exports dll00.dll Dump of file dll00.dll File Type: DLL Section contains the following exports for dll00.dll 00000000 characteristics FFFFFFFF time date stamp 0.00 version 1 ordinal base 2 number of functions 2 number of names ordinal hint RVA name 1 0 00001000 bar 2 1 00001010 foo Summary 2000 .data 1000 .gehcont 1000 .gxfg 1000 .pdata 9000 .rdata 1000 .reloc E000 .text
I played briefly with [MS.Learn]: Linker options (/INCLUDE and /EXPORT). Added a bit of complexity into the mix.
lib01.cpp:
//#pragma comment(linker, "/include:foo1") // Apparently, has no effect in an .obj contained by a .lib
#pragma comment(linker, "/export:foo01")
#if defined(__cplusplus)
extern "C" {
#endif
__declspec(dllexport) bool foo00()
{
return true;
}
bool foo01()
{
return true;
}
bool foo02()
{
return true;
}
#if defined(__cplusplus)
}
#endif
lib10.cpp:
#pragma comment(linker, "/export:foo11")
#if defined(__cplusplus)
extern "C" {
#endif
__declspec(dllexport) bool foo10()
{
return true;
}
bool foo11()
{
return true;
}
bool foo12()
{
return true;
}
#if defined(__cplusplus)
}
#endif
Output:
[prompt]> del *.obj *.exp *.lib *.dll [prompt]> [prompt]> cl /c /nologo /D_LIB /DSTATIC /Folib01.obj lib01.cpp lib01.cpp [prompt]> [prompt]> cl /c /nologo /D_LIB /DSTATIC /Folib10.obj lib10.cpp lib10.cpp [prompt]> [prompt]> lib /nologo /out:lib0110.lib lib01.obj lib10.obj [prompt]> [prompt]> cl /c /nologo /DDLL /Fodll00.obj dll00.cpp dll00.cpp [prompt]> [prompt]> :: ----- "Regular" behavior ----- [prompt]> [prompt]> link /nologo /dll /out:dll00.dll dll00.obj lib0110.lib Creating library dll00.lib and object dll00.exp [prompt]> [prompt]> dumpbin /nologo /exports dll00.dll Dump of file dll00.dll File Type: DLL Section contains the following exports for dll00.dll 00000000 characteristics FFFFFFFF time date stamp 0.00 version 1 ordinal base 1 number of functions 1 number of names ordinal hint RVA name 1 0 00001000 bar Summary 2000 .data 1000 .gehcont 1000 .gxfg 1000 .pdata 9000 .rdata 1000 .reloc E000 .text [prompt]> [prompt]> :: ----- /export a symbol ----- [prompt]> [prompt]> link /nologo /dll /out:dll00_export.dll /export:foo02 dll00.obj lib0110.lib Creating library dll00_export.lib and object dll00_export.exp [prompt]> [prompt]> dumpbin /nologo /exports dll00_export.dll Dump of file dll00_export.dll File Type: DLL Section contains the following exports for dll00_export.dll 00000000 characteristics FFFFFFFF time date stamp 0.00 version 1 ordinal base 2 number of functions 2 number of names ordinal hint RVA name 1 0 00001000 bar 2 1 0000E1A0 foo02 Summary 2000 .data 1000 .gehcont 1000 .gxfg 1000 .pdata 9000 .rdata 1000 .reloc E000 .text [prompt]> [prompt]> :: ----- /include a symbol ----- [prompt]> [prompt]> link /nologo /dll /out:dll00_include.dll /include:foo02 dll00.obj lib0110.lib Creating library dll00_include.lib and object dll00_include.exp [prompt]> [prompt]> dumpbin /nologo /exports dll00_include.dll Dump of file dll00_include.dll File Type: DLL Section contains the following exports for dll00_include.dll 00000000 characteristics FFFFFFFF time date stamp 0.00 version 1 ordinal base 3 number of functions 3 number of names ordinal hint RVA name 1 0 00001000 bar 2 1 00001010 foo00 3 2 00001020 foo01 Summary 2000 .data 1000 .gehcont 1000 .gxfg 1000 .pdata 9000 .rdata 1000 .reloc E000 .text
As seen (just like in the Docs):
/EXPORT: searches (in the .lib) for the symbol (foo02) and simply exports it
/INCLUDE: searches (in the .lib) for the symbol (foo02), gets the containing object file (lib0.obj), and includes it in the .dll:
Took a deeper look and found [MS.Learn]: /WHOLEARCHIVE (Include All Library Object Files) which states (emphasis is mine):
The /WHOLEARCHIVE option forces the linker to include every object file from either a specified static library, or if no library is specified, from all static libraries specified to the LINK command.
...
The /WHOLEARCHIVE option was introduced in Visual Studio 2015 Update 2.
Output:
[prompt]> :: ----- YAY ----- /wholearchive ----- YAY ----- [prompt]> [prompt]> link /nologo /dll /out:dll00_wholearchive.dll /wholearchive:lib0110.lib dll00.obj lib0110.lib Creating library dll00_wholearchive.lib and object dll00_wholearchive.exp [prompt]> [prompt]> dumpbin /nologo /exports dll00_wholearchive.dll Dump of file dll00_wholearchive.dll File Type: DLL Section contains the following exports for dll00_wholearchive.dll 00000000 characteristics FFFFFFFF time date stamp 0.00 version 1 ordinal base 5 number of functions 5 number of names ordinal hint RVA name 1 0 00001000 bar 2 1 00001040 foo00 3 2 00001050 foo01 4 3 00001010 foo10 5 4 00001020 foo11 Summary 2000 .data 1000 .gehcont 1000 .gxfg 1000 .pdata 9000 .rdata 1000 .reloc E000 .text
Upvotes: 5
Reputation: 461
My solution in the end was to make a dummy function that called foo()
to force all symbols in that compilation unit to be exported.
Upvotes: 2