问题 如何将本地CComSafeArray返回到LPSAFEARRAY输出参数?


我有一个COM函数应该通过a返回一个SafeArray LPSAFEARRAY* 输出参数。 该函数使用ATL创建SafeArray CComSafeArray 模板类。 我天真的实现使用 CComSafeArray<T>::Detach() 为了将所有权从局部变量移动到输出参数:

void foo(LPSAFEARRAY* psa)
{
    CComSafeArray<VARIANT> ret;
    ret.Add(CComVariant(42));
    *psa = ret.Detach();
}

int main()
{
    CComSafeArray<VARIANT> sa;
    foo(sa.GetSafeArrayPtr());

    std::cout << sa[0].lVal << std::endl;
}

问题是 CComSafeArray::Detach() 执行 Unlock 操作,以便当SafeArray的新所有者(主要的 sa 在这种情况下)被破坏,锁不是零和 Destroy 无法解锁SafeArray E_UNEXPECTED (这会导致内存泄漏,因为SafeArray未被释放)。

通过COM方法边界将所有权转移到CComSafeArrays的正确方法是什么?


编辑: 从单一答案到目前为止,似乎错误是在客户端(main)而不是从服务器端(foo),但我觉得很难相信 CComSafeArray 不是为这个琐碎的用例而设计的,必须有一种优雅的方法来将一个COM方法中的SafeArray变成一个 CComSafeArray


12048
2017-11-22 11:43


起源

您使用的是哪个版本的Visual Studio? - Phil Booth
对于VS8(2005)和VS9(2008)都会发生这种情况 - Motti
根据我的经验,我相信设计CComSafeArray的人从未真正使用它。如果需要,您可以使用自己的包装器类。 - Amnon


答案:


问题是你设置了接收 CComSafeArray的内部指针直接。 使用 Attach() 附加现有的方法 SAFEARRAY 到了 CComSafeArray

LPSAFEARRAY ar;
foo(&ar);
CComSafeArray<VARIANT> sa;
sa.Attach(ar);

10
2017-11-22 12:21



当然这不是道路 CComSafeArray 应该被使用,它违背了 CComVariant 和 CComBSTR。 - Motti
正如您在代码中看到的那样,CComSafeArray期望SAFEARRAY被锁定。你必须以某种方式锁定它。 - Amnon
并且没有类似Attach的功能可以锁定,也没有类似Detach的功能,因此无需解锁 - 因此必须在呼叫者或被叫方方面完成工作。 - Georg Fritzsche


只是为了确认标记的答案是正确的答案。 RAII包装器不能跨COM边界工作。

发布的方法实现不正确,您不能假设调用者将提供有效的SAFEARRAY。只是[out]不是Automation中的有效属性,它必须是[out,retval]或[in,out]。如果它是[out,retval],这就是它的样子,那么该方法必须从头开始创建一个新数组。如果它是[in,out],则该方法必须销毁传入的数组,如果它与预期的数组类型不匹配并创建一个新数组。


5
2017-08-06 03:35





我想在哪里无意允许这样的用例。可能不是同一个开发者写的 CComVariant & CComPtr :)

我相信 CComSafeArray作者认为价值语义是主要目标;附加/分离可能只是一个“奖励”功能。


1
2017-08-04 22:43



即使有这个理由,我仍然觉得 CComSafeArray默认的ctor和 GetSafeArrayPtr 是设计缺陷/解决方法...... - Andrey