0

我需要从 C# 代码调用本机 DLL。由于我对 C/C++ 不是很熟悉,所以我不知道应该如何在 C# 中声明 C 中定义的结构,以便可以调用它。问题是两个参数似乎是一个结构数组,我不知道如何在 C# 中声明它(参见最后一个代码块):

c++头文件:

typedef enum
{   
    OK = 0,
    //others
} RES

typedef struct
{
    unsigned char* pData;
    unsigned int length;
} Buffer;

RES SendReceive(uint32 deviceIndex
    Buffer* pReq,
    Buffer* pResp,
    unsigned int* pReceivedLen,
    unsigned int* pStatus);

c#声明:

enum
{   
    OK = 0,
    //others
} RES

struct Buffer
{
    public uint Length;
    public ??? Data; // <-- I guess it's byte[]
}

[DllImport("somemodule.dll", CallingConvention = CallingConvention.Cdecl)]
public static extern uint SendReceive(
    uint hsmIndex,
    uint originatorId,
    ushort fmNumber,
    ??? pReq,  // <-- should this be ref Buffer[] ?
    uint reserved,
    ??? pResp, // <-- should this be ref Buffer[] ?
    ref uint pReceivedLen,
    ref uint pFmStatus);

在一个等效的 java 客户端中,我发现参数不仅仅是一个 Buffer,而是一个 Buffer 数组。在 C# 中,它看起来像这样:

 var pReq = new Buffer[] 
{
    new Buffer { Data = new byte[] { 1, 0 }, Length = (uint)2 }, 
    new Buffer {Data = requestStream.ToArray(), Length = (uint)requestStream.ToArray().Length },
    //according to the header file, the last item must be {NULL, 0}
    new Buffer { Data = null, Length = 0 }
};

var pResp = new Buffer[] 
{
    new Buffer { Data = new byte[0x1000], Length = 0x1000 }, 
    //according to the header file, the last item must be {NULL, 0}
    new Buffer { Data = null, Length = 0x0 }
};

这对我来说似乎很奇怪,因为 extern C 方法确实有一个指向 Buffer 结构 (Buffer*) 的指针,而不是指向 Buffer 数组 (Buffer[]*) 的指针。如何在 C# 中定义 Struct 和 extern 方法的参数类型?

任何帮助表示赞赏,谢谢。

4

3 回答 3

2

首先,您的结构的参数顺序错误。并且需要将字节数组声明为IntPtr手动编组:

struct Buffer
{
    public IntPtr Data;
    public uint Length;
}

p/invoke 应该是:

[DllImport("MyNativeDll.dll", CallingConvention=CallingConvention.Cdecl)]
static extern RES SendReceive(
    uint deviceIndex, 
    [In] Buffer[] pReq, 
    [In, Out] Buffer[] pResp, 
    out uint pReceivedLen, 
    out uint pStatus
);

字节数组需要IntPtr使结构是 blittable。这是必需的,以便可以将数组参数声明为Buffer[].

对字节数组进行编组会有点痛苦。您将希望使用GCHandle固定托管字节数组,并调用AddrOfPinnedObject()以获取结构数组中每个结构的固定数组的地址。编写一些帮助函数以减少该任务的痛苦是值得的。

于 2013-04-25T07:34:35.027 回答
0

您在 c# 中的方法签名应该类似于:

[DllImport("MyNativeDll.dll")]
public static extern RES SendReceive (uint32 deviceIndex, ref Buffer pReq, ref Buffer pResp, ref uint pReceivedLen, ref uint pStatus);

看到这个项目,它可能会在未来帮助你,所以从.net http://clrinterop.codeplex.com/releases/view/14120生成本机调用

于 2013-04-25T07:28:49.020 回答
0

基于 C++ 标头但未经测试,请查看以下代码:

using System;
using System.Runtime.InteropServices;
using System.Text;

namespace WindowsFormsApplication1
{
    public class Class1
    {
        public struct Buffer
        {
            [MarshalAs(UnmanagedType.LPStr)]
            public StringBuilder pData;

            public uint length;
        }

        [DllImport("kernel32.dll", EntryPoint = "LoadLibrary")]
        static extern int LoadLibrary(string lpLibFileName);

        [DllImport("kernel32.dll", EntryPoint = "GetProcAddress")]
        static extern IntPtr GetProcAddress(int hModule, string lpProcName);

        [DllImport("kernel32.dll", EntryPoint = "FreeLibrary")]
        static extern bool FreeLibrary(int hModule);

        [UnmanagedFunctionPointer(CallingConvention.Cdecl, CharSet = CharSet.Ansi)]
        internal delegate IntPtr SendReceive(
            uint deviceIndex,
            ref Buffer pReq,
            ref Buffer pResp,
            uint pReceivedLen,
            uint pStatus);

        public void ExecuteExternalDllFunction()
        {
            int dll = 0;

            try
            {
                dll = LoadLibrary(@"somemodule.dll");
                IntPtr address = GetProcAddress(dll, "SendReceive");

                uint deviceIndex = 0;
                Buffer pReq = new Buffer() { length = 0, pData = new StringBuilder() };
                Buffer pResp = new Buffer() { length = 0, pData = new StringBuilder() };
                uint pReceivedLen = 0;
                uint pStatus = 0;

                if (address != IntPtr.Zero)
                {
                    SendReceive sendReceive = (SendReceive)Marshal.GetDelegateForFunctionPointer(address, typeof(SendReceive));

                    IntPtr ret = sendReceive(deviceIndex, ref pReq, ref pResp, pReceivedLen, pStatus);
                }
            }
            catch (Exception Ex)
            {
                //handle exception...
            }
            finally
            {
                if (dll > 0)
                {
                    FreeLibrary(dll);
                }
            }
        }
    }
}
于 2013-04-25T07:52:20.727 回答