Why can’t I __declspec(dllexport) a function from a static library?

A customer was having trouble exporting a function with the __decl­spec(dll­export) declaration specified, but found that if the function was in a static library, no function was exported. Why is that?

Let's go back to the classical model for linking. Code is pulled from a LIB file only if the linker encounters a reference to it. If the linker encounters no reference to any symbols offered by an OBJ file in that LIB, then that OBJ file is not included in the final image. (Remember, we're talking about OBJ files inside LIBs; explicitly-provided OBJ files are always included in the image, under the classical model.)

Now consider a LIB which contains an OBJ file that has a function marked __decl­spec(dll­export). Suppose that no symbol offered by that OBJ file is ever required by the final image. That means that the OBJ file is never added to the image. And that means that the linker does not see the __decl­spec(dll­export) qualifier on the function inside the OBJ file (since the OBJ file was never used), so the function doesn't get exported.

Let's look at it another way: __decl­spec(dll­export) does not influence the linking process. All it does is add a little sticker to the function that says, "For export." When the linker adds functions to an image, it makes note of the sticker and adds it to the list of functions that it needs to export. But if the linker never sees the function, then it never sees the sticker.

In order to export a function from a static library, you need to force a reference to the function from the image. One way is to add an OBJ to the image that contains a dummy function that calls the function you want to export. That dummy function will trigger the resolution of the symbol from the static library, at which point the linker will see the sticker.

Another way is to use the /INCLUDE directive to create an artificial reference to the function from the command line, but this gets you into the fragile world of having to know the various name decoration schemes for different architectures.

The best solution is to use an explicit DEF file, since that also gives you a chance to do other things like remove the decorations from the function (so that it can be Get­Proc­Addressed).

Exercise: "But sometimes the __decl­spec(dll­export) works from a static library, even though I did none of those special things." Explain.

Comments (21)
  1. Henke37 says:

    First possible cause: Someone else did the special thing.

    Second possible cause: He just used any symbol in the library contained obj file normally.

  2. MC says:

    I encountered a similar situation on unix and had to create a dummy function that called all the functions that might get called when dynamic libraries (shared objects) get loaded.   Then I had to call put in a call to the dummy function in the main code in a way that it didn't get optimised out,  something like if day of the month =32 then call dummy function.

  3. Rick C says:

    It sure would be nice if__decl­spec(dll­export) were extended so we didn't have to use a module definition file.

  4. Joshua says:

    I take it the Windows linker doesn't have an equivalent of -Wl,–whole-archive.

  5. James Curran says:

    My guess for the exercise is that something else has a reference to it, probably some other module in the library.

  6. Barry Kelly says:

    The confusion could be avoided by considering dllexport symbols encountered during object linking as roots (the workings of a smart linker is a lot like the tracing phase of a simple garbage collector, up to and including allocating addresses).

    But that would create the opposite problem, where people who link in random static libraries for their static utilities, and wonder where all these dll exports came from.

    [That is exactly what happens. The problem is that the dllexport symbols is never encountered if its containing OBJ is never referenced. Just like GC. -Raymond]
  7. jonwil says:

    I wish there was an easy way to get a list of all the obj files in a lib file AND all the ones that actually got linked into the exe.

    I have been bitten before with the whole "obj files that aren't referenced but which contain important static data initializers don't get pulled in" thing (which is another variant of this dllexport issue)

    [You can sort of extract this information from the (voluminous) output of LINK /VERBOSE. -Raymond]
  8. Azarien says:

    If you reference any other symbol in the obj file, the whole obj file will be included in the image, so the dllexport-ed function will actually get exported.

  9. Neil says:

    Firefox used to have a file dlldeps-xul.cpp for this very purpose, but they finally got around to getting rid of it last November. (They'd actually stopped using intermediate static libs some time ago in favour of response files for other reasons.)

  10. stickboy says:

    " __decl­spec(dll­export) does not influence the linking process."

    But *shouldn't* it?

    "The best solution is to use an explicit DEF file…"

    Alas, that doesn't work well for C++ classes. =(

    [One does not change the ground rules for linking lightly. A lot of stuff depends on those rules. For example, it makes "cleaning up unused LIBs" much more difficult. -Raymond]
  11. Joker_vD says:

    @stickboy: You can't export C++ classes from DLL. Well, you can, but then any application that wants to use this DLL must use exactly the same version of runtime library (Debug/Release distinctions also matter).

    Maybe not, but after a day of access violations caused with the difference in implementations of std::vector and std::string in main app and the DLL, we decided to rebuild that library from source as a static library — it took just a quarter of a day!

  12. Crescens2k says:


    Well, that is also possible to do with regular functions if you don't think about compatibility. That is where the problem usually lies. Because the versions of the standard C++ library is tied to the compiler version, it is possible to see heavy changes between releases. On the other hand, if you were to have a well versioned class with version compatibility in mind then it would be less of a problem.

    The biggest of the versioning issues with the standard C++ library is down to the mix of protected/private members and public members. This could lead to inlined members calling a private/protected member that may not exist in a different version and cause problems. There is one major class based version independent technology in Windows, and that is the dreaded COM. If you look at some of those implementations then you notice some of the reasons why it is version independent.

  13. voo says:

    @Joker_vD: At least under Linux that problem has been pretty much solved with a standard ABI and backwards compatible standard library versioning, as I understand it (someone correct me if I'm wrong). So it's not really an insurmountable problem even in C++..

  14. Joker_vD says:

    @Crescens2k: Well, I wish you were one of the Connector/C++ developers, because that thing has some severe memory problems with std::string and std::vector: one workaround is to always pass c_str() to it. Now, a fresh developer comes in, sees all this, sees that there is overload with std::string instead of const char*, removes all c_str()… and the application now crashes. "Hilarity ensues".

    @voo: Nothing is an insurmountable problem unless it's undecidable or has exponential complexity. However, good luck with trying to make Microsoft, Borland, Intel, Metrowerks, and those who maintain GCC/Clang/etc. ports to use one standard ABI. First of all, you'd have to invent that ABI, right?

  15. David M says:

    @Joker_vD: That is already the case. ICC, GCC and clang all use the following ABI [mentorembedded.github.io/…/abi.html]

    Note that while it is called the "Itanium ABI", it is used for x86-32, x86-64, ARM, POWER, etc.

    [But that ABI assumes that all components agree on the contents of each class (and dictates how the memory layout for the agreed-upon class shall be performed). It doesn't dictate, for example, what a std::string should look like. So you still have a problem if two modules are linked together with conflicting definitions of std::string. -Raymond]
  16. Joker_vD says:

    Yes, indeed the problem with a situation when two modules are linked together with conflicting definitions of std::string is because each module has its own set of std::string's methods (including constructor), that have completely different ideas about how a string's intestines are laid out.

    But I think it is possible to solve this problem by using one unique module with std::string's methods, that would return a pointer to an opaque structure, which could be passed as a "this" parameter to those methods… wait a second.

    Why, oh why does it always turn into COM? It's like when you steal all the parts from a sewing machines factory, and then you start to assemble them, but no matter how you put them together, you always get a machinegun.

  17. Joshua says:

    > But I think it is possible to solve this problem by using one unique module with std::string's methods, that would return a pointer to an opaque structure, which could be passed as a "this" parameter to those methods… wait a second.

    Because that isn't the problem with COM.

    The problem with COM is imposing a particular threading model, the tie-in to the event pump, the registration of classes & interfaces, and so forth. There can exist an ABI that does the first part w/o the second. For an obvious stupid example, everything invokes as call-by-name (like reflection does now). Better can be done with an hour's thought.

    [There are many parts to COM. You can use COM without registering classes. (Just use custom factories.) And if you never marshal, then apartments and interface registration don't enter the picture either. It turns out most COM code doesn't involve marshaling. The nasty parts of COM come from the marshaling. -Raymond]
  18. Crescens2k says:


    Of course it can, but also remember that COM was designed for language independence. This would be tough to compare it to a standard ABI for C++.

    The problem with an ABI like this is getting it some traction.

    I also disagree with those problems. If you control things correctly, you can get away without registering classes and setting to a particular threading model. You don't require anything to use the techniques of COM, these things are only required when you start using the runtime services.

  19. Joshua says:

    Try this one on for size. If you remove all the stuff required for registration and marshalling, it then becomes reasonably obvious to consider that one may now add methods to interfaces without breaking anything. Try to do it with interface registration not designed in advance to support it.

    What was wanted: fix the brittle base class problem with a resilient ABI.

    What was obtained: COM.

    What should have been done: fixups for vtable offsets.

    [Um, if you add a method to an interface, how do you know whether the object supports the old interface or the new one? Presumably you still need the "Interfaces are immutable" rule. Not sure what you mean by fixups for vtable offsets, but it's largely beside the point because no compiler supports them. -Raymond]
  20. Joshua says:

    [Um, if you add a method to an interface, how do you know whether the object supports the old interface or the new one?]

    Good question actually. We're talking about a restricted subset of the problem where this doesn't happen (Upgrading the standard library).

    The stated problem can be solved by load-time vtable construction (code for this would have to go in the generated portion of DLLMain for the obvious reason) and checking if the function is null before calling it. This of course assumes you know what to do about receiving an old version of the interface.

    [Not sure what you mean by fixups for vtable offsets]

    The offset of the function address in the vtable is not known until the loader loads the EXE and DLLs into memory.

    Depending on how you build your vtables, one more fixup for object size may be required (whether vtables contain the object size is an implementation detail).

    Copy constructor looks like Object(const Object &src) { src.CloneTo(this); } so that copy constructor can be virtual. Other constructors get the same treatment.

    There, cross-DLL all-vfunction brittle base class solved. No registration to break.

    Total elapsed time: 55 minutes.

    [You can sketch it out in 55 minutes if you ignore the design constraints. Fixups for vtable offsets would mean that (1) all vtable offsets must be encoded as 32-bit values, which prevents them from being compile-time constants that can be encoded optimally. This is especially painful on architectures which cannot encode 32-bit immediates. They would have to burn a register. (2) Changing all compilers to generate code differently. (3) Massively increasing the number of fixups generated per module. This is a problem for some architectures like MIPS and ia64 which forbid fixups in the code segment. (4) Incurring copy-on-write penalties at load time. One of the design constraints of COM was that it be implementable with the existing C/C++ toolchain. -Raymond]
  21. Crescens2k says:

    "The stated problem can be solved by load-time vtable construction (code for this would have to go in the generated portion of DLLMain for the obvious reason) and checking if the function is null before calling it."

    "The offset of the function address in the vtable is not known until the loader loads the EXE and DLLs into memory."

    Aren't those cases where immutable interfaces would greatly simplify things?

    I don't think I need to go deeply into the first. But for the second, while you may not know the absolute address, you know the offset. If you look at libraries like ATL, they build the QI around this concept making a tabular form of the interfaces it supports using static_cst. The immutable interfaces rule will guarantee not only the interface is going to contain the same member functions, but also they are going to be in the same order.

    It will also force a various compatibility things into the class itself, since the definition of the new class would end up being sufficiently different from the old to be able to check at compile time if you upgrade an interface.

Comments are closed.