SamWhan
SamWhan

Reputation: 8332

Why does linker fail when accessing static member in inline constructor

Just started doing some coding for a very simple automated test framework for internal use. (I know there are hundreds of them out there - really good ones too, but at the moment that's not interesting, so don't point me to any of those, please ;)) I then came across the following problem which I can't explain, thus asking for your help.

I have the following code as part of a DLL:

(The code is barely an embryo and took me <2 minutes to write, so it's logic, structure - nothing - is refined, in any way yet.)

h-file:

#pragma once

#ifdef __DLL__   // Defined in DLL-project
    #define DLLEXPORT __declspec( dllexport )
#else
    #define DLLEXPORT
#endif

class DLLEXPORT AutoTest
{
public:
            enum    eTestID {TESTID_SomeFunction};
                    AutoTest(eTestID id, LPVOID lpData)
                    {
                        if(sm_bTestsActive)
                            ExecTest(id, lpData);
                    }
            void    ActivateTests();

private:
    static  void    ExecTest(eTestID id, LPVOID lpData)
                    {
                    }
    static  BOOL    sm_bTestsActive;
};

cpp-file:

#include "StdAfx.h"
#include "AutoTest.hpp"

BOOL AutoTest::sm_bTestsActive = FALSE;

void AutoTest::ActivateTests()
{
    sm_bTestsActive=TRUE;
}

This compiles just fine and the DLL gets generated.

Here's my problem though - when instantiating the class with:

AutoTest(AutoTest::TESTID_SomeFunction, &SomeData);

from the main application, the linker fails with

error LNK2001: unresolved external symbol "private: static int AutoTest::sm_bTestsActive" (?sm_bTestsActive@AutoTest@@0HA)

<2 minutes to write - now going on 5 hours to understand why it fails!!! :O

Here's what interesting - if I move the constructor to the cpp-file (not inlined) it works just fine!?!?

Here's that code:

h-file:

#pragma once

#ifdef __DLL__   // Defined in DLL-project
    #define DLLEXPORT __declspec( dllexport )
#else
    #define DLLEXPORT
#endif

class DLLEXPORT AutoTest
{
public:
            enum    eTestID {FK3059};
                    AutoTest(eTestID id, LPVOID lpData);
            void    ActivateTests();

private:
    static  void    ExecTest(eTestID id, LPVOID lpData)
                    {
                    }
    static  BOOL    sm_bTestsActive;
};

cpp-file:

#include "StdAfx.h"
#include "AutoTest.hpp"

BOOL AutoTest::sm_bTestsActive = FALSE;

AutoTest::AutoTest(eTestID id, LPVOID lpData)
{
    if(sm_bTestsActive)
        ExecTest(id, lpData);
}

void AutoTest::ActivateTests()
{
    sm_bTestsActive=TRUE;
}

(I've made some minor edits in the code after pasting, so there may or may not be simple syntax errors.)

Also, if I remove the reference to the static member from the inline versions constructor, it works fine.

Any ideas as to why the inline version won't work?

Upvotes: 3

Views: 281

Answers (2)

BartoszKP
BartoszKP

Reputation: 35891

Should be:

#ifdef __DLL__   // Defined in DLL-project
    #define DLLEXPORT __declspec( dllexport )
#else
    #define DLLEXPORT __declspec( dllimport )
#endif

You can declare C++ classes with the dllimport or dllexport attribute. These forms imply that the entire class is imported or exported. Classes exported this way are called exportable classes.

More information in the documentation.

Upvotes: 1

Mr.C64
Mr.C64

Reputation: 42914

Pay attention to your definition of DLLEXPORT.

Make sure that it is properly expanded to __declspec(dllexport) when building the DLL, or __declspec(dllimport) when building the client.

I'd suggest using a macro with a more specific name than the generic DLLEXPORT (to avoid conflicts with other macros with the same name).

Having static data members accessed from inline member functions works fine for me (tested with VS2013).

Minimal repro:

Create a Visual Studio solution with an empty DLL project and an empty console application project.

Inside the DLL project add two files:

DllClass.h:

#pragma once

#ifndef TEST_DLL_CLASS
#define TEST_DLL_CLASS __declspec(dllimport)
#endif

class TEST_DLL_CLASS DllClass
{
public:
    DllClass();

    int GetMember() const
    {
        return m_data1;
    }

    static int GetStaticMember()
    {
        return sm_data2;
    }

private:
    int m_data1;
    static int sm_data2;
};

DllClass.cpp:

#define TEST_DLL_CLASS __declspec(dllexport)
#include "DllClass.h"

int DllClass::sm_data2 = 2;

DllClass::DllClass()
    : m_data1(1)
{
}

Inside the console app project, add one file:

Test.cpp:

#include "..\DllTestClass\DllClass.h"
#include <iostream>
using namespace std;

#pragma comment(lib, "DllTestClass")

int main()
{
    DllClass dllClass;

    cout << dllClass.GetMember() << endl;
    cout << DllClass::GetStaticMember() << endl;
}

Make sure that when building the console test app, the linker can find the DLL .lib (DllTestClass.lib) file.
For that purpose, you can navigate the console app's project properties, going to:

Project Properties | Linker | Additional Library Directories

and add $(OutDir) to the additional library directories, making it:

$(OutDir);%(AdditionalLibraryDirectories)

Builds and works correctly for me.

Upvotes: 2

Related Questions