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?