使用SafeArrayGetElement正确访问VT_UNKNOWN的SafeArray

时间:2009-10-22 18:11:34

标签: c# com interop safearray

我们有一个COM组件,其实现和接口定义存在于托管代码中,但由本机组件驱动。托管组件通过以下方法声明将SafeArray返回到本机代码。

interface IExample {
  <return: MarshalAs(UnmanagedType.SafeArray, SafeArraySubType=VarEnum.VT_UNKNOWN)>
  object[] DoSomeOperation()
}

生成的本机签名正确地将其作为SafeArray传回。

在代码审查期间,我们提出了一些关于使用SafeArrayGetElement调用结果数组的问题。问题是SafeArrayGetElement是否返回一个AddRef'd的IUnknown实例。基本上它归结为以下哪一个是正确的

示例1:

CComPtr<IUnknown> spUnk;
hr = SafeArrayGetElement(pArray, &bounds, reinterpret_cast<void**>(&spUnk));

示例2:

IUnknown* pUnk;
hr = SafeArrayGetElement(pArray, &bounds, reinterpret_cast<void**>(&pUnk));

关于这个主题的文档很薄。它只包括以下行。

  

如果数据元素是字符串,对象或变体,则该函数以正确的方式复制元素。

正确的定义有点含糊不清。

2 个答案:

答案 0 :(得分:2)

第一种方法应该是正确的,并且与整个COM中对象的处理一致,可能是你发现的定义假设消费者知道正确的方法。

提到的其他项目需要它。复制VARIANT或SAFEARRAY时,如果包含对象,则会带有隐式AddRef()。但是,当VT_BYREF存在时,VARIANT不需要它。

VariantCopy @ MSDN
SafeArrayCopy @ MSDN

此行为不是SAFEARRAYs或VARIANTs固有的,因为它是处理COM中参数的规则的一部分。但是,没有任何东西阻止某人试图规避规则。

对于输入参数,除非他们打算保留接口指针以供进一步使用,否则被调用者不负责AddRef()。但是,参数使用的其他情况需要它。

例如,置于VARIANT或其他容器中的接口应至少应用一个AddRef()调用,否则当使用VARIANT作为COM方法的输出参数时会产生问题,因为数据/引用的传输是单向的。原始对象可能在呼叫到达目的地时到期。同样,将接口封送到Stream中也需要AddRef()。

同样,通过引用调用也需要至少一个AddRef调用。如果不是这种情况,那么任何合适的长时间运行的呼叫(例如,通过DCOM)可能无法到达其目的地,并保证所引用的对象仍然存活。但是,此处经常会跳过额外的AddRef()/ Release()调用,因为在调用范围内或之前,对象应该已经为1+。

如果可以修改组件,并且您的呼叫正在进行中,则可能需要使用GIT。这允许您传递令牌,并且更容易编组COM公寓中的接口。在调用期间,所涉及对象的生命周期成为调用者的责任,并且您将能够捕获无法封送对象的情况。

Creating the Global Interface Table @ MSDN

同样有趣的是BSTR的脚注。

  

如果采用BSTR引用参数的函数的实现为参数分配了新的BSTR,它必须释放先前引用的BSTR。

String Manipulation Functions (COM)

答案 1 :(得分:1)

它应该是AddRef:ed,但我没有第一手资料(例如我没有阅读过来源)。

我认为文档非常清楚 - 正确地复制接口指针是AddRef:它。

如果你想确定,建立一个实现IUnknown的超简单ATL COM对象,将其中的一些填充到SAFEARRAY并在CComObjectBase<>::InternalAddRef中放置一个断点(如果我的记忆服务)。然后调试对SafeArrayGetElement的调用,看看你的断点是否被击中。