将 BYTE 的 VARIANT 的 SAFEARRAY 编组到 C#
Marshaling a SAFEARRAY of VARIANTs that are BYTEs to C#
我创建了一个 SAFEARRAY
存储 VARIANT
s,在 C++ 中是 BYTE
s。
当这个结构被编组到 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++ 重现代码(此函数是从本机 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
操作。
[Out, MarshalAs(UnmanagedType.SafeArray,
SafeArraySubType = VarEnum.VT_VARIANT)]
out byte[] result);
你撒谎了。你告诉编组器你想要一个变体数组,确实与 C++ 编译器生成的兼容。它将尽职地产生一个对象[],对象是 VARIANT 的标准封送处理。数组的元素是 boxed 字节。
这并没有骗过调试器,它忽略了程序声明并查看了数组类型并发现了 object[],在您的屏幕截图中很容易看到。所以它正确地访问了装箱的字节。它并没有骗过 Array.CopyTo(),它接受一个数组参数,因此被迫查看元素类型。并正确地将装箱字节转换为字节,它知道该怎么做。
但 C# 编译器被骗得很惨。它不知道它需要发出拆箱指令。实际上不确定出了什么问题,您可能正在获取对象地址的低字节。确实很随机:)
在 pinvoke 声明中撒谎可能非常有用。如果数组包含实际对象,如字符串、数组或接口指针,则在这种特殊情况下工作得很好。编组员不尖叫血腥谋杀的可能原因。不在这里,拳击把它绊倒了。您必须修复声明,使用 object[]
.
我创建了一个 SAFEARRAY
存储 VARIANT
s,在 C++ 中是 BYTE
s。
当这个结构被编组到 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++ 重现代码(此函数是从本机 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
操作。
[Out, MarshalAs(UnmanagedType.SafeArray,
SafeArraySubType = VarEnum.VT_VARIANT)]
out byte[] result);
你撒谎了。你告诉编组器你想要一个变体数组,确实与 C++ 编译器生成的兼容。它将尽职地产生一个对象[],对象是 VARIANT 的标准封送处理。数组的元素是 boxed 字节。
这并没有骗过调试器,它忽略了程序声明并查看了数组类型并发现了 object[],在您的屏幕截图中很容易看到。所以它正确地访问了装箱的字节。它并没有骗过 Array.CopyTo(),它接受一个数组参数,因此被迫查看元素类型。并正确地将装箱字节转换为字节,它知道该怎么做。
但 C# 编译器被骗得很惨。它不知道它需要发出拆箱指令。实际上不确定出了什么问题,您可能正在获取对象地址的低字节。确实很随机:)
在 pinvoke 声明中撒谎可能非常有用。如果数组包含实际对象,如字符串、数组或接口指针,则在这种特殊情况下工作得很好。编组员不尖叫血腥谋杀的可能原因。不在这里,拳击把它绊倒了。您必须修复声明,使用 object[]
.