来自C ++的Pinvoke结构转换

时间:2011-10-28 00:08:50

标签: c# pinvoke marshalling

以下是一些经验证有效的C ++:

typedef struct
{
  PVOID  buffer;
  UINT32 length;
} DATA_BUFFER;

typedef struct 
{
  DATA_BUFFER TxBuf [1];
  DATA_BUFFER RxBuf [1];
} JVM_COMM_BUFFER;

UINT32 SendAndRecv(
  IN    JHI_HANDLE        handle,
  IN    CHAR*             AppId,
  INOUT JVM_COMM_BUFFER* pComm
);

以下是我尝试将其移植到C#:

    [StructLayout(LayoutKind.Sequential)]
    public struct DATA_BUFFER
    {
        public byte[] buffer;
        public uint length;
    }

    [StructLayout(LayoutKind.Sequential)]
    public struct JVM_COMM_BUFFER
    {
        public DATA_BUFFER TxBuf;
        public DATA_BUFFER RxBuf;
    }

    [DllImport("jhi.dll", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Auto)]
    public static extern UInt32 SendAndRecv(IntPtr handle, string AppId, ref JVM_COMM_BUFFER pComm);

C#中没有从编组中抛出异常,但C ++和C#版本的结果并不相同。对我缺少的东西有任何想法吗?

2 个答案:

答案 0 :(得分:0)

问题可能在你的第一个结构中。 C ++定义包含一个指向数据缓冲区的指针,但Marshaller无法直接将其转换为.NET byte[](或者向另一个方向转换它可能会将byte[]转换为无效指针,因此你的非法参数错误)。相反,您可以通过两个步骤手动执行此操作:

[StructLayout(LayoutKind.Sequential)]
public struct DATA_BUFFER
{
    public IntPtr buffer;
    public uint length;
}

然后使用Marshal手动读取缓冲区(这只是我对Marshal API的记忆中的一个快速示例):

var txBufBytes = new byte[pComm.TxBuf.length];
Marshal.Copy(pComm.TxBuff.buffer, 0, pComm.TxBuf.length);

除此之外,正如@svick在评论中提到的,如果您的本机代码假设为非unicode / wide字符,则可能需要将CharSet设置为CharSet.Ansi

最后,第二个结构的C ++定义似乎定义了单元素数组,而这些数组实际上可能是指针,而不是内存中的结构。如果是这种情况,您可能必须替换您的定义以使用IntPtr s作为互操作,然后使用Marshal.PtrToStructure来获取实际结构。

您应该至少通过比较C ++中sizeof(...)的结果与C#中Marshal.SizeOf(...)的结果来比较结构大小是否相同。

鉴于您在评论中所说的内容,您应该能够使用我上面描述的DATA_BUFFER修改,以及您用于JVM_COMM_BUFFER的原始结构定义

[StructLayout(LayoutKind.Sequential)]
struct JVM_COMM_BUFFER
{
    public DATA_BUFFER TxBuf;
    public DATA_BUFFER RxBuf;
}

将此与您的DllImport

进行略微修改相结合
[DllImport("jhi.dll", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)]
public static extern UInt32 SendAndRecv(IntPtr handle, string AppId, [In, Out] ref JVM_COMM_BUFFER pComm);

CharSet.Ansi对于确保.NET字符串正确编组到ansi字符串非常重要(假设您的本机C函数不期望wchar_t字符串类型)。可能不需要[In, Out]属性,但可能会向marshaller提示如何正确控制该参数。

如果JVM_COMM_BUFFER确实是INOUT,并且您在调用函数之前使用数据预填充它,则可能需要确保数据全部有效。您正在调用的函数可能包含有关其参数所具有的值的文档。但是,此处的定义应根据您提供的C ++定义正确编组。

答案 1 :(得分:0)

问题是仅分配内存和分配固定对象之间的区别。一旦我将它切换到固定对象,那么除缓冲区外没有任何IntPtrs的签名工作正常。