如何使用 pinvoke 将二进制数据缓冲区从 C 传递到 C#

Posted

技术标签:

【中文标题】如何使用 pinvoke 将二进制数据缓冲区从 C 传递到 C#【英文标题】:How to pass a buffer of binary data from C to C# with pinvoke 【发布时间】:2014-10-31 20:12:39 【问题描述】:

我有一个用 C 编码的 DLL,它具有将图像数据输出为 unsigned char *、在不同图像格式之间进行转换等功能。我有一个客户想从 C# 中使用它。我猜 C# 端的适当数据类型应该是 byte []。

我认为 PInvoke 会有所帮助,但我找不到合适的技术将这种“二进制数据”编组到 C#(其可变长度存储在单独的变量中)。我见过的所有编组示例都涉及以 null 结尾的字符串,并且图像数组不会在 ascii null 处停止。

我已经看到我可能会考虑通过 IntPtr 将指针传递给数据,但它 看起来这将涉及 C# 端的不安全代码。

【问题讨论】:

您可以使用 IntPtr 而不会不安全。使用 Marshal 类的方法。您也可以使用 byte[] 进行编组。所以关于你的函数是什么样子的想法会有所帮助。 我们真正需要知道的是谁调用了互操作(托管端或非托管端),以及数据是什么以及它以何种方式流动。 【参考方案1】:

你要分配全局内存,GlobalAlloc

然后在 C# 中,您需要将指针编组为托管类型。

对于一个字符串,我使用以下代码,(您可以根据自己的需要进行调整):

 public static string GetString() 
        IntPtr str = mylib.getstring(); //get native pointer
        if (str == IntPtr.Zero)
            return null;

        string newStr = Marshal.PtrToStringAnsi(str); //marshall to managed string
        Marshal.FreeHGlobal(str); //free memory

        return newStr;
    

编辑:忘记提及如何 PInvoke 您的库:

class mylib 
   [DllImport("mylib.dll", CharSet = CharSet.Ansi, CallingConvention = CallingConvention.Cdecl)]
    public extern static IntPtr getstring();

别忘了导入 System.Runtime.InteropServices

【讨论】:

谢谢,实际上我的 C 函数通常会写入一个预先分配的 char *buffer。 其实 FreeHGlobal 匹配 LocalAlloc。 我想 pathStr 是您的答案中的错字(未声明)。我看不到字符串的长度在哪里设置。我尝试调整您的代码并得到一个长度为零的字符串。 Microsft 文档说 Marshal.PtrToStringAnsi 将所有字符复制到第一个空字符。所以这个功能不适合我所说的问题。 双参数版本 ov PtrToString 更接近我需要的:第一个参数和之前一样 (str),第二个参数是要复制的字符数。【参考方案2】:

好的,乔治 TG 的回答让我走上了正确的道路,(谢谢!)但假设我们有一个带有签名的函数 void fillBytes(unsigned char *outBytes, int nbBytes); 在将 nbBytes 写入 outBytes 数组的 C 端,那么 C#端可以这样写:

class Program

    [DllImport("FixedSizeString", CharSet = CharSet.Ansi, CallingConvention = CallingConvention.Cdecl)]
    public static extern void fillBytes(IntPtr buffer, int nbBytes);
    void main(string[] args)
    
        int BUFFSIZ = 256;// corresponds to the number of bytes in the buffer
        IntPtr iptr = Marshal.AllocHGlobal(BUFFSIZ);
        byte[] buffer = new byte[BUFFSIZ];
        if (iptr == IntPtr.Zero)
            Console.WriteLine("Allocation failed");
        else
        
            fillBytes(iptr, BUFFSIZ);
            for (int i = 0; i < BUFFSIZ; ++i)
            
                buffer[i] = Marshal.ReadByte(buffer, i);
            
            Marshal.FreeHGlobal(iptr);
            // now deal with buffer here ...
        
    

【讨论】:

您应该使用Marshal.Copy 而不是循环将数据从非托管缓冲区复制回来。循环将非常昂贵。

以上是关于如何使用 pinvoke 将二进制数据缓冲区从 C 传递到 C#的主要内容,如果未能解决你的问题,请参考以下文章

如何使用 pinvoke 将图像从 c 或 c++ 发送到 C#

使用协议缓冲区/Protobuf 的 PInvoke 通用字符串格式

如何使用 C#/pinvoke 将输入发送到 WinSta0\Winlogon 桌面

IndexOutOfRangeException - 无法使用 PInvoke 查看调用堆栈

将带有结构字段的结构从 c++ 返回到 c# pinvoke

使用协议缓冲区将二进制文件从 Java 服务器发送到 C# Unity3d 客户端