Exporting symbols in static library that is linked to dynamic library

I have the following scenario in MSVC2017:

  1. A static library with the function bool foo()
  2. A dynamic link library that links to the static library above
  3. An application that loads the dynamic link library using explicit run-time linking and calls 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

Answers (2)

CristiFati
CristiFati

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)



Update #0

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


Update #1

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:

    • As a consequence, the other 2 symbols (foo00, foo01) marked for export in the .obj file are exported

Conclusion

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

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

Related Questions