0

My knowledge is a bit fuzzy in terms of how linking a DLL works but I'm observing a change to a static member variable in an executable that doesn't change the same static member variable in a DLL. Here's the scenario I have:

main.cpp is statically linked to mylib.lib. Within mylib.lib, I have the following class:

// foo.h
class Foo
{
public:
    static int m_global;
    Foo();
    ~Foo();
};

and

// foo.cpp
#include "foo.h"

int Foo::m_global = 5;

I also have a DLL that links to mylib.lib with the following:

//testdll.h
#define MATHLIBRARY_API __declspec(dllimport)

void MATHLIBRARY_API printFoo();

and

// testdll.cpp
#include "testdll.h"
#include <iostream>

void printFoo() {
    std::cout << Foo::m_global << std::endl;
}

Finally, in main.cpp of my executable

// main.cpp
#include <iostream>
#include "testdll.h"
#include "foo.h"

int main() {
    std::cout << Foo::m_global << std::endl;
    Foo::m_global = 7;

    std::cout << Foo::m_global << std::endl;
    printMutiply();

    return 0;
}

My expected output is 5, 7, 7. However, I'm seeing 5, 7, 5 which is telling me that the static member variable change isn't being seen by the DLL. Why is this so? And how can I make the DLL see changes in the static member variable made in the executable??

Daniel Heilper
  • 1,182
  • 2
  • 17
  • 34
Joe
  • 587
  • 1
  • 8
  • 15
  • 1
    If you compile the DLL, the `class foo` has to be attributed with `__declspec(dllexport)`. If you compile executable, the (same) `class foo` has to be attributed `__declspec(dllimport)`. This is often done (e.g. by us) using some macro trickery. I found a concerning SO Q/A: [SO: Macro for dllexport/dllimport switch](https://stackoverflow.com/q/14980649/7478597) which might be of help. – Scheff's Cat May 08 '18 at 07:15

2 Answers2

4

Believe it or not, but your application violates One Definition Rule, and as such, triggers Undefined Behavior. Your program (as it is called in C++ standard) ends up having double definition of Foo::m_global - one in the loadable library, and another one inside main. As an observable effect of this undefined behavior, Microsoft dynamic loader creates two symbols, one from loadable object, another from main.

In Linux word, ld (linux loader) would actually conflate those symbols into one (which would, in turn, trigger double destruction for non-trivial objects).

Bottom line - do not share definitions of global symbols between loadable libraries and executable. This goes for both functions and variables, but sharing functions usually does not have a visible side-effect, although technically is undefined behavior as well.

SergeyA
  • 61,605
  • 5
  • 78
  • 137
  • _do not share global symbols between loadable libraries and executable._ I don't get it. How will you ever call any library function from outside? Do you consider "global" != "extern"? – Scheff's Cat May 08 '18 at 07:11
  • 1
    @Scheff by sharing I meant sharing **definition**. My wording is not perfect, I will edit. – SergeyA May 08 '18 at 07:13
  • I only see one definition of `Foo::m_global` (the `int Foo::m_global = 5;` in `foo.cpp`). Where is the second one? – rubenvb May 08 '18 at 07:14
  • 1
    @rubenvb in the question description OP says the dll and main executable link to the same lib. – SergeyA May 08 '18 at 07:27
  • @SergeyA Right, that gives the program (the linked binary of main.cpp and foo.cpp and testdll.cpp) exactly one definition of `Foo::m_global`. – rubenvb May 08 '18 at 07:28
  • 1
    @rubenvb, no. It gives program exactly two definitions of it. Both loadable library and main executable have a definition of `Foo::m_global`, and when loadable is loaded into executable, you end up with two. – SergeyA May 08 '18 at 07:29
  • 2
    @SergeyA, ah, there's static linking involved. That's highly confusing and wrong indeed. You should add to your answer a proper solution instead of only a description of the problem. – rubenvb May 08 '18 at 07:29
  • @rubenvb and happens all the time! It is actually quite hard to avoid it :) when using common libraries. I do not know proper solution, because OP didn't provide reasoning for statically linking. I am pretty sure the toy example is not the real code. – SergeyA May 08 '18 at 07:30
  • @sergeyA you're correct, the example here is representative of an issue I'm seeing in a larger code base – Joe May 08 '18 at 10:20
  • @SergeyA given freedom of design, how would you approach resolving this issue?? – Joe May 08 '18 at 14:22
  • 1
    Not link anything statically, most likely. @Joe – SergeyA May 08 '18 at 14:27
  • I see - sorry that it's not obvious for me. How would you go about sharing the static member with the DLL with it defined in one place? @SergeyA – Joe May 08 '18 at 14:33
  • 1
    @Joe, I am talking in generalities, because I do not have your real case. What I meant was that you do not link libraries static in this scenario at all. – SergeyA May 09 '18 at 00:50
0

This is how we handle it in our projects:

/* First a general definition which covers differences
 * of Windows and Linux for all of your libraries:
 */
#ifdef _WIN32
/* for Windows, Visual C++ */
#define MY_EXPORT __declspec(dllexport)
#define MY_IMPORT __declspec(dllimport)
#else /* _WIN32 */
/* for gcc */
#define MY_EXPORT __attribute__((visibility("default")))
#define MY_IMPORT __attribute__((visibility("default")))
#endif /* _WIN32 */

This has to be prepared for each of your libraries:

/* The API macro to distiguish two cases:
 * 1. DLL/Shared Object
 * 2. usage of DLL/Shared Object
 */
#ifdef BUILD_MY_LIB
#define  MY_LIB_API MY_EXPORT
#else /* BUILD_MY_LIB */
#define MY_LIB_API MY_IMPORT
#endif /* BUILD_MY_LIB */

MY_LIB_API is then used in any class to be exported from MyLib library:

class MY_LIB_API Foo {
};

The rest is done with compiler arguments:

  1. To compile the MyLib DLL or Shared Object, -DBUILD_MY_LIB is added to command line arguments of compiler.

  2. To use the DLL or Shared Object, no additional setting is necessary.

Actually, our solution considers static libraries also. It defines MY_LIB_API to be empty. However, we didn't used it for a long time anymore. Hence, I left this part out. (I must admit I forgot how it works in detail...)

Scheff's Cat
  • 19,528
  • 6
  • 28
  • 56