Link 仅在模板中使用 _com_ptr_t(由 #import 自动创建)时出错

Link error when using a _com_ptr_t (auto-created by #import) only in a template

函数模板中的 #importing a .tlb file, I tried to use one of the generated _com_ptr_t 个对象之后。如果我使用的函数也在函数模板之外使用,那么这很好用。但是,如果它仅用于函数模板,我会收到 link 错误。

谁能解释为什么会这样?我猜这与模板实例化有关,但我不确定 #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 : error LNK2019: unresolved external symbol "public: class _bstr_t __thiscall MSHTML::IHTMLElement::GettagName(void)" (?GettagName@IHTMLElement@MSHTML@@QAE?AV_bstr_t@@XZ) referenced in function "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@@?_GUID_3050f1ff_98b5_11cf_bb82_00aa00bdce0b@@3U__s_GUID@@B@@@@@@YAHV?$_com_ptr_t@V?$_com_IIID@UIHTMLElement@MSHTML@@?_GUID_3050f1ff_98b5_11cf_bb82_00aa00bdce0b@@3U__s_GUID@@B@@@@@Z)

Source.exe : fatal error LNK1120: 1 unresolved externals

备注:

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

根据评论,我发现了这个存档 KB Article 讲的是 #pragma implementation_key 编译器指令,当类型库有很多方法(看起来超过 1000 个)时,它用于提高性能,mshtml.tlb 做到了。该指令出现在生成的 mshtml.tlh.tli 文件中。这篇文章还谈到了 'side-effects',这可能就是这里发生的事情。

如果将(未记录!)'no_function_mapping' 修饰符添加到 #import:

#import <mshtml.tlb> no_function_mapping

这些#pragmas 被删除,代码编译和链接。

[编辑]更多的解释/猜测。似乎 MS 已经引入了 implementation_key pragma 来尝试提高 importing/imported 类型库的性能(以某种方式):在调用者可能只使用 10,000 个函数中的 2 个函数的情况下。

这是生成的 mshtml.tlh header 的部分,它定义了类型库的所有单独方法,所有 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 header #include:

#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);
}

知识库文章提到了此映射过程的副作用。我的猜测是,在使用此编译指示时,MS 编译器可以检测到显式调用,例如:

MSHTML::IHTMLElementPtr elem();
elem->tagName.length();

但无法识别 OP 模板函数中的间接使用。

#import 语句的 no_function_mapping 限定符告诉代码生成器不要在 start_map_region[=55= 中添加], stop_map_region, 和 implementation_key pragmas, 你会得到 'regular' 代码, 它识别模板替换。

如果 OP 为他们的测试选择了一个较小的类型库,那么这个 'feature' 可能不明显,因为 implementation_key pragma 可能没用过。

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

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