从 c++ 到 c# 的 pinvoke 字节数组

Posted

技术标签:

【中文标题】从 c++ 到 c# 的 pinvoke 字节数组【英文标题】:pinvoke byte array from c++ to c# 【发布时间】:2017-06-25 22:12:10 【问题描述】:

我正在尝试将在 c++ 中创建的字节数组移动到 c# 以供使用,现在我看到字节数组在 c++ 端是有效的,但是当我移回 c# 时我得到了 null。

c++代码

__declspec(dllexport) void test(unsigned char* t_memory, int* t_size)

    int width, height, channels_in_file;
    t_memory = stbi_load("test.png", &width, &height, &channels_in_file, 0);
    *t_size = width*height;

c#代码

[DllImport(DllFilePath, EntryPoint = "test", CharSet = CharSet.Unicode, CallingConvention = CallingConvention.Cdecl)]
private static extern int _test(ref IntPtr memory, out int size);

public static void Test()
   
    IntPtr memory = IntPtr.Zero;
    _test(ref memory, out int size);

    byte[] memoryArray = new byte[size];

    Marshal.Copy(memory, memoryArray, 0, size);

    Bitmap bmp;
    using (var ms = new MemoryStream(memoryArray))
    
        bmp = new Bitmap(ms);
    

    bmp.Save("test_recreated.png");

【问题讨论】:

来自项目页面:“其中一些库对于现有的开源库来说似乎是多余的。它们是否更好?”。不。这会一遍又一遍地重新发明。 【参考方案1】:

C++ 代码不返回数组,因为参数声明不正确。您传递了指针,但需要传递指针的地址。

应该像这样更改 C++ 代码以匹配 C# 代码:

__declspec(dllexport) int test(unsigned char** t_memory, int* t_size)

    int width, height, channels_in_file;
    *t_memory = stbi_load("test.png", &width, &height, &channels_in_file, 0);
    *t_size = width*height;
    return 0;

您必须传递数组的地址,而不是数组本身。请注意此更改后与 size 参数设置的相似之处。

我还包含一个返回值以匹配 C# 代码。

您还需要导出释放器以避免泄漏此内存。

【讨论】:

【参考方案2】:

试试下面的代码。一旦你存在,方法 t_size 就不会存在。所以内存必须在调用方法中分配。你的图有多大。大小可能必须是 long 而不是 int。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Runtime.InteropServices;
using System.Drawing;
using System.IO;

namespace ConsoleApplication1

    class Program
    
        const string DllFilePath = @"c:\temp";

        [DllImport(DllFilePath, EntryPoint = "test", CharSet = CharSet.Unicode, CallingConvention = CallingConvention.Cdecl)]
        private static extern int _test(ref IntPtr memory, ref IntPtr size);

        static void Main(string[] args)
        
        

        public static void Test()
           
            IntPtr memory = IntPtr.Zero;
            IntPtr _size = Marshal.AllocCoTaskMem(sizeof(int));

            _test(ref memory, ref _size);
            int size = (int)Marshal.PtrToStructure(_size, typeof(int));

            byte[] memoryArray = new byte[size];

            Marshal.Copy(memory, memoryArray, 0, size);

            Bitmap bmp;
            using (var ms = new MemoryStream(memoryArray))
            
                bmp = new Bitmap(ms);
            

            bmp.Save("test_recreated.png");
        
    

【讨论】:

什么错误?它应该与发布的 c++ 代码一起使用。如果是错的,那么c++就是错的。 这是互操作。整个目标是生成沿二进制接口匹配的两段代码。你不能生产一个并声明另一个是错误的。你需要确保双方都同意。您提供的 C# 代码与问题中的 C++ 不匹配。然而,问题中的 C++ 不能返回任何东西,正如我的回答中所解释的那样。 什么不匹配?代码应该可以工作。你试过了吗?你了解 IntPtr 的真正含义吗? 是的,我想我知道IntPtr 是什么!我为什么要试试你的代码。我知道它与问题中的 C++ 不匹配。我知道问题中的 C++ 永远无法返回字节数组。最后,我知道您肯定没有尝试运行您的代码。你没有这样做,你怎么能批评别人不这样做。 语言不同,声明也必须不同。该代码仍然有效。您向专家寻求帮助的原因是您不知道如何自己做。因此,请相信专家。你不知道答案。

以上是关于从 c++ 到 c# 的 pinvoke 字节数组的主要内容,如果未能解决你的问题,请参考以下文章

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

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

通过 PInvoke 从 C# 到 C++ 的简单结构中的垃圾数据

将字符串从 C++ 传递到 C#

如何使用 PInvoke 将 std::wstring 从 C++ 返回到 C# 而无需编写字符串缓冲区转换 [重复]

在 c# 和 c++ 之间将 double 类型的二维多维数组作为输入和输出的 pinvoke 编组