35

I have a DLL which contains a class with static members. I use __declspec(dllexport) in order to make use of this class's methods. But when I link it to another project and try to compile it, I get "unresolved external symbol" errors for the static data.

e.g. In DLL, Test.h

class __declspec(dllexport) Test{
protected:
    static int d;
public:
    static void m(){int x = a;}
}

In DLL, Test.cpp

#include "Test.h"

int Test::d;

In the application which uses Test, I call m().

I also tried using __declspec(dllexport) for each method separately but I still get the same link errors for the static members.

If I check the DLL (the .lib) using dumpbin, I could see that the symbols have been exported.

For instance, the app gives the following error at link time:

1>Main.obj : error LNK2001: unresolved external symbol "protected: static int CalcEngine::i_MatrixRow" (?i_MatrixRow@CalcEngine@@1HA)

But the dumpbin of the .lib contains:

Version      : 0
  Machine      : 14C (x86)
  TimeDateStamp: 4BA3611A Fri Mar 19 17:03:46 2010
  SizeOfData   : 0000002C
  DLL name     : CalcEngine.dll
  Symbol name  : ?i_MatrixRow@CalcEngine@@1HA (protected: static int CalcEngine::i_MatrixRow)
  Type         : data
  Name type    : name
  Hint         : 31
  Name         : ?i_MatrixRow@CalcEngine@@1HA

I can't figure out how to solve this. What am I doing wrong? How can I get over these errors?

P.S. The code was originally developed for Linux and the .so/binary combination works without a problem

EDIT: In the given case, the static variables are not directly referred by the application but the method is inlined since it's in the header. I was able to resolve the link errors by moving the methods to the .cpp file.

Gayan
  • 1,647
  • 7
  • 26
  • 35
  • My first reaction would be to blame name mangling, however from examining the dumpbin, it seems that the mangled names match. Just to be sure, are you compiling both with the same compiler version? – Ramon Zarazua B. Mar 19 '10 at 18:55
  • Yes. I'm not sure about the compiler version but I use Visual C++ 2008 Express to compile the DLL and the application – Gayan Mar 19 '10 at 18:57

4 Answers4

19

In this thread at cprogramming.com it is suggested that a static variable is local to the dll and not exported.

Summary of discussion below

The static member is not accessed directly by code in the calling application, only through member functions of the class in the dll. However there are several inline functions accessing the static member. Those functions will be inline expanded into the calling applications code makeing the calling application access the static member directly. That will violate the finding referenced above that static variables are local to the dll and cannot be referenced from the calling application.

Community
  • 1
  • 1
Anders Abel
  • 64,109
  • 15
  • 143
  • 213
  • 1
    Yes I came across that (I've been trying to solve this for half a day now). But the given solutions do not apply to my case because I don't use the static members defined in the dll, in my application. They are hidden and are only used by local methods. – Gayan Mar 19 '10 at 19:13
  • 1
    Are there any inline methods accessing the static member? In that case the inline expanded code will be put in your application, directly accessing the static member. – Anders Abel Mar 19 '10 at 19:15
  • Yes!! I've written most of the methods in the header (bad practice.. I know) so think that's the case here – Gayan Mar 19 '10 at 19:19
  • 2
    Try moving all method bodies that uses the static member into .cpp-files which are compiled into the dll and cannot be inlined in the application, that will probably make it. – Anders Abel Mar 19 '10 at 19:21
  • 1
    After a lot of work I was able to get the executable to compile. I had to move a lot of methods from .h to .cpp files. This doesn't explain why the exported symbols were reported as being unresolved. – Gayan Mar 19 '10 at 20:10
  • 1
    I'm a bit hesitant to set this as the answer because in the linked answer, it's shown that symbols of static members "can" be exported. Two instances are created for the app and the dll but the symbol is not given as being "unresolved", which is what happened in my case. Please correct the post and I'll accept it. – Gayan Mar 19 '10 at 20:12
  • Thanks, Anders. Your explanation really helped me with TinyXml library. They created many inline member functions that address static members. So MS VC compiler refused to link against this DLL. – Sergey Gatich Oct 30 '16 at 22:43
  • I can see how you can fix the problem this way but it is incorrect. One can export static data. I will answer below. – Jack D Menendez Mar 12 '21 at 16:50
15

My guess is that the class which uses the DLL should see dllimport instead of dllexport in the header. If I am correct, this can typically be achieved by defining a preprocessor macro like:

#ifdef EXPORTING
#define DECLSPEC __declspec(dllexport)
#else
#define DECLSPEC __declspec(dllimport)
#endif

and then use it in the class declaration:

class DECLSPEC Test{
protected:
    static int d;
public:
    static void m(){}
}

So that in Test.cpp (or wherever it makes sense in your DLL project) you can specify that you are exporting so that it will be exported with dllexport:

#define EXPORTING
#include "Test.h"

int Test::d;

while the other project, which does not define EXPORTING, will see dllimport.

Does it make sense?

Ghislain Fourny
  • 6,423
  • 1
  • 27
  • 33
  • I thought dllimport was optional. I read somewhere that it was a mechanism to assist the linker and compiler to perform better optimization, hence increasing performance. Correct me if I'm wrong since my experience in this area is limited – Gayan Mar 19 '10 at 19:21
  • 3
    Yes, I also read that dllimport is optional. My understanding is that you can omit it, however dllexport should not be there for the application importing the dll: otherwise the compiler thinks it is exported by the application, looks for the corresponding definition, which does not exist, and this produces the error message. – Ghislain Fourny Mar 19 '10 at 20:10
  • I don't think the dllimport is optional if that exported member is going to be used by a higher level assembly (dll/exe), you'll get a linker error. I just experimented in a project of mine just a minute ago because I too needed to export a static public member which was a string. – jxramos Nov 06 '15 at 20:52
4

With Windows DLLs, there is a specific distinction between __declspec(dllexport) vs __declspec(dllimport), dllexport should be used when compiling the DLL, dllimport should be used when compiling programs that link to this DLL. The standard way of defining this would be with a macro.

The following is the visual studio example:

// The following ifdef block is the standard way of creating macros which make exporting 
// from a DLL simpler. All files within this DLL are compiled with the DLL_EXPORTS
// symbol defined on the command line. this symbol should not be defined on any project
// that uses this DLL. This way any other project whose source files include this file see 
// DLL_API functions as being imported from a DLL, whereas this DLL sees symbols
// defined with this macro as being exported.
#ifdef DLL_EXPORTS
#define DLL_API __declspec(dllexport)
#else
#define DLL_API __declspec(dllimport)
#endif
Craig M. Brandenburg
  • 2,624
  • 2
  • 22
  • 33
Ramon Zarazua B.
  • 6,210
  • 4
  • 18
  • 25
0

Despite the summary, one can export static data from a DLL. However, there is a problem that comes up with the standard macros supplied by the Visual Studio DLL project:

#ifdef DLL_EXPORTS
#define DLL_API __declspec(dllexport)
#else
#define DLL_API __declspec(dllimport)
#endif

If you have multiple DLLs calling code from one DLL to another or between EXE and DLL, there is going to be a problem with this macro because every header is going to be exporting. One needs unique macros that handle __declspec. The safest way to handle this problem is as follows:

#ifdef MYPROJECT_DLL_EXPORTS
     #define MYPROJECT_API __declspec(dllexport)
#else
     #define MYPROJECT_API __declspec(dllimport)
#endif

Then only in the compiler preprocessor options for the DLL project define MYPROJECT_API. In your header code:

struct/class MYPROJECT_API myclass {
   static int counter;
};

And in a .cpp file:

int myclass::counter = 0;
Jack D Menendez
  • 135
  • 1
  • 7