我可以在输入BSTR变体上调用variantChangeType吗?

Can I call VariantChangeType on an input BSTR VARIANT?

本文关键字:调用 variantChangeType 输入 BSTR 我可以      更新时间:2023-10-16

i具有使用以下签名的方法编写的com对象。假设该变体包含一个BSTR(仅VT_BSTR,而不是VT_BYREF | VT_BSTR)。

HRESULT myfunc(/*[in]*/ VARIANT param)

我想将类型更改为其他东西。如果VariantChangeType的第一个参数与第二个参数相同,则"变体将被转换到位"

所以,我可以转换到位吗?

HRESULT myfunc(/*[in]*/ VARIANT param)
{
    VariantChangeType(&param, param, 0, VT_I4);
}

还是我应该复制到第二个变体?

HRESULT myfunc(/*[in]*/ VARIANT param)
{
    VARIANT temp;
    VariantInit(&temp);
    VariantChangeType(&temp, param, 0, VT_I4);
}

我的理解是后者是必需的,因为前者将释放bstr,该BSTR由客户拥有,应由客户释放。

使用VariantChangeType与第二个变体需要使用,尽管可能并不明显。

即使变体是按值传递的,但在变体点中包含的任何指针都包含到同一内存地址。由于BSTR是指针,这意味着BSTR的原始地址已传递到函数中,就像参数为BSTR而不是变体一样。

使用VariantChangeType(现场)或VariantClear将触发SysFreeString,这意味着原始变体(呼叫者拥有)仍然包含BSTR的地址,但该地址不再保留BSTR。

来自"变体操纵函数"文档...

使用VT_BSTR类型释放或更改变体的类型时,在包含的字符串上调用sysfreestring。

这并不明显的原因是该代码似乎可以起作用,即使我上面描述的所有内容都不应该。

#ifndef WIN32_LEAN_AND_MEAN
#define WIN32_LEAN_AND_MEAN
#endif // !WIN32_LEAN_AND_MEAN
#include <Windows.h>
#include <OleAuto.h>
HRESULT myfuncbad(/*[in]*/ VARIANT param)
{
    // In-place conversion
    VariantChangeType(&param, &param, 0, VT_I4);
    return S_OK;
}
HRESULT myfuncgood(/*[in]*/ VARIANT param)
{
    VARIANT temp;
    VariantInit(&temp);
    // Copy and convert into a new VARIANT
    VariantChangeType(&temp, &param, 0, VT_I4);
    VariantClear(&temp);
    return S_OK;
}
int main()
{
    VARIANT input;
    VariantInit(&input);
    V_BSTR(&input) = SysAllocString(L"1");
    V_VT(&input) = VT_BSTR;
    myfuncgood(input);
    wprintf(L"Memory location of BSTR = 0x%xn", (unsigned)(V_BSTR(&input)));
    wprintf(L"Contents of BSTR = %sn", V_BSTR(&input));
    myfuncbad(input);
    wprintf(L"Memory location of BSTR = 0x%xn", (unsigned)(V_BSTR(&input)));
    wprintf(L"Contents of BSTR = %sn", V_BSTR(&input));
}

此代码运行并输出类似以下

Memory location of BSTR = 0x2d1af0c
Contents of BSTR = 1
Memory location of BSTR = 0x2d1af0c
Contents of BSTR = 1

但是为什么?事实证明,BSTR分配是缓存的。因此,即使调用了VariantChangeType(就地)或VariantClear,BSTR分配也可能持续一段时间。在传递到这些函数的变体上,这将是很明显的,但是该变体的任何"副价值"副本都可能仍然在一段时间内看到BSTR。

无论如何,从技术上讲,BSTR已被myfuncbad释放,不应再由呼叫者引用。另外,在原始变体上调用VariantClear可能会导致错误。

其他阅读

  • 为什么无论如何都有BSTR缓存?
  • 缓存很好,但是它们会混淆内存泄漏检测工具
  • Eric的BSTR语义指南
  • 扩展视觉基本 - 终极数据类型

保存的内置转换。您不需要时间变体。

我在所有ATL COM代码中都使用它:

CComVariant v;
GetSomeData(v);  // Assume v returns a VT_BSTR variant.
HRESULT hr = v.ChangeType(VT_I4);
if (FAILED(hr))
    ...

此代码以讨论的方式转化为内置转换。在内部,在旧的VARIANT BSTR使用计数值降低之前,将用VarI4FromBSTR计算结果。

我在某些调试会话中对此进行了验证,因为我也不确定。

编辑最后,我在MSDN中找到了确认此的语句。

对于VT_BSTR,该字符串只有一个所有者。所有字符串 必须将变体分配给Sysallocstring函数。什么时候 使用VT_BSTR类型释放或更改变体的类型, sysfreestring在包含的字符串上被调用。

代码在@jveazey的答案中起作用,它与BSTR缓存无关。有一个真正的位置转换!

制作副本会更安全,但是当我阅读"组件对象模型的规则"时(https://msdn.microsoft.com/en-en-us/library/MS810016.aspx):

•以下规则适用于接口成员函数的参数,包括返回值,这些函数未传递"副价值":◦对于参数,呼叫者应分配并释放内存。

您正在谈论的情况是逐个价值(不管发生了该呼叫的BSTR的变体如何)。因此,我相信Callee在这种情况下拥有参数,如果要确保其价值的持续可行性,则由呼叫者制作副本。