【问题标题】:Can I call VariantChangeType on an input BSTR VARIANT?我可以在输入 BSTR VARIANT 上调用 VariantChangeType 吗?
【发布时间】:2018-05-17 06:20:31
【问题描述】:

我有一个用 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,它归客户端所有,应该由客户端释放。

【问题讨论】:

  • 不,VariantChangeType() 将结果存储到另一个变体中。您作为第一个参数传递的那个。因此,完全没有理由释放 BSTR。除非你对结果变量做了一些不明智的事情,所以它不是空的,比如使用 VariantCopy :)
  • @HansPassant VariantChangeType() 的第一个参数可以与第二个参数相同,这意味着将就地转换变体。那么,也许更好的说法是,我应该就地转换还是转换为第二个变体?我已经更新了问题以澄清。
  • FWIW,我创建了一个 Gist 来演示这个问题:gist.github.com/Neuroboy23/efb3d45783faf03bd87ee2a69519ce84

标签: c++ com variant bstr


【解决方案1】:

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

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

使用VariantChangeType(就地)或VariantClear将触发SysFreeString,这意味着原始变体(由调用者拥有)仍然包含BSTR的地址,但该地址不再包含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 可能会导致错误。

补充阅读

【讨论】:

  • 这里重要的是 /*[in]*/ 注释。这意味着 idl 拥有它,因此传入的缓冲区不是你的。它对被调用者是只读的。所以你不能改变那个缓冲区。就是这样。
【解决方案2】:

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

我在我的所有 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中找到了语句。

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

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

【讨论】:

  • 不幸的是,这是一个危险的答案。在您提供的示例中,它是安全的,因为您从方法调用接收VARIANT - 一旦该方法调用完成,VARIANT 的所有者及其包含的BSTR 就是调用者,这很好如果调用者想要转换它。但是 OP 的问题是关于 VARIANT 及其包含的 BSTR 被传递到方法中的情况。在这种情况下,更改类型是不安全的,因为您(被调用者)将释放调用者拥有的东西(稍后将尝试再次释放它)。
  • MSDN 明确指出“当释放或更改具有 VT_BSTR 类型的变体类型时,会在包含的字符串上调用 SysFreeString。”示例中的被调用者 (myfuncbad) 很糟糕,因为它正在释放调用者所拥有的东西。
  • 被调用者不是 OP 中的所有者。在方法签名中,参数是[in] VARIANT。在 C++ 中,这是 VARIANT,而不是 VARIANT*。调用该方法时,VARIANT 结构将被复制(复制的是BSTR 指针,而不是BSTR 本身)。这意味着当被调用者使用VariantChangeType 时,被引用的BSTR 被释放。但是,调用者是所有者,它不会知道被调用者释放了BSTR(因为调用者的结构没有被被调用者的VariantChangeType 调用修改)。然后调用者将释放它的VARIANT,它会崩溃。
  • 我说的是原始问题中提供的代码。最初的问题不是询问使用具有相同前两个参数的VariantChangeType 通常是否安全(它不是——它仅在某些情况下是安全的)。该问题询问特定场景中的安全性 - 在采用[in] VARIANT 的被调用者中调用它。您正在为不同的场景提供答案。我已经编写了必要的代码来演示这个问题。 gist.github.com/Neuroboy23/efb3d45783faf03bd87ee2a69519ce84
  • @MichaelGunter - 你应该自己回答:-)
【解决方案3】:

复制一份会更安全,但是当我阅读“组件对象模型的规则”(https://msdn.microsoft.com/en-us/library/ms810016.aspx) 时:

•以下规则适用于接口成员函数的参数,包括返回值,它们不是“按值”传递的: ◦对于in参数,调用者应该分配和释放内存。

您正在谈论的情况是按值传递(无论 VARIANT 碰巧包含此调用的 BSTR)。所以我相信在这种情况下被调用者拥有参数,如果它想确保其值的持续生存能力,则由调用者制作副本。

【讨论】:

  • BSTR 是由 SysAllocString 分配的指针。因此,它是一个参考,即使它不是 VT_BYREF。因此,从技术上讲,它不是“按值”传递的。
  • 我和@jveazey 一起做这个。调用者总是会尝试释放它的VARIANT,如果任何包含的内存已经被释放,那将是一个崩溃。
猜你喜欢
  • 1970-01-01
  • 2010-09-20
  • 2019-10-24
  • 1970-01-01
  • 1970-01-01
  • 2012-07-23
  • 2021-07-05
  • 1970-01-01
  • 2011-07-12
相关资源
最近更新 更多