views:

128

answers:

2

I'm building a DLL from a group of static libraries and I'm having a problem where only parts of classes are exported.

What I'm doing is declaring all symbols I want to export with a preprocessor definition like:

#if defined(MYPROJ_BUILD_DLL)
//Build as a DLL
#   define MY_API __declspec(dllexport)
#elif defined(MYPROJ_USE_DLL)
//Use as a DLL
#   define MY_API __declspec(dllimport)
#else
//Build or use as a static lib
#   define MY_API
#endif

For example:

class MY_API Foo{ 
   ...
}

I then build static library with MYPROJ_BUILD_DLL & MYPROJ_USE_DLL undefined causing a static library to be built.

In another build I create a DLL from these static libraries. So I define MYPROJ_BUILD_DLL causing all symbols I want to export to be attributed with __declspec(dllexport) (this is done by including all static library headers in the DLL-project source file).

Edit: Note on unrefenced symbols: Linker options Keep Unreferenced Data (/OPT:NOREF) and Do Not Remove Redundant COMDATs (/OPT:NOICF) is set so that no unreferenced symbols will be removed.

Ok, so now to the problem. When I use this new DLL I get unresolved externals because not all symbols of a class is exported. For example in a class like this:

class MY_API Foo{
public:
   Foo(char const* );
   int bar();
private:
   Foo( char const*, char const* );
};

Only Foo::Foo( char const*, char const*); and int Foo::bar(); is exported. How can that be? I can understand if the entire class was missing, due to e.g. I forgot to include the header in the DLL-build. But it's only partial missing.

Also, say if Foo::Foo( char const*) was not implemented; then the DLL build would have unresolved external errors. But the build is fine (I also double checked for declarations without implementation).

Note: The combined size of the static libraries I'm combining is in the region of 30MB, and the resulting DLL is 1.2MB.

I'm using Visual Studio 9.0 (2008) to build everything. And Depends to check for exported symbols.

Edit: For the ones who wonder why I don't just build a DLL from each of the static libraries: I can't because they cross-reference each other (that's why I need to group them together in one a single DLL). I know, it's horrible I can't really understand the logic behind it.

+2  A: 

Remember that when you link against a static LIB, by default the linker is only pulling in the functions, classes, and data that the client (which in this case is your DLL) actually references.

So what happens is this:

  1. You build your static LIB, and that's fine. This LIB is 100% valid.
  2. You now build your static DLL around the original binary LIB. It pulls in only the stuff that it actually references. It doesn't pull in the entire class definition which is what it needs to do. This DLL, though it builds, is invalid.
  3. A client then uses the DLL, and expects to see a complete binary definition for the exported class, because that's what the client sees in the accompanying header file.
  4. However the class has only been partially imported, and this is why you're getting those link errors.

To fix:

  • If you're able to, don't build your DLL off your static LIB. Build your DLL off the original source code. And build your static LIB off the original source code.
  • Otherwise you can possibly fiddle with linker settings but I strongly recommend option 1 above. Note that OPT:NOREF won't work here as OPT:NOREF has no effect on static LIBs.

What wont't fix it:

  • High-level linker tweaks like OPT:NOREF, anything involving COMDATs, etc. If you want those functions present, you have to make sure they're referenced, either by referencing them, or by telling the linker explicitly, "hey, this Symbol X is referenced".

As a side note:

  • Anytime I build a DLL or LIB, I build both a DLL and a LIB, using exactly the technique you're using. Having a single code base that can generate either a DLL or a LIB by toggling a setting is ideal. But building a DLL off of a (binary) static LIB when you own the source code for both... I'm having a hard time imagining when such a scenario would be necessary.
Swingline Rage
You are right and I forgot to add that info to the question. I added a note about my linker settings that so that unreferenced symbols will not be removed. And the motivation why I don't build a DLL for each of the static libraries; I can't since they cross-reference each other.
mandrake
Just a note (also added to my answer) that OPT:NOREF has no effect on static LIBs anyway. It defeats the entire purpose of having a static LIB. This makes sense. Just not very much.
Swingline Rage
The OPT:NOREF setting applied to the DLL linker setting not the static lib. But it should not matter. If a symbol is marked to export the linker would not optimize it away.
mandrake
+1  A: 

The problem is surely that you use the already-built static .lib in your DLL project. That cannot work, you have to rebuild the .lib so that the functions get the __declspec(dllexport) declarator and will be exported by the linker.

At that point, it just isn't all that useful anymore to create the DLL compatible version of the .lib in the first place. Just create two projects, one that creates the static .lib, another that creates the DLL. Technically it is possible to still use the static .lib in your DLL project but you'll have to export the functions with a .def file. That can be high maintenance if the library has a lot of exports.

Hans Passant
You are right that the other option to __declspec(dllexport) is using a module definition file (.def). But is equally valid to use __declspec to export symbols. Further, it's not possible to 'export' any symbols when building a static lib. A static library can be seen as a collection of object files that the compiler outputted.
mandrake
No. What he's saying is that the linker is not marking those functions as exported. This is partially correct. The linker is not marking any of the class's PRIVATE methods as exported. The public methods are exported because they originated in a static .LIB. Your declspec that you build the *DLL* against has no effect. It happens to work in the case of *public* functions, because those are exported anyway.
Swingline Rage
In other word's, you're in one of those no-mans-land DLL/LIB scenarios that gave rise to the term "DLL Hell." Is there no way to simply make a DLL and be done with it?
Swingline Rage
When building a static library you never mark anything as exported. So how can marking it with __declspec make any difference? And when building a dll, marking a class with __declspec(dllexport) will export the all methods and static members (c.f. http://msdn.microsoft.com/en-us/library/81h27t8c.aspx). Further it's not private but public methods that are unresolved.
mandrake
It makes a difference because the linker will see a reference to the function, necessary to assign a value to the export table slot. The same thing will happen with a program that uses the static library, if no code in the main program ever has a reference to a function in the .lib then it will not be included into the final binary. Don't forget that a .lib is *not* a final binary, it is just a bag of .obj files. See this for yourself by writing a small program that uses just one function from the library. The resulting .exe will be much smaller than the .lib
Hans Passant
Exacly, and that's what I do when building the DLL. All headers of the static libraries are marked with __declspec(dllexport) when I include them in the DLL-project.
mandrake
The declaration is not enough, the compiler must actually compile the definition with __declspec(dllexport) in effect. That generates the metadata that the linker needs to build the export table.
Hans Passant