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

时间:2017-12-03 19:20:53

标签: c++ com variant bstr

我有一个用C ++编写的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由客户拥有并且应该被客户释放。

3 个答案:

答案 0 :(得分:2)

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

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

使用VariantChangeType(就地)或VariantClear会触发SysFreeString,这意味着原始变体(由来电者拥有)仍然包含BSTR的地址,但地址为no更长时间持有BSTR。

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

  

当使用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%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分配也可能会暂停一段时间。对于传递给这些函数的变体,它将立即显而易见,但变体的任何“按值”副本仍可能在一段时间内看到BSTR。

无论如何,BSTR在技术上已被myfuncbad释放,并且不应再被调用者引用。此外,在原始版本上调用VariantClear可能会导致错误。

补充阅读

答案 1 :(得分:-1)

制作副本会更安全,但正如我读到&#34;组件对象模型的规则&#34; (https://msdn.microsoft.com/en-us/library/ms810016.aspx):

•以下规则适用于接口成员函数的参数,包括未返回的返回值&#34;按值&#34;: ◦对于in参数,调用者应分配并释放内存。

您正在谈论的案例是值传递(无论VARIANT是否包含此次调用的BSTR)。所以我认为在这种情况下被调用者拥有参数,如果要确保其值的持续可行性,则由调用者来制作副本。

答案 2 :(得分:-1)

就地转换是保存。你不需要时间变体。

我在我的所有ATL COM代码中都使用它,如下所示:

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

此代码以讨论的方式转换为就地转换。 在内部,在旧的VarI4FromBSTR VARIANT使用计数值递减之前,使用BSTR计算结果。

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

编辑最后我在MSDN that confirms this找到了该声明。

  

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

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