调用外部C ++方法

时间:2016-09-23 08:40:42

标签: c# c++ pinvoke marshalling

有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的日志时,我看到by byte []根本没有正确识别bData。我做错了什么?也许这个定义不正确:public byte [] lpbBody?如何将结构中的数组作为LPBYTE传递给C ++方法?

1 个答案:

答案 0 :(得分:1)

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

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

我真诚地相信这正是您所经历的。
幸运的是,这个问题很容易解决:

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

第一行告诉GC不要管理Managed - >此对象的非托管映射。  第二个说明了一切。 您现在要做的就是更改数据'在C#端的struct来保存IntPtr而不是byte []
(不需要改变C ++方面)。

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

完成GCHandle对象后,请务必调用GCHandle.Free()方法。

我希望您使用MarshalAsAttribute类标记您的编组类型,并在示例中省略它们。