调用外部 C++ 方法时无法识别字节数组
Posted
技术标签:
【中文标题】调用外部 C++ 方法时无法识别字节数组【英文标题】:Byte array is not recognised when calling external C++ method 【发布时间】:2016-09-23 08:40:41 【问题描述】:有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[] lpbody?如何将结构中的数组作为 LPBYTE 传递给 C++ 方法?
【问题讨论】:
【参考方案1】:当您分配一个托管对象(例如您遇到问题的字节数组)时,它会映射到托管堆中的某个地址,而该地址又映射到某个非托管内存地址。当 GC 运行时,托管地址和非托管地址之间的映射可能会发生变化,因为它通过移动非托管内存块来对分配给它的非托管内存空间进行碎片整理。
当您使用 byte[] 作为引用调用非托管 API 时,编组过程基本上将字节数组对象的非托管地址传递给本机 API。因此,由于前面提到的碎片整理,字节数组的内存地址很可能不再指向您在尝试使用它时所期望的地址。
我真诚地相信这就是您所遇到的。幸运的是,这个问题可以很容易地解决:
GCHandle pinned = GCHandle.Alloc(bData, GCHandleType.Pinned);
IntPtr arrPtr = pinned.AddrOfPinnedObject();
第一行告诉 GC 不要摆弄这个对象的 Managed -> Unmanaged 映射。 第二个不言自明。 您现在要做的就是更改 C# 端的“数据”结构以保存 IntPtr 而不是 byte[](无需更改 C++ 端)。
public struct Data
public int dwSize;
public IntPtr lpbBody;
确保在使用 GCHandle 对象后调用GCHandle.Free() 方法。
我确实希望您使用 MarshalAsAttribute 类标记您的封送类型,而您只是在示例中省略了它们。
【讨论】:
不,我没有使用 MarshalAsAttribute。我应该在哪里使用它? 类/结构和字段的顶部。互联网伙伴周围有大量教程。我建议您阅读有关 Marshalling 及其在 .NET 中的工作原理的内容。 为什么这些特定类型需要任何MarshalAs
属性?以上是关于调用外部 C++ 方法时无法识别字节数组的主要内容,如果未能解决你的问题,请参考以下文章