我有一个用C ++编写的COM对象,其方法使用以下签名。假设变体包含BSTR(只是VT_BSTR,而不是VT_BYREF | VT_BSTR)。
HRESULT myfunc(/*[in]*/ VARIANT param)
我想将类型更改为其他内容。如果VariantChangeType的第一个参数与第二个参数相同,则“变体将在适当的位置转换。”
那么,我可以转换到位吗?
HRESULT myfunc(/*[in]*/ VARIANT param)
{
VariantChangeType(¶m, param, 0, VT_I4);
}
或者我应该复制到第二个版本?
HRESULT myfunc(/*[in]*/ VARIANT param)
{
VARIANT temp;
VariantInit(&temp);
VariantChangeType(&temp, param, 0, VT_I4);
}
我的理解是后者是必需的,因为前者会释放BSTR,BSTR由客户拥有并且应该被客户释放。
答案 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(¶m, ¶m, 0, VT_I4);
return S_OK;
}
HRESULT myfuncgood(/*[in]*/ VARIANT param)
{
VARIANT temp;
VariantInit(&temp);
// Copy and convert into a new VARIANT
VariantChangeType(&temp, ¶m, 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缓存无关。有一个真正的现场转换!