How do you control __declspec(dllimport/dllexport) on Windows when building multiple DLLs that use each other?

70 Views Asked by At

Please note that my problem is different than the one addressed in How can I define the switch macro that switches between my dllexport and dllimport attribute macros with CMake?. This is not about the switch between import and export. I have all kinds of switching going on, and I have followed every suggestion I could find online. None of them address the issue of multiple DLLs where most of them refer to other DLLs. And that includes not having any cycles. This is more of how do you have BOTH importing and exporting going on at the same time?


I have several DLLs that have some dependency on each other. Under Linux/Mac they all just work, but under Windows, you have to specify importing and exporting explicitly. The standard way is to use an include file (.hpp) that #defines a variable like __EXP_IMP to be either __declspec(dllimport) or __declspec(dllexport), depending on the setting of another #define, such as BUILD_DLL or USE_DLL.

The problem is, I have a DLL that is being compiled by CMake, where its header files should be marked with "exported", while the header files for another external DLL (which was just built prior) should be marked as "imported". Obviously, this standard mechanism using a couple #defines will not handle such a thing.

Here is some of my actual code. The following is what it is in the "lib_dll.h" header file, which is included at the top of all project include files:

#ifndef DYNALIB_EXP_IMP
#   ifdef DYNALIB_BUILD_DLL
#     define DYNALIB_EXP_IMP      __declspec(dllexport)
#     define DYNALIB_IMPORT      __declspec(dllimport)
#     define DYNALIB_TPL
#   elif defined(DYNALIB_USE_DLL)
#     define DYNALIB_EXP_IMP      __declspec(dllimport)
#     define DYNALIB_IMPORT      __declspec(dllimport)
#     define DYNALIB_TPL         extern
#   else
#     define DYNALIB_EXP_IMP
#     define DYNALIB_IMPORT
#     define DYNALIB_TPL
#   endif
#endif

So, three states can be selected for these defines: BUILDING the DLL, USING the DLL, and doing neither.

Then, we have another DLL which has the following at the top of one of its C++ source files:

#include <lib_dll.h>

#include <Utilities/Hashcoder.h>
#include <MindComponents/Goal.h>
#include <MindComponents/State.h>

Goal::Goal(const char* name) : _name(name) {
};

In this scenario, we want the Hashcoder.h file to have the IMPORT version of the declaration in it, because it is importing the class from the external DLL, but we want the EXPORT version of the declaration in the Goal.h and State.h include files.

Here is some code fragments showing how things look inside the include files:

#include <lib_dll.h>

namespace hashc {
    extern DYNALIB_EXP_IMP const int      HASH_SEED;               // Starting point for first cycle
    extern DYNALIB_EXP_IMP const int      HASH_MULTIPLIER;         // Multiplier for each cycle
    extern DYNALIB_EXP_IMP const unsigned HASH_MASK;               // All 1 bits except the sign

    DYNALIB_EXP_IMP int hashSeed();
    DYNALIB_EXP_IMP int hashMultiplier();
    DYNALIB_EXP_IMP int hashMask();

    DYNALIB_EXP_IMP int hashCode(bool key);
    DYNALIB_EXP_IMP int hashCode(char key);
    DYNALIB_EXP_IMP int hashCode(double key);

I can't be the only one in the world who has faced this. Any brilliant solutions would be appreciated.

Getting my project working under Windows has been a royal pain. All of this just worked without any effort at all under Linux and Mac. I have spent over 2 weeks refactoring my code to get it to compile, link, and run under Windows. I'm almost there.

Everything works fine with the single utility DLL. The test executable linked it fine, and a whole bunch of tests all passed. In that case, however, the test app IMPORTED everything, while the DLL EXPORTED everything, so it was easy. The issue comes in when you introduce more DLLs, and you have some that need to import stuff from another DLL, while they are being built.

Things I Tried

I've been working on some ideas, and I do have one that looks promising. I just don't know if it is the best way to approach things. The idea is to organize the header files into 3 groups. First, you have includes that don't import or export. Then you have the includes that import from some other DLL. Then you have the headers for this DLL, which will do either IMPORT or EXPORT, depending on the context.

Then once the headers are organized in that fashion, I can use a modified version of the <lib_dll.h> file that looks like this:

#undef DYNALIB_EXP_IMP
#undef DYNALIB_TPL

#ifdef DYNALIB_BUILD_DLL
#  define DYNALIB_EXP_IMP      __declspec(dllexport)
#  define DYNALIB_TPL
#elif defined(DYNALIB_USE_DLL)
#  define DYNALIB_EXP_IMP      __declspec(dllimport)
#  define DYNALIB_TPL         extern
#else
#  define DYNALIB_EXP_IMP
#  define DYNALIB_TPL
#endif

#undef DYNALIB_BUILD_DLL
#undef DYNALIB_USE_DLL

Then you would do the following:

#include <lib_dll.h>
#include <header1.hpp>
#include <header2.hpp>

#define DYNALIB_USE_DLL
#include <lib_dll.h>

#include <header_import_DLL1.h>
#include <header_import_DLL2.h>

#define DYNALIB_BUILD_DLL
#include <lib_dll.h>

#include <header_export1.h>
#include <header_export2.h>

I haven't had the chance to try this yet, but I will later today.

0

There are 0 best solutions below