将 C 结构编组到 C#

发布于 2024-12-19 00:48:56 字数 1034 浏览 0 评论 0原文

假设我有一个结构:

typedef struct {
float x;
float y;
float z;
int   ID;
} Vertex;

和一个 C++ 函数:

float first(Vertex* ptr, int length){ //really silly function, just an example
    Vertex u,v;
    u.x = ptr[0].x; //...and so on, copy x,y,z,ID
    v.x = ptr[1].x; 
    return (u.x * v.x + u.y * v.y + u.z * v.z);
    }


Vertex* another(float a, int desired_size){
    Vertex v = (Vertex*)malloc(desired_size*sizeof(Vertex));
    v[0].x = a;
    v[1].x = -a; //..and so on.. make some Vertices.
    return v;
}

首先 - 我的 IDE。我正在使用 Visual Studio 2010 构建 C# (4.0) 应用程序; C++部分也是在VS2010中构建的。

我知道如何构建 C/C++ 代码的 DLL 并在 C# 应用程序中使用它,但直到今天我只使用原始参数和返回值,例如:

    [DllImport("library.dll", CharSet = CharSet.Ansi, SetLastError = true, CallingConvention = CallingConvention.StdCall)]
    public static extern int simple(int a, int b);

今天我需要传递 数组 structs(如上面的示例)..也许还会收到一个返回..

如何将 C# 类“翻译”为 C 结构体(反之亦然)?

Suppose I have a structure:

typedef struct {
float x;
float y;
float z;
int   ID;
} Vertex;

and a C++ function:

float first(Vertex* ptr, int length){ //really silly function, just an example
    Vertex u,v;
    u.x = ptr[0].x; //...and so on, copy x,y,z,ID
    v.x = ptr[1].x; 
    return (u.x * v.x + u.y * v.y + u.z * v.z);
    }


Vertex* another(float a, int desired_size){
    Vertex v = (Vertex*)malloc(desired_size*sizeof(Vertex));
    v[0].x = a;
    v[1].x = -a; //..and so on.. make some Vertices.
    return v;
}

First - my IDE. I'm using Visual Studio 2010, building a C# (4.0) application; The C++ part is also built in VS2010.

I know how to build a DLL of C/C++ code and use it in C# application, but until today I used only primitive arguments and return values like:

    [DllImport("library.dll", CharSet = CharSet.Ansi, SetLastError = true, CallingConvention = CallingConvention.StdCall)]
    public static extern int simple(int a, int b);

Today I need to pass an array of structs (as in the example above).. and perhaps also receive one back..

How to I "translate" a C# class to a C struct (and vice versa) ??

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

聚集的泪 2024-12-26 00:48:56

该结构可以这样声明:

[StructLayout(LayoutKind.Sequential)]
public struct Vertex {
    public float x;
    public float y;
    public float z;
    public int ID;
}

接下来,您需要确定调用约定。您的 C++ 代码几乎肯定是使用 cdecl 编译的。让我们坚持下去。

首先,该函数很容易从 C# 调用:

[DllImport("library.dll", CallingConvention = CallingConvention.Cdecl)]
public static extern float first(Vertex[] vertices);

请注意,您不应该在此处使用 SetLastError – 这是针对 Windows API 函数的。并且无需设置 CharSet,因为此处没有文本。


现在,另一个事情变得更加复杂。如果您可以在 C# 代码中分配内存,那么这绝对是正确的选择。

void PopulateVertices(Vertex *vertices, int count)
{
    for (int i=0; i<count; i++)
    {
        vertices[i].x = ....
    }
}

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

[DllImport("library.dll", CallingConvention = CallingConvention.Cdecl)]
public static extern void PopulateVertices(Vertex[] vertices, int count);

并这样调用它

Vertex[] vertices = new Vertex[2];
PopulateVertices(vertices, vertices.Length);

如果您不想在栅栏的 C# 端进行分配,则可以这样做:

  1. 从 C++ 代码返回一个指针,并使用 分配它CoTaskMemAlloc
  2. 在 C# 中,将导入函数的返回值声明为 IntPtr
  3. 使用 Marshal.PtrToStructure 和一些指针算术将返回数组编组到 C# 数组中。
  4. 调用 Marshal.FreeCoTaskMem 来释放本机模块中分配的内存。

但如果您需要我的建议,请尝试并坚持在托管代码中分配数组。

The struct can be declared like this:

[StructLayout(LayoutKind.Sequential)]
public struct Vertex {
    public float x;
    public float y;
    public float z;
    public int ID;
}

Next you need to settle on a calling convention. Your C++ code is almost certainly compiled with cdecl. Let's stick with that.

The function first is easy to call from C#:

[DllImport("library.dll", CallingConvention = CallingConvention.Cdecl)]
public static extern float first(Vertex[] vertices);

Note that you should not use SetLastError here–that's for Windows API functions. And there's no need to set the CharSet since there is no text here.


Now, for another things get more complex. If you can allocate the memory in the C# code then that is definitely the way to go.

void PopulateVertices(Vertex *vertices, int count)
{
    for (int i=0; i<count; i++)
    {
        vertices[i].x = ....
    }
}

On the C# side you declare it like this:

[DllImport("library.dll", CallingConvention = CallingConvention.Cdecl)]
public static extern void PopulateVertices(Vertex[] vertices, int count);

and call it like this

Vertex[] vertices = new Vertex[2];
PopulateVertices(vertices, vertices.Length);

If you don't want to allocate on the C# side of the fence then do it like this:

  1. Return a pointer from the C++ code and allocate it with CoTaskMemAlloc.
  2. In C# declare the return value of the imported function as IntPtr.
  3. Use Marshal.PtrToStructure and some pointer arithmetic to marshal the return array into a C# array.
  4. Call Marshal.FreeCoTaskMem to free the memory allocated in the native module.

But if you want my advice, try and stick to allocating the array in the managed code.

剩一世无双 2024-12-26 00:48:56

应该这么简单:

[StructLayout(LayoutKind.Sequential)]
public struct Vertex {
    float x;
    float y;
    float z;
    int   ID;
}

[DllImport("library.dll", CallingConvention=CallingConvention.StdCall)]
public static extern float first(Vertex[] verticies, int arrLen);

可能遇到的问题是,是否对 C 版本的结构进行了任何打包,也可能是对结构布局进行了打包。如果布局不匹配,您可能需要将其更改为 LayoutKind.Explicit 并在每个字段上使用 [FieldOffset(0)] 属性。 C 也不知道传递的顶点数组的长度,因此如果它发生变化,您需要将其传递给该方法。

要返回数组:

[DllImport("library.dll", CallingConvention=CallingConvention.StdCall)]
public static extern Vertex[] another(float a);

封送拆收器在传入参数时处理所有内存问题,但返回数组时它无法执行任何操作。由于内存是在非托管堆上分配的,因此 GC 对此一无所知,并且您将导致内存泄漏。编组器只会将本机结构复制到托管结构数组,但它无法释放使用 malloc 分配的内存。

如果您可以更改 C++ 代码,解决此问题的最简单方法是更改​​ another 的签名以接受顶点数组(以及数组的长度),而不是返回一个。我不需要为您编写任何代码来执行此操作,@DavidHeffernan 已经在他的答案(中断部分)中完成了此操作。

It should be this easy:

[StructLayout(LayoutKind.Sequential)]
public struct Vertex {
    float x;
    float y;
    float z;
    int   ID;
}

[DllImport("library.dll", CallingConvention=CallingConvention.StdCall)]
public static extern float first(Vertex[] verticies, int arrLen);

The issues you may run into would be if there is any packing done on the C version of the struct, and possibly the struct layout. If the layout doesn't match, you may want to change it to LayoutKind.Explicit and use the [FieldOffset(0)] attribute on each field. C would also have no idea, the length of the verticies array passed, so if that changes, you'd want to pass that along to the method.

To get an array back:

[DllImport("library.dll", CallingConvention=CallingConvention.StdCall)]
public static extern Vertex[] another(float a);

The marshaler handles all of the memory issues when passing arguments in, but returning the array it can't do anything. Since the memory is allocated on the unmanaged heap, the GC has no idea about it, and you will wind up with a memory leak. The marshaller will simply copy the native structs to the managed struct array, but it can't free the memory you've allocated with malloc.

The easiest way to get around it, if you can change the C++ code, would be to change the signature of another to take in an array of verticies (and the length of the array) instead of returning one. I don't need to write out any code for you that does this, @DavidHeffernan has already done this in his answer, the part of the break.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文