调用外部 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 标记你的编组类型,你只是在示例中省略了它们。
有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 标记你的编组类型,你只是在示例中省略了它们。