将包含 int 和 int[] 的结构从 C# 编组到 C++

Posted

技术标签:

【中文标题】将包含 int 和 int[] 的结构从 C# 编组到 C++【英文标题】:Marshalling struct containing int and int[] from C# to C++ 【发布时间】:2012-11-22 00:23:51 【问题描述】:

我有一个带有非托管代码和 C# UI 的 C++ DLL。有一个从 C++ DLL 导入的函数,它以我编写的结构作为参数。

将由我编写的结构 (MyImage) 从 C# 编组到 C++ 后,我可以访问其中的 int[] 数组的内容,但内容不同。我不知道我在这里缺少什么,因为我花了很多时间并尝试了一些技巧来解决这个问题(显然还不够)。

C# 中的 MyImage 结构:

[StructLayout(LayoutKind.Sequential)]
struct MyImage

    public int width;
    public int height;
    public int[] bits; //these represent colors of image - 4 bytes for each pixel

C++ 中的 MyImage 结构:

struct MyImage

    int width;
    int height;
    Color* bits; //typedef unsigned int Color;

    MyImage(int w, int h)
    
         bits = new Color[w*h];
    

    Color GetPixel(int x, int y)
    
          if (x or y out of image bounds) return UNDEFINED_COLOR;
          return bits[y*width+x];
    

以MyImage为参数的C#函数声明:

[DLLImport("G_DLL.dll")]
public static extern void DisplayImageInPolygon(Point[] p, int n, MyImage texture, 
                                                  int tex_x0, int tex_y0);

C++ 实现

DLLEXPORT void __stdcall DisplayImageInPolygon(Point *p, int n, MyImage img,
                                               int imgx0, int imgy0)

    //And below they have improper values (i don't know where they come from)
    Color test1 = img.GetPixel(0,0);
    Color test2 = img.GetPixel(1,0);

所以在调试问题时,我注意到 c++ struct 中的 MyImage.bits 数组包含不同的数据。

我该如何解决?

【问题讨论】:

【参考方案1】:

由于bits 字段是指向在本机代码中分配的内存的指针,因此您需要在 C# 代码中将其声明为 IntPtr

struct MyImage

    public int width;
    public int height;
    public IntPtr bits;

如果您想访问 C# 代码中的单个像素,您需要编写一个 GetPixel 方法,就像您在 C++ 代码中所做的那样。

请注意,由于bits 字段是指向在本机代码中分配的内存的指针,我希望实际代码具有调用delete[] bits 的结构的析构函数。否则你的代码会泄露。

这也意味着您将需要在本机代码中创建和销毁实例,而永远不要在托管代码中这样做。这是您目前遵循的政策吗?我怀疑不是基于我可以在这里看到的代码。

您还需要重新考虑按值传递struct。当您调用该函数时,您真的要复制它吗?这样做意味着你有两个结构的实例,它们的bits 字段都指向同一个内存。但是,谁拥有那段记忆呢?这个结构确实需要通过引用来传递。

我认为您的设计存在一些问题,但我看不到足够多的代码,或者对您的问题了解得不够多,无法给您具体的建议。


在 cmets 中,您声明您的主要目标是将这些位从 C# 代码传输到 C++ 代码。我建议你这样做:

MyImage* NewImage(int w, int h, Color* bits)

    MyImage* img = new MyImage;
    img->w = w;
    img->h = h;
    img->bits = new Color[w*h];
    for (int i=0; i<w*h; i++)
        img->bits[i] = bits[i];
    return img;


void DeleteImage(MyImage* img)

    delete[] img->bits;
    delete img;


void DoSomethingWithImage(MyImage* img)

    // do whatever it is you need to do

在 C# 端,您可以这样声明:

[DllImport(@"dllname.dll", CallingConvention=CallingConvention.Cdecl)]
static extern IntPtr NewImage(int w, int h, int[] bits);

[DllImport(@"dllname.dll", CallingConvention=CallingConvention.Cdecl)]
static extern void DeleteImage(ImtPtr img);

[DllImport(@"dllname.dll", CallingConvention=CallingConvention.Cdecl)]
static extern void DoSomethingWithImage(ImtPtr img);

【讨论】:

是否可以在没有不安全代码的情况下创建一个 IntPtr 到 int[] 数组?这种结构只有一个存在的目的-> 将图像传递到我可以处理的 c++ 层。你是说我应该在 IntPtr 中传递整个结构? 在那种情况下,我根本不会在 C# 中声明结构。只需在本机代码中调用一个传递数据的函数,并创建本机结构。 你说的对我有用。还有 1 个问题:我看对了吗,int[] 数组在作为函数参数传递时被编组为 int* 没有问题,而当数组作为 int[] 在结构内部传递时这不起作用?难道我们不能强制程序将结构内部的这个数组视为将作为参数传递给函数吗? 我认为你做对了。您可以向字段添加一个属性,将其编组为指向第一个元素的指针,这似乎是合理的。但我认为这对你没有用。因为这样您就拥有了在托管内存上运行的本机代码中使用的结构。所以你必须固定那个记忆。它会变得一团糟。按照我的答案中的代码复制一份可以使责任线更清晰。【参考方案2】:

您应该尝试的第一件事是将 C# 代码也声明为 unsigned int 类型。一位可能被解释为您的 int 的符号。

所以在 C# 中是这样的(请注意这些位现在是 uint[]):

[StructLayout(LayoutKind.Sequential)]
struct MyImage

    public int width;
    public int height;
    public uint[] bits; //these represent colors of image - 4 bytes for each pixel

【讨论】:

【参考方案3】:

您可以使用PInvoke Interop Assistant。您只需粘贴您的结构和函数声明,它就会为您生成 C# 代码。它对我有很多帮助。

【讨论】:

以上是关于将包含 int 和 int[] 的结构从 C# 编组到 C++的主要内容,如果未能解决你的问题,请参考以下文章

如何编组包含未知大小的 int 数组的结构?

从C#中的数组中删除指定元素的几种方法,超简单

将具有 int* 成员的 C++ 结构编组到 C#

FourCC 作为 int C#

C# 结构体转string 类型

使用 SWIG 时如何将 int 数组和 List<string> 作为参数从 C# 传递给 C++