c++dlldynamic-linkingmember-functionstemplate-function

DLL + export class + template member func = unresolved external symbol. Any chance to fix?


First of all, This is not a duplicate question, because 1) this is a linker problem, compiler is passed successfully because I have explicitly instantiated. 2) It's not about template class, but template member function, 3) I have some restrictions on the code structure so some existing tricks do not apply. I have searched my title here and the first few threads (40832391, 20330521, 25320619, 12848876, 36940394) are all about template class, not template member function. Some other threads are actually talking about failure of instantiation so actually a compiler issue but I have tried explicit instantiation and compiling has passed successfully, to repeat. So I hope you can suppress the temptation a little bit to close my question as duplicate and here comes my thread.

Environment:

Source:

There are two projects:

1) DllProject, build as a dll, contains two sources: Dll.h and Dll.cpp.

Dll.h:

#pragma once

#ifdef _WINDLL
#define API_TYPE __declspec(dllexport)
#else
#define API_TYPE __declspec(dllimport)
#endif

class API_TYPE AClass {
public:
    template <class T> void Func(T& data);
    template <class T> void CallFunc(T& data) {
        Func<T>(data);
    }
};

Dll.cpp:

#include "Dll.h"

template <class T> void AClass::Func(T& data) {
    data++;
}

template void AClass::Func<float>(float&);  //attempt to explicitly instantiate

2) ExeProject, built as an exe, contains Exe.cpp.

Exe.cpp:

#include "Dll.h"

int main() {
    AClass a;
    float f = 0.f;
    a.CallFunc<float>(f);
}

As you can see, what I want is to only call the template member function CallFunc defined in dll which in turn calls another core template member function Func that does the real work. I don't need to call Func directly in exe so I don't need to export it in dll. Only CallFunc is in the API that needs to be exported and it works fine. The dll project DllProject compiles correctly. The exe project ExeProject also compiles without any problem. But errors arise at linking time:

1>------ Build started: Project: ExeProject, Configuration: Debug x64 ------
1>Exe.obj : error LNK2019: unresolved external symbol "public: void __cdecl AClass::Func<float>(float &)" (??$Func@M@AClass@@QEAAXAEAM@Z) referenced in function "public: void __cdecl AClass::CallFunc<float>(float &)" (??$CallFunc@M@AClass@@QEAAXAEAM@Z)
1>C:\tmp\DllProject\x64\Debug\ExeProject.exe : fatal error LNK1120: 1 unresolved externals
========== Build: 0 succeeded, 1 failed, 0 up-to-date, 0 skipped ==========

I have explicitly instantiated the template member function Func in Dll.cpp at line 8, so compilation works. But linker fails to link to this instantiated function in dll from inside CallFunc. I think the function is instantiated in dll, not in exe, because I specified __declspec(dllimport) for the class. I don't know why CallFunc can't find Func; it's just right above it.

The sources are isolated and minimized from a big opensource (DllProject corresponds to the library itself while ExeProject to test code), so the implementation and declaration of template member function are separated into two files which is unavoidable in large project, and the code structure are not to be changed. The DllProject, as its name suggests, is to be build as a dll, though building and linking everything as static library works without any problem. I have done a lot of search, but threads in this forum and others either move the template implementation into the class declaration, or into the header by #include a .tpp file, all violating the above restrictions, or suggest explicit instantiation in various ways of expression, which I think I have done already because compiling is passed.

I have tried the following methods:

1) Compile under Release configuration

2) use inline (and even __forceinline)

3) Put explicit specialization in Dll.h in class:

class API_TYPE AClass {
public:
    template <class T> void Func(T& data);
    template<> void AClass::Func<float>(float&);
    template <class T> void CallFunc(T& data) {
        Func<T>(data);
    }
};

4) Put explicit specialization in Dll.h outside class:

class API_TYPE AClass {
...
};
template<> void AClass::Func<float>(float&);

6) Put explicit instantiation in Dll.h outside class:

class API_TYPE AClass {
...
};
template void AClass::Func<float>(float&);

7) Add API_TYPE in the declaration of Func, template implementation and explicit instantiation

template <class T> void API_TYPE Func(T& data);

But none of them works and reports the same error.

8) Put explicit instantiation in Dll.h in class:

class API_TYPE AClass {
public:
    template <class T> void Func(T& data);
    template <class T> void CallFunc(T& data) {
        Func<T>(data);
    }
    template void AClass::Func<float>(float&);
};

Compile error: error C2252: an explicit instantiation of a template can only occur at namespace scope

9) Put explicit specialization in Dll.cpp:

Compile error: error C2910: 'AClass::Func': cannot be explicitly specialized

I hope that's enough to demonstrate my effort. So, is there any chance to fix the "unresolved external symbol" under the aforementioned restriction? In case you forget it or didn't read it at all, the restriction is

The template implementation of Func must be separate from its declaration, i.e., must not be in class declaration or in header file.

In case you assume I don't know template function should be instantiated, to repeat, I have explicitly instantiated Func, and tried it in many ways. So compiler is perfectly happy but the linker spews the error "unresolved external symbol." So why can't the linker find the already instantiated template member function? I also checked the output symbols in the import library DllProject.dll using dumpbin. The symbol ??$Func@M@AClass@@QEAAXAEAM@Z indeed resides in it, as factual as the earth rotating eastward. So do you know how to actively trace the behavior of the linker to figure out why it fails to find the function location instead of blind guessing? Thanks a lot.


Solution

  • №6 with export directive should work:

    template API_TYPE void AClass::Func<float>(float&);
    

    Explicit instantiation tells the compiler that this variant is instantiated in some translation unit while export directive informs the linker that it is should be exported / imported.