DLL + класс экспорта + член шаблона func = неразрешенный внешний символ. Есть ли шанс исправить?
Прежде всего, это не повторяющийся вопрос, потому что 1) это проблема с компоновщиком, компилятор успешно прошел, потому что я его явно создал. 2) Речь идет не о классе шаблона, а о функции-члене шаблона. 3) У меня есть некоторые ограничения на структуру кода, поэтому некоторые существующие приемы не применяются. Я искал свой заголовок здесь, и первые несколько потоков ( 40832391, 20330521, 25320619, 12848876, 36940394) все о классе шаблона, а не о функции члена шаблона. Некоторые другие потоки на самом деле говорят о сбое создания экземпляра, так что на самом деле проблема с компилятором, но я попытался выполнить явное создание экземпляра и компиляция прошла успешно, чтобы повторить. Поэтому я надеюсь, что вы можете немного искушать искушение закрыть мой вопрос как дубликат, и вот моя тема.
Среда:
- Windows 10 версия 1803
- Visual Studio 2015, обновление 3
- Отладка режима x64 в VS
Источник:
Есть два проекта:
1) DllProject, созданный как dll, содержит два источника: Dll.h и 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, собранный как exe, содержит Exe.cpp.
Exe.cpp:
#include "Dll.h"
int main() {
AClass a;
float f = 0.f;
a.CallFunc<float>(f);
}
Как видите, я хочу только вызвать функцию-член шаблона CallFunc
определенный в dll, который в свою очередь вызывает другую функцию-член основного шаблона Func
это делает настоящую работу. Мне не нужно звонить Func
непосредственно в EXE, поэтому мне не нужно экспортировать его в DLL. Только CallFunc
находится в API, который должен быть экспортирован, и он работает нормально. Проект dll DllProject компилируется правильно. Exe-проект ExeProject также компилируется без проблем. Но ошибки возникают во время связывания:
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 ==========
Я явно создал экземпляр функции-члена шаблона Func
в Dll.cpp в строке 8, так что компиляция работает. Но компоновщик не может связаться с этой созданной в dll функции изнутри CallFunc
, Я думаю, что функция создается в dll, а не в exe, потому что я указал __declspec(dllimport)
для класса. Я не знаю почему CallFunc
не могу найти Func
; это просто прямо над ним.
Источники изолированы и свернуты из большого открытого источника (DllProject соответствует самой библиотеке, а ExeProject для тестирования кода), поэтому реализация и объявление функции-члена шаблона разделены на два файла, что неизбежно в большом проекте, и структура кода не подлежит изменению. DllProject, как следует из его названия, должен быть собран как dll, хотя сборка и связывание всего как статической библиотеки работает без каких-либо проблем. Я провел большой поиск, но потоки на этом форуме и других форумах либо перемещают реализацию шаблона в объявление класса, либо в заголовок с помощью файла #include.tpp, что нарушает вышеуказанные ограничения, или предлагает явное создание экземпляров различными способами. выражения, что, я думаю, я уже сделал, потому что компиляция пройдена.
Я пробовал следующие методы:
1) Компилировать в конфигурации Release
2) использовать inline
(и даже __forceinline
)
3) Поместите явную специализацию в Dll.h в классе:
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) Поместите явную специализацию в Dll.h вне класса:
class API_TYPE AClass {
...
};
template<> void AClass::Func<float>(float&);
6) Поместите явную реализацию в Dll.h вне класса:
class API_TYPE AClass {
...
};
template void AClass::Func<float>(float&);
7) Добавить API_TYPE
в декларации Func
, реализация шаблона и явная реализация
template <class T> void API_TYPE Func(T& data);
Но ни один из них не работает и сообщает об одной и той же ошибке.
8) Поместите явный экземпляр в Dll.h в классе:
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&);
};
Ошибка компиляции: ошибка C2252: явное создание шаблона может происходить только в области пространства имен
9) Поместите явную специализацию в Dll.cpp:
Ошибка компиляции: ошибка C2910: 'AClass::Func': нельзя явно специализировать
Я надеюсь, что этого достаточно, чтобы продемонстрировать мои усилия. Итак, есть ли шанс исправить "неразрешенный внешний символ" в соответствии с вышеупомянутым ограничением? В случае, если вы забыли или не читали вообще, ограничение
Шаблон реализации
Func
должен быть отделен от его объявления, т. е. не должен быть в объявлении класса или в заголовочном файле.
В случае, если вы предполагаете, что я не знаю, должна быть создана экземпляр функции шаблона, повторюсь, я явно ее создал Func
и попробовал это разными способами. Так что компилятор вполне доволен, но компоновщик издает ошибку "неразрешенный внешний символ". Так почему же компоновщик не может найти уже созданную функцию-член шаблона? Я также проверил выходные символы в библиотеке импорта DllProject.dll, используя dumpbin
, Символ ??$Func@M@AClass@@QEAAXAEAM@Z
на самом деле в нем проживает, так же, как земля вращается на восток. Итак, знаете ли вы, как активно отслеживать поведение компоновщика, чтобы выяснить, почему он не может найти расположение функции вместо слепого предположения? Большое спасибо.
1 ответ
№6 с экспортной директивой должен работать:
template API_TYPE void AClass::Func<float>(float&);
Явное создание экземпляра сообщает компилятору, что этот вариант создается в некотором модуле перевода, в то время как директива экспорта сообщает компоновщику, что он должен быть экспортирован / импортирован.