1

I have a shared library (.so in Linux, .dll in Windows) that needs to access a static variable contained in whatever executable it is loaded with. This variable happens to be of a class template type, and within a namespace. Despite declaring the variable as "extern" (and on Windows, "__declspec(dllimport)"), VC10 gives an "unresolved external symbol" error for this variable when the DLL is linked. This seems strange to me, as it should indeed not be resolved, and instead left for load time.

The header:

// a header demonstrating MSVC-compatible linkage
#ifdef _MSC_VER

#ifdef I_AM_DLL
#define TO_DLL_LINKAGE __declspec( dllimport )
#else
#define TO_DLL_LINKAGE __declspec( dllexport )
#endif

#else  // not MSVC
#define TO_DLL_LINKAGE
#endif

template<class T>
class TheClass
{
public:
   TheClass(T t) : value_(t) {}

   T value() const
   {
      return value_;
   }
private:
   T value_;
};

typedef TheClass<int> MyClass;

and the DLL:

// a test library (DLL) for linkage experiment
#define I_AM_DLL
#include "theclass.hpp"

#include <iostream>

namespace foo {
extern TO_DLL_LINKAGE MyClass theObject;
}

void bar() {
   int i = foo::theObject.value();
   std::cout << "object value is " << i << std::endl;
}

the error:

error LNK2001: unresolved external symbol "_declspec(dllimport) class TheClass foo::theObject" (_imp_?theObject@foo@@3V?$TheClass@H@@A)

I suppose it goes without saying that this works fine in gcc. I have also reviewed a number of similar StackOverflow questions, but they either recommend what I'm already doing, or don't apply for various reasons (e.g. exporting instead of importing, a class instead of a class instance, etc.).

What additional magic do I need to make MSVC10 happy? Thanks.

Jeff Trull
  • 1,236
  • 11
  • 16
  • Lots of mistakes. You reversed dllexport and dllimport. Remove *extern* from the *definition*. You didn't provide a default constructor. – Hans Passant Nov 27 '12 at 19:04
  • Thanks for your comment Hans. It is my intention to *import* the object into the DLL. Accordingly the extern declaration and the "dllimport". Did I get that backwards? Also is there some reason I need a default constructor? – Jeff Trull Nov 27 '12 at 19:51
  • Erm, pretty unclear who exports it then. You must link the import library of the DLL that actually exports the variable to keep the linker happy. – Hans Passant Nov 27 '12 at 20:17
  • I should clarify that it's at the link time of the DLL, not the whole thing (exe + dll) that I get the "unresolved" error. It's expected that the DLL will have an import symbol or the equivalent in its "interface". Note this works fine in gcc... I just need to know how to correctly do it in VC++. – Jeff Trull Nov 27 '12 at 20:30

1 Answers1

1

It turns out there are two fundamental problems here:

  1. On Windows, symbol resolution is performed at link time, even for shared libraries
  2. dllimport and dllexport are asymmetric - all dllimports must be resolved

When running with gcc under Linux, my program works because the "extern" reference to theObject is resolved at program load time. The shared library (a .so) lists the symbols it is exporting and those it is importing, and the OS's program loader verifies that all imports for both the main program and the shared library are satisfied at the time the program is launched.

By contrast in the Windows/VC++ world, the specific module that is to satisfy the imported symbol must be identified as the shared library is linked - typically through an "import library" or .lib file. This cannot be deferred to program load time. Thus, the link step fails.

In my particular situation I have an executable module that both requires symbols from, and supplies (one) symbol to, a shared library. For static libraries and on gcc/Linux it's not a problem, but for Windows/VC++ this creates a circular dependency. There is a solution, but it requires some extra effort, discussed in this StackOverflow question and also in the Microsoft documentation. The bottom line is to do this will require a more complex link step where an import library is generated from the executable for use during the linking stage of the shared library. Such a library is generated automatically if you have __dllspec(dllexport) storage class for any of your data. The last step is to add this import library to the linking stage of the shared library DLL.

If you are a CMake user, as I am, this process is made much easier by a special target property called ENABLE_EXPORTS, which allows libraries to "link to" the executable.

Community
  • 1
  • 1
Jeff Trull
  • 1,236
  • 11
  • 16