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

1

6

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:

  • Windows 10 version 1803
  • Visual Studio 2015 Update 3
  • Debug x64 mode in VS

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.

Pines answered 15/7, 2018 at 9:34 Comment(8)
"are all about template class, not template member function." There's not so much difference, the definition must appear in the header or an explicitly linked unit which covers all the instantiated types.Faze
Have you tried explicit instantiation in your exe project?Israelite
@john: the same error.Pines
I think variant №6 with explicit instantiation should have worked. Maybe with export directive like template API_TYPE void AClass::Func<float>(float&);Formic
@VTT: the same error.Pines
"I don't need to call Func directly in exe so I don't need to export it in dll." CallFunc is an inline function, so the executable will be the one instantiating and compiling CallFunc and therefore calling Func, not the library. Thus it must be exported.Fremitus
Do you actually link the library? variant template API_TYPE void AClass::Func<float>(float&); works fine for me.Formic
@VTT: Yes, I cleaned the solution, checked the references and rebuild everthing again, it works! You are the winner! Do you mind posting your answer to my question? I will accept it. Thank you.Pines
F
5

№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.

Formic answered 15/7, 2018 at 10:29 Comment(3)
Above all, thank you very much for the answer! The problem has puzzled me for many days. Two comments I would like to add: 1) To add __declspec(dllexport) only to the class is insufficient. To export an instantiated template member function that is implemented separately in .cpp, we have to still add __declspec(dllexport) to the template function declaration for the linker to be able to find it. ...Pines
2) Though I have tried this approach (#7)), I don't have the luck to see it working on my machine. Then I figured out what was happening. Because I am working in VS, the import lib is linked by References -> DllProject, not by being explictly specified in Linker->Additional Dependencies list. Setting properties of this reference is also among my many trials, but one of them was not set properly and I left in haste because I think the error might be caused by some C++ language specific reason. So keep a clear mind is so important, especially when troubleshooting subtle problems like this :-)Pines
One more: 3) It does not matter where the export-directed explicit instantiation is. It's OK to put it in .h or .cpp. The key is API_TYPE is needed in function declaration even if the class containing it is qualified with the same thing.Pines

© 2022 - 2024 — McMap. All rights reserved.