[VS2015] Importing symbols from DLL in release mode - linker error

Started by
4 comments, last by Juliean 8 years, 5 months ago

EDIT: I actually solved the issue, but please feel free to still read and respond to my questions if you know about dlls & templates.

Hello,

first off, I know that what I'm doing is not actually the best practice, but it serves my purpose very well, and changing the whole system would force some really drastical changes on my codebase that I don't really want to take right now.

second, I found out that the problem is the symbol not actually being exported - so you can read the post as if the part about actually using it in the other project is irrelevant.

That out of the way, Im having some really awkward issues when exporting/importing template symbols from my core dll to other dlls. I'm doing this to share a semi-runtime generated type id between engine/editor/plugins. The core-code for this is here:


// Dll.h
#pragma once

#ifdef ACCLIMATE_EXPORT
#define ACCLIMATE_API __declspec (dllexport)
#define EXPORT_TEMPLATE template
#else
#define ACCLIMATE_API __declspec (dllimport)
#define EXPORT_TEMPLATE extern template
#endif

#define EXPORT_TEMPLATE_CLASS(...) \
    EXPORT_TEMPLATE class ACCLIMATE_API __VA_ARGS__;

This should be correct, and most of the time it works (ACCLIMATE_EXPORT is defined for both release and debug mode in the project settings). However, with my asset system, I am irregularely and pretty randomly getting issues with specific symbols not being exported, like right now. This is a little complicated, but here is an example of what works/compiles right now (lets look at ITexture):


// Gfx\RegisterAssets.h
#pragma once
#include "..\Asset\Asset.h"

namespace acl
{
	namespace asset
	{
		struct Context;
	}

	namespace gfx
	{

		class Screen;
		struct LoadContext;

		class ITexture;
		class IFont;
                // ...

		EXPORT_ASSET_OBJECT(gfx::ITexture)
		EXPORT_ASSET_OBJECT(gfx::IFont)
                // ...

		void registerAssets(const asset::Context& asset, const Screen& screen, const gfx::LoadContext& load);

	}
}

EXPORT_ASSET_OBJECT is the following macro:


#define EXPORT_OBJECT(Type) \
        static_assert(acl::core::isObject<Type>::value, "Type is not an object"); \
        EXPORT_TEMPLATE_CLASS(acl::core::ObjectContainer<Type>);

#define EXPORT_ASSET_OBJECT(Type) EXPORT_OBJECT(acl::asset::Asset<Type>) \
                                EXPORT_OBJECT(Type)

What this does is export the "ObjectContainer<Type>" symbol from the Dll, which is the core class in my type system, and needed for the type id.

This code so far is all from my main engine, which creates a DLL. In the next step, I will actually need this exported type id, for example in the editor application (using the engine dll) like this:


// Editor/Gfx/TextureEditArea.h
#pragma once
#include "API\BaseAssetEditArea.h"

namespace acl
{
    namespace gfx
    {
       class ITexture;
    }   

    namespace gui
    {
        class Texture;
        class ScrollArea;
        class Button;
    }

    namespace editor
    {

        USE_ASSET_OBJECT(gfx::ITexture); // here is the import statement

        class TextureEditArea :
            public AssetEditArea<gfx::ITexture> // needs symbol here, otherwise typeid is wrong
        {
        public:
            TextureEditArea(gui::Module& module);
            ~TextureEditArea(void);

            const std::wstring& OnGetIcon(bool isLocked) const override;

        private:

            void OnSelectAsset(AssetType* pAsset) override;

            void OnScaleButton(bool isDown);
            void OnZoomButton(void);

            gfx::TextureAssetPtr m_pAsset;
            gui::Texture* m_pTexture;
            gui::ScrollArea* m_pArea;
            gui::Button* m_pZoomButton;

            bool m_scale;
        };

    }
}

So what I'm doing then is having an USE_ASSET_OBJECT-macro, that is identically to EXPORT_ASSET_OBJECT, just that it always does dllimport, so it should import the symbol. And it does work - the symbol is found, and the ID is the same for engine, editor and plugin-dlls. Works both for release and debug mode - and the same structure of code is used for about 20 other assets and works synchronously.

Ok, now for what doesn't work. I just made a new type of asset in the main engine:


// AI/RegisterAssets.h
#pragma once
#include "..\Asset\Asset.h"

namespace acl
{
    namespace asset
    {
        struct Context;
    }

    namespace ai
    {

        class BehaviourTree;

        EXPORT_ASSET_OBJECT(BehaviourTree)
        
        class Registry;

        void registerAssets(const asset::Context& asset, Registry& registry);

    }
}

Same concept: Forward declare class, EXPORT_ASSET_OBJECT. FYI, both this and the Gfx/RegisterAsset.h-file is used in the exact same spot side-by-side:


// BaseEngine.cpp
#include "AI\RegisterAssets.h"
#include "Gfx\RegisterAssets.h"

// ... in some method:

        ai::registerAssets(assetContext, aiContext.registry);
        gfx::registerAssets(assetContext, gfxContext.screen, gfxContext.load);

Also both have the only instances of EXPORT_ASSET_OBJECT for the specific assets.

Ok, and now I want to also use my BehaviourTreeAsset in the editor:


// Editor/AI/BehaviourTreeEditArea.h
#pragma once
#include "API\BaseAssetEditArea.h"

namespace acl
{
    namespace ai
    {
        class BehaviourTree;
    }

    namespace gfx
    {
        class ILine;
    }

    namespace render
    {
        class IStage;
    }

    namespace editor
    {

        USE_ASSET_OBJECT(ai::BehaviourTree);

        class BehaviourTreeEditArea :
            public AssetEditArea<ai::BehaviourTree>
        {
        public:
            BehaviourTreeEditArea(gui::Module& module, gfx::ILine& line, render::IStage& stage);
            ~BehaviourTreeEditArea(void);

            void OnSelectAsset(AssetType * pAsset) override;
        };

    }
}

Same concept as for textures - forward declare type of asset, USE_ASSET_OBJECT... boom!


BehaviourTreeEditArea.obj : error LNK2001: Unresolved external symbol ""__declspec(dllimport) public: static unsigned int __cdecl acl::core::ObjectContainer<class acl::asset::Asset<class acl::ai::BehaviourTree> >::Type(void)" (__imp_?Type@?$ObjectContainer@V?$Asset@VBehaviourTree@ai@acl@@@asset@acl@@@core@acl@@SAIXZ)".

Unresolved external symbol linker error, only in release mode - the exact same code happily compiles and runs in debug mode. What the heck? The relevant code is pretty identical to how I use the ITexture-asset, and pretty much every other asset. Both are placed in the same project/exe. I made sure everything is recompiled in both the editor and the engine multiple times.

_______________________________________________________________________

So far the explanation. Now my questions:

- Has anyone any idea what could be going wrong here? I'm sorry I probably won't be able to make a minimal code example since this issue seems to be extremely highly specific, so I'm pretty much hoping on people having experienced something similar and/or having a general idea on what the compiler is doing with this. I'm aware that it could have something to do with VS2015, as I've had the same project not compile at first with a similar error when upgrading, but that could be resolved very easily by removing IMPORT_TEMPLATE statements at some wrong places...

- Since it is compiling in debug mode, how come there is even a difference with those things between debug and release mode in VS? I've had similar smaller issues before starting with VS2012 where debug would compile and release would give me a linker error due to missing dllimport-statements (I've never had anything THAT persistent, though). Is there any setting that causes ie. different handling on symbol export/import?

- On a more general line, is this even the correct way for handling template-symbol export/import? I have an EXPORT_TEMPLATE in at least one header of the dll-project, and an IMPORT_TEMPLATE in at least one header of the target project. Occasionally I have to IMPORT_TEMPLATE in specific additional places where I get a similar linker error, but I don't feel like this is all the right way... any tips on how to handle this, ie. is there a way to just use one header file that handles both the export in the source project and the import in the target project by including?

________________________________________________________

So, any idea? Would be thankful for any input, I've tried EVERYTHING to make this translation unit (BehaviourTreeEditArea.obj) compile in release mode I can think of (different setup of including the file that has the EXPORT_TEMPLATE statement, changed from forward declaration to actually including the actual class at multiple points, moving the IMPORT_TEMPLATE statement from .h to .cpp) but he just doesn't find that damn symbol...

Thanks!

EDIT:

Looking through the generated .lib file of my project, it seems for whatever reason it is already missing the wanted symbol (__imp_?Type@?$ObjectContainer@V?$Asset@VBehaviourTree@ai@acl@@@asset@acl@@@core@acl@@SAIXZ) or any related symbol whatsoever. Searching for all the ObjectContainer<Asset> symbols, I find a lot of them, like:


 __imp_?Type@?$ObjectContainer@V?$Asset@VITexture@gfx@acl@@@asset@acl@@@core@acl@@SAIXZ

for the ITexture-asset (which is working), however there is no trace of anything related to "__imp_?Type@?$ObjectContainer@V?$Asset@VBehaviourTree". Why? Do I need to do something else to ensure that the symbol is properly exported in release mode?

Advertisement

Okay, I've come to kind of a conclusion, but its still fucking weird.

I did some more testing with the .lib-file and found out that it actually generates some symbols regarding the BehaviourTree asset container, just not all of them. I found out that its depending on the actual class, as I could literally EXPORT_ASSET_OBJECT(...) EVERY other class at this point in the file, and it would generate the symbols:


#pragma once
#include "..\Asset\Asset.h"

namespace acl
{
    namespace asset
    {
        struct Context;
    }

    namespace ai
    {

        class BehaviourTreeWhatTheHolyFuck; // class does not exist anywhere else

        EXPORT_ASSET_OBJECT(BehaviourTreeWhatTheHolyFuck)
        
        class Registry;

        void registerAssets(const asset::Context& asset, Registry& registry);

    }
}

It doesn't even need to be an actual existing class, for f*cks sake. Renaming the other class didn't fix it though, so I removed every other reference to the BehaviourTree-class I could find. Turns out I could bring him to generate the symbol by removing a specific class, that also uses the ObjectContainer::Type-method that was missing.


#pragma once
#include "BehaviourTreeIO.h"
#include "..\Asset\AssetIO.h"

namespace acl
{
    namespace ai
    {
        EXPORT_ASSET_OBJECT(BehaviourTree); // btw this fixes it

        class BehaviourTreeAssetLoader :
            public asset::AssetDataBindingIO<BehaviourTreeAssetLoader, BehaviourTree>
        {
        };

    }
}

Adding another EXPORT declaration here also fixes it, but... why? Why do I need to export the symbol at this point? It doesn't even matter if this file is even included anywhere or the class is used, as long as its part of the project and compiled, it makes the compiler throw out the symbol. Also I have probably 20 different asset types that all follow the same pattern, and I always only had to EXPORT in that one file, even though the rest of the code is pretty identical. Look:


#pragma once
#include "ITextureLoader.h"
#include "ITexture.h"
#include "..\Asset\AssetIO.h"

namespace acl
{
    namespace gfx
    {

        // no export needed here, its already exportet in TextureAsset.h

        class TextureAssetLoader :
            public asset::AssetDataBindingIO<TextureAssetLoader, ITexture>
        {
        };

    }
}

Its exactly the same as the BehaviourTreeAssetLoader.h, in what I include, the same for cpp - in neigther case I include the file that has the EXPORT-declaration, which I would understand if I needed to do it generally but again I only had to do it for this one f*cking class, for whichever reason...

Uh, so, even though the issue is resolved, can somebody please try to explain what is actually going on here? Is there a reason, or is it some sort of compiler bug that I should report?

Point of order - you can't export templates. Templates only exist at compile time and are used to generate code. You can export template specializations however.

That being said - this seems to address your issue.

TLDR: DLLs and C++ rarely mix.


Point of order - you can't export templates. Templates only exist at compile time and are used to generate code. You can export template specializations however.

That being said - this seems to address your issue.

Well, thats what I am already doing anyways, in case you got consfused by the macro trickery or my horrible use of wordings :)

I've also read that article at one time before, but didn't find it again, so thanks for that. I quess I'll have to make sure to include the template specialization export macro everywhere I use it in the engine. I quess I just put it in the file that declares the object I want to export, didn't want to mix the type-system with the actual modules but since I'm using it pretty much everywhere anyways, should be fine.


TLDR: DLLs and C++ rarely mix.

Tell me more about it ;) Its specially sick once you add templates to the mix... But since I don't have to support multiple compilers, its okay most of the time... though if I continue to get those kind of errors, I might look for a different solution.

Modules can't come soon enough...

(Not that they'll solve the DLL issue)

Modules can't come soon enough...

I'm exited about that too.

Oh, btw in case anybody is interested, I kept on getting more of those linker errors those days - they definately changed something in VS2015, there is no way I was having that kind of trouble like once or twice a year and now like 10 times a day just by accident. Putting the EXPORT_TEMPLATE macro in the header of the object being exported seems to fix all of those issues though:


#pragma once
#include "..\Asset\ExportAsset.h"

namespace acl
{
    namespace gfx
    {

        class ITexture
        {

        };

	EXPORT_ASSET_OBJECT(gfx::ITexture)

    }
}

As I said I just wanted to have the type/object and asset system on top of everything else, but that isn't really viable anymore anymays, so yeah... I also confirmed that forward declaration is no problem so at least I don't have to recompile everything if my assets change because the ExportAssets-header doesn't depend on any class declaration:


#pragma once
#include "..\Core\ExportObject.h"

namespace acl
{
    namespace asset
    {

        template<typename Asset>
        class Asset;

#define EXPORT_ASSET_OBJECT(Type) EXPORT_OBJECT(acl::asset::Asset<Type>) \
                                EXPORT_OBJECT(Type)
#define USE_ASSET_OBJECT(Type) USE_OBJECT(acl::asset::Asset<Type>) \
                            USE_OBJECT(Type)

    }
}

So I've gotten rid of this issue finally, though I'm still not sure what exactly causes it in VS2015 and Release mode in particular, so if anyone has some insight on it I would be glad to know.

This topic is closed to new replies.

Advertisement