Search code examples
c++dlldllimportdllexport

Global initialized variable in a DLL


Is it possible to use a global variable from one DLL module to initialize global variable in other DLL module? If so, how?

I am using Microsoft Visual Studio 17.3.6 and use a C++/CLI wrapper class with some C files. I am in a bigger project but I have put together a smaller example that exhibits the behavior.

I would have thought that it would work like this. There are four files, file1.c, file1.h in one project and file2.c and file2.h in second project. They are built to two DLLs, file1.dll and file2.dll. First project has file1_EXPORTS preprocessor symbol defined and second has file2_EXPORTS defined. Include guards omitted for clarity.

file1.c:

#include <file1.h>
#include <file2.h>

structure1_t struct1 = {
    &struct2
};

file1.h:

typedef struct
{
    structure2_t* ptr;
} structure1_t;

#ifdef file1_EXPORTS
#define EXPORT_SYMBOL __declspec(dllexport)
#else
#define EXPORT_SYMBOL __declspec(dllimport)
#endif

EXPORT_SYMBOL structure1_t struct1;

file2.h:

typedef struct
{
    int* i;
} structure2_t;

#if defined (file2_EXPORTS)
#define EXPORT_SYMBOL __declspec(dllexport)
#else
#define EXPORT_SYMBOL __declspec(dllimport)
#endif

EXPORT_SYMBOL structure2_t struct2;

file2.c:

#include <file2.h>
#include <file1.h>

int i;
structure2_t struct2 = {
    &i
};

However, it cannot be compiled like this. There is an error C2099: initializer is not a constant. When I change the line (in file2.h) #define EXPORT_SYMBOL __declspec(dllimport) to #define EXPORT_SYMBOL extern, there are linker errors LNK2001: unresolved external symbol struct2 and LNK1120: 1 unresolved externals. Only combination that works for me is leaving the definition empty, e.g. #define EXPORT_SYMBOL. Other change that I must do is leave the definition in file1.h or the program fails in the next step.

Now those are built correctly and libraries file1.dll and file2.dll are created. Assume that there is a third file file3.cpp which uses the DLLs:

#include <file1.h>
#include <file2.h>
#include <iostream>

int main(void)
{
    std::cout << struct1.ptr << std::endl;
    std::cout << struct2.i << std::endl;
    return 0;
}

it prints:

0000000000000000
0000000000000000

So, my question is, is there some way this could work without the NULL pointers (e.g. struct1 has pointer to struct2 and struct2 has pointer to the integer)? Why is the program behaving like this, is it because file3.cpp sees only the declarations in the header and the variables are never initialized correctly? Other variant and solution would perhaps be to have an initialization function that puts the needed values to the structures. However, I am hesitant to doing this as I have a lot of structures that I would have to manually fill in. Or perhaps it could be filled in the DllMain as per here. Furthermore, I should mention that the C files have the extern "C" blocks in them.

I tried different combinations of __declspec(dllimport), __declspec(dllexport) and extern keywords but I cannot manage to make it work correctly. Also, putting EXPORT_SYMBOL to the C files as per Exporting global variables from DLL did not help either.

This answer would suggest that what I want to do is not possible but I do not know if it is relevant only to const variables or not.


Solution

  • What I was trying to do is not possible. Address of dllimported symbol cannot be used in an initializer of static data. I solved it by including the .c files with the structures' definition in each module that needed them. Structures themselves may stay extern when it is implemented this way. So with regards to my code examples, it would look like this:

    file1.c:

    #include <file1.h>
    #include <file2.h>
    #include <file2.c>
    
    structure1_t struct1 = {
        &struct2
    };
    

    file1.h:

    typedef struct
    {
        structure2_t* ptr;
    } structure1_t;
    
    extern structure1_t struct1;
    

    file2.h:

    typedef struct
    {
        int* i;
    } structure2_t;
    
    extern structure2_t struct2;
    

    file2.c:

    #include <file2.h>
    
    int i;
    structure2_t struct2 = {
        &i
    };
    

    (for illustration, have not tested it). This way, effectively no data are shared across DLLs as each one has its own copy. If data had to stay in the separate DLLs or if including the .c files was not possible, other solution would be not to have an initializer at all, use __declspec() instead of extern and initialize structures in a function call. However, I chose not to do that as I have hundreds of structures and it would not be feasible this way.