仅在模板中使用 _com_ptr_t(由 #import 自动创建)时出现链接错误 [英] Link error when using a _com_ptr_t (auto-created by #import) only in a template

查看:23
本文介绍了仅在模板中使用 _com_ptr_t(由 #import 自动创建)时出现链接错误的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

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

这是一个最小的例子,它包括根据 COMPILE_OPTION 的值的所有组合.

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

代码:

#import #if COMPILE_OPTION &1int foo(MSHTML::IHTMLElementPtr elem) {返回 elem->tagName.length();}#万一#if COMPILE_OPTION &2模板int bar(T elem) {返回 elem->tagName.length();}#万一int main() {#if COMPILE_OPTION &2栏(MSHTML::IHTMLElementPtr());#别的foo(MSHTML::IHTMLElementPtr());#万一}

现在编译成功:

<块引用>

cl Source.cpp/DCOMPILE_OPTION=1
cl Source.cpp/DCOMPILE_OPTION=3

但是这个:

<块引用>

cl Source.cpp/DCOMPILE_OPTION=2

产生以下内容(相关错误以粗体显示):

<块引用>

Source.obj : error LNK2019: 未解析的外部符号public: class _bstr_t __thiscall MSHTML::IHTMLElement::GettagName(void)" (?GettagName@IHTMLElement@MSHTML@@QAE?AV_bstr_t@@XZ) 引用在函数int __cdecl bar<class _com_ptr_t<class _com_IIID<struct MSHTML::IHTMLElement,&struct __s_GUID const _GUID_3050f1ff_98b5_11cf_bb0b80d<class _com_IIID<struct __s_GUID>>(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@@@@@@III_Hptr_MSHTML_t@$1?_GUID_3050f1ff_98b5_11cf_bb82_00aa00bdce0b@@3U__s_GUID@@B@@@@@Z)

<块引用>

Source.exe:致命错误 LNK1120:1 个未解析的外部

注意事项:

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

解决方案

根据评论,我发现了这个存档知识库文章,讨论了#pragma implementation_keyem> 编译器指令,用于在类型库有很多方法(似乎超过 1000 个)时提高性能,mshtml.tlb 就是这样做的.该指令出现在生成的 mshtml.tlh.tli 文件中.这篇文章还谈到了副作用",这可能就是这里发生的事情.

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

#import no_function_mapping

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

多一点解释/猜测.似乎 MS 引入了 implementation_key pragma 以尝试提高导入/导入类型库的性能(以某种方式):在调用者可能只使用 10,000 个函数中的 2 个的情况下.

这是生成的 mshtml.tlh 标头的部分,它定义了类型库的所有单独方法,所有 51,468 个方法,由 包围start_map_regionstop_map_region 编译指示.失败的方法 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 ();//等等,直到OP的失败方法:__declspec(implementation_key(190)) _bstr_t IHTMLElement::GettagName ();//不断地,直到:__declspec(implementation_key(51466)) ISVGElementInstancePtr ISVGUseElement::GetanimatedInstanceRoot ();__declspec(implementation_key(51467)) long ISVGElementInstanceList::Getlength();__declspec(implementation_key(51468)) ISVGElementInstancePtr ISVGElementInstanceList::item(长索引);#pragma stop_map_region

这里是 mshtml.tli 文件中的相应部分,它是由 mshtml.tlh 标头 #include 的:

#pragma implementation_key(190)内联 _bstr_t MSHTML::IHTMLElement::GettagName () {BSTR _result = 0;HRESULT _hr = get_tagName(&_result);if (FAILED(_hr)) _com_issue_errorex(_hr, this, __uuidof(this));返回 _bstr_t(_result, false);}

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

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

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

#import 语句的 no_function_mapping 限定符告诉代码生成器不要在 start_map_regionstop_map_regionimplementation_key 中添加 pragmas,你会得到常规"代码,它识别模板替换.

如果 OP 为他们的测试选择了一个较小的类型库,那么这个特性"可能并不明显,因为可能没有使用 implementation_key 编译指示.

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

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

After #importing a .tlb file, I tried to use one of the generated _com_ptr_t objects in a function template. This works fine if the functions I use are also used outside of a function template. However if it's only used in a function template, I get a link error.

Can anyone explain why this happens? I'm guessing it's to do with template instantiation but I'm not sure how the #import mechanism works. Is it the expected behaviour?

Here's a minimal example which includes all combinations according to the value of COMPILE_OPTION.

  1. Define only the regular function that uses the object (causing it to be instantiated)
  2. Define only the function template (which doesn't cause the used function to be instantiated) - This is the problem case
  3. Define both, this shows that if the object is used in a regular function the template option works as expected (and not as in case #2)

Code:

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

Now the following compile successfully:

cl Source.cpp /DCOMPILE_OPTION=1
cl Source.cpp /DCOMPILE_OPTION=3

But this one:

cl Source.cpp /DCOMPILE_OPTION=2

Produces the following (relevant error in bold):

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@@$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 : fatal error LNK1120: 1 unresolved externals

Notes:

  1. A workaround is to use the raw COM methods
  2. This is obviously a simplified version, the reason I'm using a template is that I'm using a library that has several types with common functionality that don't share a common base interface.

解决方案

As per the comments, I unearthed this archived KB Article which talks about the #pragma implementation_key compiler directive, which is used to improve performance when a type library has a lot of methods (over 1000 it seems), which mshtml.tlb does. This directive appears in the generated mshtml.tlh and .tli files. The article also talks about 'side-effects' which is perhaps what is happening here.

If you add the (undocumented!) 'no_function_mapping' modifier to the #import:

#import <mshtml.tlb> no_function_mapping

these #pragmas are removed and the code compiles and links.

[EDIT] A bit more explanation / guesswork. It seems MS have introduced the implementation_key pragma to try to improve the performance (somehow) of importing/imported type libraries: in cases where the caller might only use 2 functions out of 10,000's.

This is the section of the generated mshtml.tlh header that defines all the individual methods of the type library, all 51,468 of them, surrounded by the start_map_region and stop_map_region pragmas. The failing method GettagName() is number 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

And here is the corresponding part in the mshtml.tli file which is #include'd by the mshtml.tlh header:

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

The KB article refers to side effects of this mapping process. My guess is that when working with this pragmas, the MS compiler can detect explicit calls, eg:

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

but doesn't recognize the indirect use in the OP's template function.

The no_function_mapping qualifier to the #import statement tells the code generator not to add in the start_map_region, stop_map_region, and implementation_key pragmas, and you get 'regular' code, which recognizes the template substitution.

If the OP had chosen a smaller type library for their test, then this 'feature' may not have been apparent, as the implementation_key pragma might not have been used.

My minimal tests do not show any size change in the compiled executable, with the no_function_mapping modifier.

What the performance improvement might be is unclear. It might be a smaller memory footprint at run-time, as the dual interface doesn't have to keep a track of the dispId's that won't be called. My understanding is a bit vague, but using IDispatch::Invoke() requires determining which dispId will be invoked for a given function name, using some kind of map created after calling IDispatch::GetIdsOfNames(). This map would get pretty large for 50k+ methods, so perhaps the implementation_key approach is a way to reduce the size of the map (and hence the lookup time) to contain only those methods that will actually be used?

这篇关于仅在模板中使用 _com_ptr_t(由 #import 自动创建)时出现链接错误的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆