我在C ++中创建SAFEARRAY
VARIANT
个存储BYTE
。
当这个结构被编组到C#时,会发生奇怪的事情。
如果我在C#中将此结构的内容打印到WinForms ListBox
,例如:
byte data[]
TestSafeArray(out data);
lstOutput.Items.Clear();
foreach (byte x in data)
{
lstOutput.Items.Add(x); // Strange numbers
}
我得到一些与原始数字无关的数字。而且,每次我运行C#客户端进行新测试时,我都会得到一组不同的数字。
请注意,如果我使用Visual Studio调试器检查该data
数组的内容,我会得到正确的数字,如下面的屏幕截图所示:
但是,如果我 CopyTo
将已编组的data
数组添加到新数组,我会得到正确的数字:
byte[] data;
TestSafeArray(out data);
// Copy to a new byte array
byte[] byteData = new byte[data.Length];
data.CopyTo(byteData, 0);
lstOutput.Items.Clear();
foreach (byte x in byteData)
{
lstOutput.Items.Add(x); // ** WORKS! **
}
这是我用来构建SAFEARRAY
的C ++ repro代码(此函数从本机DLL导出):
extern "C" HRESULT __stdcall TestSafeArray(/* [out] */ SAFEARRAY** ppsa)
{
HRESULT hr = S_OK;
try
{
const std::vector<BYTE> v{ 11, 22, 33, 44 };
const int count = static_cast<int>(v.size());
CComSafeArray<VARIANT> sa(count);
for (int i = 0; i < count; i++)
{
CComVariant var(v[i]);
hr = sa.SetAt(i, var);
if (FAILED(hr))
{
return hr;
}
}
*ppsa = sa.Detach();
}
catch (const CAtlException& e)
{
hr = e;
}
return hr;
}
这是我使用的C#P / Invoke:
[DllImport("NativeDll.dll", PreserveSig = false)]
private static extern void TestSafeArray(
[Out, MarshalAs(UnmanagedType.SafeArray,
SafeArraySubType = VarEnum.VT_VARIANT)]
out byte[] result);
请注意,如果在C ++中我创建SAFEARRAY
直接存储BYTE
s (而不是SAFEARRAY(VARIANT)
),我会立即在C#中获得正确的值,没有中间CopyTo
操作。
答案 0 :(得分:3)
[Out, MarshalAs(UnmanagedType.SafeArray,
SafeArraySubType = VarEnum.VT_VARIANT)]
out byte[] result);
你压扁了。你告诉marshaller你想要一系列变体,确实与C ++编译器生成的变量兼容。它将尽职尽责地生成一个对象[],对象是VARIANT的标准编组。数组的元素是盒装字节。
这并没有欺骗调试器,它忽略了程序声明并查看了数组类型并发现了对象[],在屏幕截图中很容易看到。所以它正确访问了盒装字节。并且它没有欺骗Array.CopyTo(),它需要一个Array参数,因此被迫查看元素类型。并且将盒装字节正确转换为字节,它知道如何做到这一点。
但是C#编译器被愚弄了。它不知道它需要发出一个unbox指令。实际上不确定出了什么问题,你可能得到了对象地址的低字节。确实非常随意:)
pinvoke声明中的Fibbing非常有用。如果数组包含实际对象(如字符串,数组或接口指针),则在此特定情况下工作正常。编组人员不会尖叫血腥谋杀的可能原因。不在这里,拳击绊倒了。您必须修改声明,使用object[]
。