我可以在输入 BSTR VARIANT 上调用 VariantChangeType 吗?

Can I call VariantChangeType on an input BSTR VARIANT?

我有一个用 C++ 编写的 COM 对象,其方法使用以下签名。假设变体包含一个 BSTR(只是 VT_BSTR,而不是 VT_BYREF | VT_BSTR)。

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

我想将类型更改为其他类型。如果 VariantChangeType 的第一个参数与第二个参数相同,则 "variant will be converted in place."

那么,我可以原地转换吗?

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 归客户所有,应该由客户释放。

复制一份会更安全,但据我阅读"The Rules of the Component Object Model" (https://msdn.microsoft.com/en-us/library/ms810016.aspx):

•以下规则适用于接口成员函数的参数,包括未传递给 "by-value" 的 return 值: ◦对于in参数,调用者应该分配和释放内存。

您所说的情况是按值传递(无论 VARIANT 恰好包含此调用的 BSTR)。所以我相信在这种情况下被调用者拥有参数,如果调用者想要确保其值的持续可行性,则由调用者来制作副本。

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

即使变体是按值传递的,变体中包含的任何指针都指向相同的内存地址。由于 BSTR 是一个指针,这意味着将 BSTR 的原始地址传递给函数,就像参数是 BSTR 而不是 VARIANT 一样。

使用 VariantChangeType(就地)或 VariantClear will trigger SysFreeString,这意味着原始变体(由调用者拥有)仍然包含 BSTR 的地址,但该地址不再包含 BSTR。

来自 "Variant Manipulation Functions" 文档...

When releasing or changing the type of a variant with the VT_BSTR type, SysFreeString is called on the contained string.

这不是很明显的原因是这段代码似乎有效,尽管我上面描述的一切都说它不应该。

#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%x\n", (unsigned)(V_BSTR(&input)));
    wprintf(L"Contents of BSTR = %s\n", V_BSTR(&input));

    myfuncbad(input);
    wprintf(L"Memory location of BSTR = 0x%x\n", (unsigned)(V_BSTR(&input)));
    wprintf(L"Contents of BSTR = %s\n", V_BSTR(&input));
}

此代码运行并输出如下内容

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

但是为什么呢?结果是BSTR allocations are cached。因此,即使调用 VariantChangeType(就地)或 VariantClear,BSTR 分配也可能会持续一段时间。传递给这些函数的变体会立即显而易见,但变体的任何 "by-value" 副本可能仍会在一段时间内看到 BSTR。

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

补充阅读

就地转换已保存。您不需要时间变量。

我在我的所有 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 计算结果。

我在一些调试会话中验证了这一点,因为我对此也不确定。

EDIT 最后我在 MSDN that confirms this.

中找到了语句

For VT_BSTR, there is only one owner for the string. All strings in variants must be allocated with the SysAllocString function. When releasing or changing the type of a variant with the VT_BSTR type, SysFreeString is called on the contained string.

代码在@jveazey 的答案中有效它与 BSTR 缓存无关。真正的就地转换!