【问题标题】:C# P/Invoke and array of structs containing byte arraysC# P/Invoke 和包含字节数组的结构数组
【发布时间】:2013-04-25 16:33:57
【问题描述】:

我需要从 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方法的参数类型如何定义?

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

【问题讨论】:

    标签: c# interop pinvoke


    【解决方案1】:

    首先,您的结构中的参数顺序错误。并且字节数组需要通过手动编组声明为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() 来获取结构数组中每个结构的固定数组的地址。编写一些帮助函数以减轻该任务的痛苦是值得的。

    【讨论】:

    • 谢谢大卫。让我困惑的是头文件中的注释:要发送的缓冲区数组。数组 * 中的最后一个元素必须是 {NULL, 0}。。正如我上一个代码块示例所示,pReq 和 pResp 参数似乎是一个 Buffer 数组。我应该如何在 P/invoke 中声明这个?
    • 好的,我现在赶上了。将它们声明为Buffer[]
    • @ThomasZweifel 好的,使用Buffer[] 工作得很好,我测试过。但是您确实需要该结构是 blittable 的。所以这意味着IntPtr 和固定。
    • 谢谢大卫。我现在正在处理它,将需要我一段时间。感谢您的大力帮助!
    【解决方案2】:

    你在 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

    【讨论】:

    • 嗨。谢谢。我拥有的库不是 COM 或托管 C++。所以我猜 PInvoke 互操作助手不会与我的头文件一起工作。
    • 此项目旨在使用本机 C 或 C++ 代码,而不是托管。事实上,使用托管 C++ 编写的库不需要互操作,因为它们已经是 .Net 代码,您可以直接调用它们。
    【解决方案3】:

    基于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);
                    }
                }
            }
        }
    }
    

    【讨论】:

    • pData 是一个字节数组,而不是一个以空字符结尾的字符串
    • 就我在很多项目中使用上面的代码而言,我没有任何问题。另外,也请检查一下:stackoverflow.com/questions/6063428/… 我敢肯定,根据托管库端的性质和功能,还有其他方法可以实现相同的结果。
    • 这里行不通。由于这些是 struct 数组,因此您需要使 struct blittable。
    • 另外,手册LoadLibraryGetProcAddressFreeLibrary 的目的是什么?
    • 我认为我们正在远离回答这个问题。但是,我使用了很久以前构建的项目中的代码示例,并且需要知道 DLL 是否存在于应用程序正在查看的文件夹中。 stuct 的情况对我来说是缺失的,因为不需要任何结构。尽管如此,请检查以下内容,我认为现在最好保持低位。谢谢大卫。 stackoverflow.com/questions/548382/…
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2018-08-11
    • 1970-01-01
    • 2018-04-05
    • 1970-01-01
    • 1970-01-01
    • 2012-01-21
    • 2010-11-18
    相关资源
    最近更新 更多