首页 > 解决方案 > 仅在模板中使用 _com_ptr_t(由#import 自动创建)时出现链接错误

问题描述

#import.tlb文件之后,我尝试_com_ptr_t在函数模板中使用生成的对象之一。如果我使用的函数也在函数模板之外使用,这很好用。但是,如果它仅在函数模板中使用,则会出现链接错误。

谁能解释为什么会这样?我猜这与模板实例化有关,但我不确定该#import机制是如何工作的。这是预期的行为吗?

这是一个最小示例,其中包括根据 的值的所有组合COMPILE_OPTION

  1. 仅定义使用该对象的常规函数​​(使其被实例化)
  2. 仅定义函数模板(不会导致使用的函数被实例化) - 这是问题案例
  3. 定义两者,这表明如果在常规函数中使用对象,则模板选项按预期工作(而不是在 #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

笔记:

  1. 一种解决方法是使用原始 COM 方法
  2. 这显然是一个简化版本,我使用模板的原因是我使用的库具有多种类型,这些类型具有不共享通用基本接口的通用功能。

标签: c++templatesvisual-c++importlinker-errors

解决方案


根据评论,我发现了这篇存档 的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_regionstop_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_regionstop_map_regionimplementation_key杂注,您会得到“常规”代码,它可以识别模板替换。

如果 OP 为他们的测试选择了一个较小的类型库,那么这个“特性”可能并不明显,因为可能没有使用implementation_key pragma。

我的最小测试没有显示使用no_function_mapping修饰符编译的可执行文件中的任何大小变化。

性能改进可能是什么尚不清楚。它可能在运行时占用较小的内存,因为双接口不必跟踪不会被调用的 dispId。我的理解有点模糊,但是使用 IDispatch::Invoke() 需要使用调用 IDispatch::GetIdsOfNames() 后创建的某种映射来确定将为给定函数名称调用哪个 dispId。对于 50k+ 方法,这个映射会变得非常大,所以也许implementation_key方法是一种减少映射大小(以及因此查找时间)以仅包含那些实际使用的方法的方法?


推荐阅读