c++ - 仅在模板中使用 _com_ptr_t(由#import 自动创建)时出现链接错误
问题描述
在#import
.tlb文件之后,我尝试_com_ptr_t
在函数模板中使用生成的对象之一。如果我使用的函数也在函数模板之外使用,这很好用。但是,如果它仅在函数模板中使用,则会出现链接错误。
谁能解释为什么会这样?我猜这与模板实例化有关,但我不确定该#import
机制是如何工作的。这是预期的行为吗?
这是一个最小示例,其中包括根据 的值的所有组合COMPILE_OPTION
。
- 仅定义使用该对象的常规函数(使其被实例化)
- 仅定义函数模板(不会导致使用的函数被实例化) - 这是问题案例
- 定义两者,这表明如果在常规函数中使用对象,则模板选项按预期工作(而不是在 #2 的情况下)
代码:
#import <mshtml.tlb>
#if COMPILE_OPTION & 1
int foo(MSHTML::IHTMLElementPtr elem) {
return elem->tagName.length();
}
#endif
#if COMPILE_OPTION & 2
template <class T>
int bar(T elem) {
return elem->tagName.length();
}
#endif
int main() {
#if COMPILE_OPTION & 2
bar(MSHTML::IHTMLElementPtr());
#else
foo(MSHTML::IHTMLElementPtr());
#endif
}
现在以下编译成功:
cl Source.cpp /DCOMPILE_OPTION=1
cl Source.cpp /DCOMPILE_OPTION=3
但是这个:
cl Source.cpp /DCOMPILE_OPTION=2
产生以下内容(相关错误以粗体显示):
Source.obj:错误 LNK2019:函数“int __cdecl bar <class _com_ptr_t<class _com_IIID<struct MSHTML::IHTMLElement,&struct __s_GUID const _GUID_3050f1ff_98b5_11cf_bb82_00aa00bdce0b> > >(class _com_ptr_t<class _com_IIID<struct MSHTML::IHTMLElement,&struct __s_GUID const _GUID_3050f1ff_98b5_11cf_bb82_00aa00bdce0b> >)" (??$bar@V?$ _com_ptr_t@V?$_com_IIID@UIHTMLElement@MSHTML@@$1?_GUID_3050f1ff_98b5_11cf_bb82_00aa00bdce0b@@3U__s_GUID@@B@@@@@@YAHV?$_com_ptr_t@V?$_com_IIID@UIHTMLElement@MSHTML@@$1?_GUID_3050f1ff_98b5_11cf_bb82_00aa00bdce0b@@3U__s_GUID@@ B@@@@@Z)
Source.exe : 致命错误 LNK1120: 1 unresolved externals
笔记:
- 一种解决方法是使用原始 COM 方法
- 这显然是一个简化版本,我使用模板的原因是我使用的库具有多种类型,这些类型具有不共享通用基本接口的通用功能。
解决方案
根据评论,我发现了这篇存档 的KB 文章,其中讨论了#pragma implementation_key编译器指令,当类型库有很多方法(似乎超过 1000 个)时,该指令用于提高性能,mshtml.tlb 就是这样做的。该指令出现在生成的mshtml.tlh和.tli文件中。这篇文章还谈到了“副作用”,这可能就是这里发生的事情。
如果将(未记录!)“no_function_mapping”修饰符添加到#import:
#import <mshtml.tlb> no_function_mapping
这些#pragmas 被删除并且代码编译和链接。
[编辑] 更多解释/猜测。似乎 MS 已经引入了 implementation_key pragma 来尝试提高导入/导入类型库的性能(以某种方式):在调用者可能只使用 10,000 个函数中的 2 个函数的情况下。
这是生成的mshtml.tlh标头的部分,它定义了类型库的所有单个方法,全部51,468个,由start_map_region和stop_map_region pragma 包围。失败的方法 GettagName() 是数字 190。
#pragma start_map_region("your_project_path\Debug\mshtml.tli")
__declspec(implementation_key(1)) void IHTMLStyle::PutfontFamily ( _bstr_t p );
__declspec(implementation_key(2)) _bstr_t IHTMLStyle::GetfontFamily ( );
//And on and on, down to the OP's failing method:
__declspec(implementation_key(190)) _bstr_t IHTMLElement::GettagName ( );
//and on and on, down to:
__declspec(implementation_key(51466)) ISVGElementInstancePtr ISVGUseElement::GetanimatedInstanceRoot ( );
__declspec(implementation_key(51467)) long ISVGElementInstanceList::Getlength ( );
__declspec(implementation_key(51468)) ISVGElementInstancePtr ISVGElementInstanceList::item ( long index );
#pragma stop_map_region
这是mshtml.tli文件中的相应部分,它由 mshtml.tlh 标头#include'd:
#pragma implementation_key(190)
inline _bstr_t MSHTML::IHTMLElement::GettagName ( ) {
BSTR _result = 0;
HRESULT _hr = get_tagName(&_result);
if (FAILED(_hr)) _com_issue_errorex(_hr, this, __uuidof(this));
return _bstr_t(_result, false);
}
知识库文章提到了此映射过程的副作用。我的猜测是,在使用这个 pragma 时,MS 编译器可以检测到显式调用,例如:
MSHTML::IHTMLElementPtr elem();
elem->tagName.length();
但不承认 OP 的模板函数中的间接使用。
#import 语句的no_function_mapping限定符告诉代码生成器不要添加start_map_region、stop_map_region和implementation_key杂注,您会得到“常规”代码,它可以识别模板替换。
如果 OP 为他们的测试选择了一个较小的类型库,那么这个“特性”可能并不明显,因为可能没有使用implementation_key pragma。
我的最小测试没有显示使用no_function_mapping修饰符编译的可执行文件中的任何大小变化。
性能改进可能是什么尚不清楚。它可能在运行时占用较小的内存,因为双接口不必跟踪不会被调用的 dispId。我的理解有点模糊,但是使用 IDispatch::Invoke() 需要使用调用 IDispatch::GetIdsOfNames() 后创建的某种映射来确定将为给定函数名称调用哪个 dispId。对于 50k+ 方法,这个映射会变得非常大,所以也许implementation_key方法是一种减少映射大小(以及因此查找时间)以仅包含那些实际使用的方法的方法?
推荐阅读
- azure - PowerShell - Azure 站点恢复 - 故障转移
- flutter - 实现启用空安全的类会导致编译错误
- javascript - 为什么我的mongodb和node js连接不成功?
- php - Google Drive API:为什么在我查看 webContentLink 之前上传的文件不会出现在我的 Google Drive 中
- c++ - 如何将`cv::Mat`的`type`传递给函数?
- laravel - 在共享主机上托管 Laravel 网站有哪些安全风险?
- apache-kafka - 从组中删除的 Kafka 消费者可以重新加入吗
- ios - Swift:iPhone 和独立 Watch App 上的本地通知
- azure-devops - 无法在“主页/Page1/Page2/SpecificPage”检索 Azure Wiki 页面。返回的 HTTP 代码是“404”
- python - 如何刮掉所有
- 特定div下的实例并处理它