编组SAFEARRAY的VARIANT,这是BYTE到C#

时间:2016-11-04 20:26:37

标签: c# c++ byte com-interop safearray

我在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数组的内容,我会得到正确的数字,如下面的屏幕截图所示:

VS debugger shows the correct numbers

但是,如果我 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操作。

1 个答案:

答案 0 :(得分:3)

[Out, MarshalAs(UnmanagedType.SafeArray, 
                SafeArraySubType = VarEnum.VT_VARIANT)]
out byte[] result);
你压扁了。你告诉marshaller你想要一系列变体,确实与C ++编译器生成的变量兼容。它将尽职尽责地生成一个对象[],对象是VARIANT的标准编组。数组的元素是盒装字节。

这并没有欺骗调试器,它忽略了程序声明并查看了数组类型并发现了对象[],在屏幕截图中很容易看到。所以它正确访问了盒装字节。并且它没有欺骗Array.CopyTo(),它需要一个Array参数,因此被迫查看元素类型。并且将盒装字节正确转换为字节,它知道如何做到这一点。

但是C#编译器被愚弄了。它不知道它需要发出一个unbox指令。实际上不确定出了什么问题,你可能得到了对象地址的低字节。确实非常随意:)

pinvoke声明中的Fibbing非常有用。如果数组包含实际对象(如字符串,数组或接口指针),则在此特定情况下工作正常。编组人员不会尖叫血腥谋杀的可能原因。不在这里,拳击绊倒了。您必须修改声明,使用object[]