4

Here is the .h:

class Logger
{
private:
    static int mTresholdSeverity;

public:
    static __declspec(dllexport) void log(const char* message);
    static __declspec(dllexport) void logFormat(const char* format, ...);

    static __declspec(dllexport) int getTresholdSeverity() { return mTresholdSeverity; }
    static __declspec(dllexport) void setTresholdSeverity(int tresholdSeverity) { mTresholdSeverity = tresholdSeverity; }
};

And .cpp:

#include "Logger.h"
#include <cstdarg>

int Logger::mTresholdSeverity = 200;

void Logger::log(const char* message)
{
    //...
}

void Logger::logFormat(const char* format, ...)
{
    //...
}

I get this error:
error LNK2001: unresolved external symbol "private: static int TransformationViewer_Utility_Logging::Logger::mTresholdSeverity" (?mTresholdSeverity@Logger@TransformationViewer_Utility_Logging@@0HA) ...

Obviously, mTresholdSeverity is initialized. The error is removed if I comment out getTresholdSeverity() and setTresholdSeverity() or if I move their definition into .cpp file.

Why is there a link error when a static method defined in header file (getTresholdSeverity() or setTresholdSeverity()) uses a static variable (mTresholdSeverity)?

JohnB
  • 13,315
  • 4
  • 38
  • 65
mrzli
  • 16,799
  • 4
  • 38
  • 45
  • Did you forget to include the object file resulting from compilation of the above cpp in the linking? – JohnB Sep 01 '12 at 16:37
  • @JohnB No, I can use the log() method for example if I get it to compile by doing one of the things I mentioned in the penultimate paragraph. Moving the definition to .cpp is acceptable solution for me, I'm just wondering why it doesn't work when definition is in header. – mrzli Sep 01 '12 at 17:09

3 Answers3

4

Here is how it works.

Every DLL (or EXE) or otherwise complete "fully-linked" binary, must have definitions of all referenced names, including static variables, including static data members in C++ classes.

They will be separate across DLLs in the application. That means, this variable value will be different, depending on which DLL you look from. Moving the functions to CPP file will make them do a different thing: they will now see the DLL's copy of the variable and not the EXE's.

To make what you wrote compile, there has to be the definition from the CPP present in all user binaries at one place. That means, the DLL and the users of the DLL (EXEs or DLLs) must have one CPP file with this definition.

This is a very big hassle, because among other things it makes Singleton pattern impossible (having a shared data object for all users within the program) and each copy of a DLL must have its own static state. Such problem exists only on Windows, with DLLs which are dynamic *loaded * libraries. On UNIX systems, there is a different technology known as Shared Objects (SO). They support true dynamic linking, which means, running the linker to resolve external names at runtime.

Pavel Radzivilovsky
  • 18,794
  • 5
  • 57
  • 67
  • I'm a C# guy, so I don't completely understand this stuff (or everything you said), but let me get this straight: if I have those method definitions in header, they don't know where to find initialization/definition for the static variable they use; and if the method definitions are in .cpp file, the variable is initialized at the top of that same file so there is no problem? – mrzli Sep 01 '12 at 17:34
  • This is wrong. Static data members have external linkage, and are defined in exactly one place in a valid program. You don't get a definition in every module that uses one; only in the one that defines it. So there is only one in the entire program, regardless of how many DLLs you have. – Pete Becker Sep 01 '12 at 18:55
  • [true part from Pete's comment] Static data members have external linkage, and are defined in exactly one place in a valid program. You don't get a definition in every module that uses one; [/true part from Pete's comment]. – Pavel Radzivilovsky Sep 01 '12 at 20:24
  • the rest is false. answering your question below. – Pavel Radzivilovsky Sep 01 '12 at 20:24
  • the declaration is in .h file. These are as many as you want in a program.the definition is one in CPP file. One per DLL. Many per program. A function in the class using this private member will proceed according to which CPP file the function is defined, or ultimately, onto which binary DLL the function was linked. – Pavel Radzivilovsky Sep 01 '12 at 20:26
  • Further: an inline function, defined in the .h file, will access different CPP variable if the header is included in a CPP file which is destined to compile inside a DLL. Which is why inline function makes a problem. In C#, they get around this issue by not allowing inline functions and sacrificing a bit of performance optimization for safety and clarity. It is good for some people. – Pavel Radzivilovsky Sep 01 '12 at 20:30
  • 2
    Since @PavelRadzivilovsky has now recognized that his information is twenty years out of date, you can ignore everything he says here. – Pete Becker Sep 02 '12 at 12:34
  • @pete, I was referring to function proxy. Everything stays the same for static variables. No, not one per program. One per Module (DLL). Period. Please upvote. – Pavel Radzivilovsky Sep 04 '12 at 06:32
  • @PavelRadzivilovsky - once again: Microsoft's runtime library exports static data members; Borland's runtime library (much of which I wrote) exports static data members. Your insistence that it can't be done is wrong. It's **exactly** the same mechanism as exported functions. It works. – Pete Becker Sep 04 '12 at 10:40
  • @PeteBecker How can this be **exactly** the same mechanism, if the first one involves "jmp DWORD PTR __imp_funcXXX" at the fixed address of the function call (see http://msdn.microsoft.com/en-us/library/zw3za17w%28v=vs.80%29.aspx)? How do you think this could possibly work? With due respect for implementing CRT.. one cannot implement the linker to support this? Can you give one example of such variable? Can you post a backup to your claim that it works right across DLL boundaries, and not duplicates? Can you say how to write code to take advantage of it? CALL != MOV! – Pavel Radzivilovsky Sep 04 '12 at 13:54
  • @PavelRadzivilovsky - as I've explained several times, it uses the relocation table, with an indirect reference, just as a function call uses the relocation table, with an indirect reference. And, no, I'm not going to go through a lengthy technical discussion of how this works. I'm not going any further down this rathole that you've created. – Pete Becker Sep 04 '12 at 13:58
  • Function call is translated to CALL [one address]. Just as if it would be a local function. So anyway it is not the same, right? You suggest that variable access is coded as "Load variable address from a table" and then MOV to this address, for a specially-declspeced static variable? It would be possible. It would of course not be the same, and it it is also not what compilers do, AFAIK. And I do not see rathole, it is both an interesting and important software development issue. I believe you are completely mistaken, I may also be mistaken, but in no case your statement cannot be correct. – Pavel Radzivilovsky Sep 05 '12 at 06:53
  • further reading: the question of this guy: http://stackoverflow.com/questions/4911994/sharing-a-global-static-variable-between-a-process-and-dll – Pavel Radzivilovsky Sep 05 '12 at 06:57
0

The problem is that mThresholdSeverity is not exported from the DLL, but the two accessors are defined inline, so wherever they're called they have to be able to see mThresholdSeverity. There are two solutions: either export mThresholdSeverity from the DLL (sorry, I don't remember how to do that off the top of my head), or make the accessors non-inline functions, define them in the DLL, and export them from the DLL.

Pete Becker
  • 74,985
  • 8
  • 76
  • 165
  • it is not possible to export, or otherwise share a static variable across Windows DLL boundaries in a process. (it is possible for unix shared objects though) – Pavel Radzivilovsky Sep 01 '12 at 16:59
  • Seems to me that `__declspec(dllexport)` did it; I've just reviewed some of the Dinkumware library code (I did the Windows stuff for that many years ago) and that's one of the mechanisms that's used there. This might have changed more recently. – Pete Becker Sep 01 '12 at 17:11
  • Well, the question is what it did. The reason why it is impossible to share a variable is that the address of the variable in the target process address space is not known at build time, or more precisely link time, when external names are resolved into addresses. UNIX systems do the linking of SOs at runtime to resolve this. – Pavel Radzivilovsky Sep 01 '12 at 17:14
  • Well, the answer is that it worked. The compiler has to generate code that accesses the variable indirectly through the relocation table, and the loader fixes up the address at load time. As I implied before, this is done throughout Microsoft's C++ runtime library. – Pete Becker Sep 01 '12 at 17:19
  • @PeteBecker I tried __declspec(dllexport) on the variable, but the error is still there if get/setTresholdSeverity() definitions remain in header. – mrzli Sep 01 '12 at 17:39
  • @mrzli - it's pretty much the same as an exported function: dllexport on the definition, dllimport where it's used. But I haven't done this in a while, so there may be something more needed. – Pete Becker Sep 01 '12 at 18:53
  • @PeteBecker no and no; "The compiler has to generate code for indirect access" cannot do for variables. It is true for functions only. I will try to explain. There's no such thing in compilation of C language to translate variable access to anything else than "*(substitute address here)"-like expression in binary. Same is with function call, but then indirect access is achieved by actually calling a DIFFERENT function which calls yours. As you see, variable cannot do any action (which would be "assign it elsewhere") so the whole approach fails. Do not use DLLs on Windows.. – Pavel Radzivilovsky Sep 01 '12 at 20:18
  • @PavelRadzivilovsky - I'm speaking from direct experience with compiler and linker implementation at Borland. No, a function call doesn't go through another function; it's simply an indirect call through a jump table in the executable, whose contents get initialized by the loader. All that is transparent in the source code (aside from the annotations that tell it what's imported). But since your bottom line is "Do not use DLLs on Windows" I guess there's nothing more to be said. – Pete Becker Sep 02 '12 at 01:07
  • Seems like it is different for 16 and 32bit: http://msdn.microsoft.com/en-us/library/zw3za17w(v=vs.80).aspx – Pavel Radzivilovsky Sep 02 '12 at 06:30
  • @PavelRadzivilovsky - apparently your assertions here have been based on information that's twenty years out of date. – Pete Becker Sep 02 '12 at 10:59
0

Just so this doesn't get lost in the noise: you can change your accessors into ordinary, non-inline functions and define them in the same source file as your static data member. As long as you export them, you'll be able to call them from anywhere, and they'll access the static data just fine.

Pete Becker
  • 74,985
  • 8
  • 76
  • 165