调用外部 C++ 方法时无法识别字节数组

Byte array is not recognised when calling external C++ method

有C++ api:

typedef struct
{
    BYTE    bCommandCode;
    BYTE    bParameterCode;

    struct
    {
        DWORD   dwSize;
        LPBYTE  lpbBody;
    }
    Data;
}
COMMAND;

还有一个函数:

DLL_API DWORD WINAPI ExecuteCommand( LPCSTR, CONST COMMAND, CONST DWORD, LPREPLY);

我的 C# 等效代码:

public struct Data
{
    public int dwSize;
    public byte[] lpbBody;
}

public struct Command
{
    public byte bCommandCode;
    public byte bParameterCode;

    public Data Data;
}

[DllImport(@"api.dll", CallingConvention = CallingConvention.Winapi)]
public static extern int ExecuteCommand(string port, Command command, int timeout, ref Reply reply);

这里不需要回复结构。

我调用 ExecuteCommand:

Command command = new Command();
command.bCommandCode = 0x10;
command.bParameterCode = 0x10;

byte[] bData = { 0xff, 0xff };
command.Data.dwSize = bData.Length;
command.Data.lpbBody = bData;

Reply reply = new Reply();
var result = ExecuteCommand("COM1", command, 5000, ref reply);

当我看到来自 C++ dll 的日志时,我发现 byte[] bData 根本没有被正确识别。我做错了什么?也许这个定义不正确:public byte[] lpbBody?如何将结构中的数组作为 LPBYTE 传递给 C++ 方法?

当您分配一个托管对象(例如您遇到问题的字节数组)时,它会映射到托管堆中的某个地址,而该地址又会映射到某个非托管内存地址。当 GC 运行时,托管地址和非托管地址之间的映射可能会发生变化,因为它通过移动非托管内存块来整理分配给它的非托管内存 space 的碎片。

当您使用 byte[] 作为引用调用非托管 API 时,编组过程基本上将字节数组对象的非托管地址传递给本机 API。因此,由于上述碎片整理,字节数组的内存地址很可能不再指向您尝试使用它时所期望的地址。

我真诚地相信这就是您遇到的情况。
幸运的是,这个问题很容易解决:

GCHandle pinned = GCHandle.Alloc(bData, GCHandleType.Pinned);
IntPtr arrPtr = pinned.AddrOfPinnedObject();

第一行告诉 GC 不要 fiddle 使用此对象的托管 -> 非托管映射。 第二个不言自明。 您现在要做的就是更改 C# 端的 'Data' 结构以保存 IntPtr 而不是 byte[]
(无需更改 C++ 端)。

public struct Data
{
    public int dwSize;
    public IntPtr lpbBody;
}

确保在完成 GCHandle 对象后调用 GCHandle.Free() 方法。

我希望你用 MarshalAsAttribute class 标记你的编组类型,你只是在示例中省略了它们。