将C结构编组到C#
假设我有一个结构:将C结构编组到C#,c#,.net,dll,interop,pinvoke,C#,.net,Dll,Interop,Pinvoke,假设我有一个结构: 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;
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。我正在使用VisualStudio2010构建一个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结构(反之亦然)?应该很简单:
[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也不知道传递的Vertices数组的长度,因此如果该长度发生变化,则希望将其传递给该方法
要恢复阵列,请执行以下操作:
[DllImport("library.dll", CallingConvention=CallingConvention.StdCall)]
public static extern Vertex[] another(float a);
封送拆收器在传入参数时处理所有内存问题,但返回数组时却无能为力。由于内存是在非托管堆上分配的,因此GC对此一无所知,最终会导致内存泄漏。封送拆收器只需将本机结构复制到托管结构数组,但它无法释放您使用malloc
分配的内存
<>最容易的方法,如果你能改变C++代码,那就是改变代码的代码>另一个< /C> >,以输入一个垂直数组(数组的长度),而不是返回一个数组。我不需要为您编写任何这样做的代码,@DavidHeffernan已经在他的回答中这样做了,这是中断的一部分。结构可以这样声明:
[StructLayout(LayoutKind.Sequential)]
public struct Vertex {
public float x;
public float y;
public float z;
public int ID;
}
[DllImport("library.dll", CallingConvention = CallingConvention.Cdecl)]
public static extern void PopulateVertices(Vertex[] vertices, int count);
接下来,你需要确定一个通话约定。您的C++代码几乎肯定是用<代码> CCDL> <代码>编译的。让我们坚持下去
第一个函数很容易从C#调用:
请注意,此处不应使用SetLastError
——这是针对Windows API函数的。因为这里没有文本,所以不需要设置CharSet
现在,对于另一个,事情变得更复杂了。如果你能在C代码中分配内存,那么这绝对是一条可行之路
void PopulateVertices(Vertex *vertices, int count)
{
for (int i=0; i<count; i++)
{
vertices[i].x = ....
}
}
这样称呼它
Vertex[] vertices = new Vertex[2];
PopulateVertices(vertices, vertices.Length);
如果您不想在围栏的C#侧进行分配,请执行以下操作:
[StructLayout(LayoutKind.Sequential)]
public struct Vertex {
public float x;
public float y;
public float z;
public int ID;
}
[DllImport("library.dll", CallingConvention = CallingConvention.Cdecl)]
public static extern void PopulateVertices(Vertex[] vertices, int count);
< L>从C++代码返回指针并将其分配给<代码> COTASKMeMeloCals> />代码>
IntPtr
Marshal.PtrToStructure
和一些指针算法将返回数组封送到C#数组中Marshal.FreeCoTaskMem
释放本机模块中分配的内存但是如果您需要我的建议,请尝试在托管代码中分配数组。另一种方法呢?内存分配没有问题吗?指针?返回一个用C++ MALOC分配的数组根本不起作用。我猜,封送器可能试图用< COT> COTASKMEMFEXE/COD>释放内存。也许用
CoTaskMemAlloc
分配它就可以了。“我总是喜欢在篱笆的C#一侧分配。”戴维德费弗南——我怀疑这一点。我认为封送拆收器尽可能少地处理非托管内存,特别是当它分配了malloc
时,以防止其他代码仍然期望该指针有效。是的,这是一个问题。这两个声明的另一个大问题是无法计算数组的大小。这是行不通的。因此,如果可能的话,最好(更容易?)在C端分配内存,然后将分配的空间传递给函数?这样当然容易得多。为了选择该解决方案,您需要提前知道阵列的大小。有时,您可以在DLL中调用一个函数(您提供的)来找出所需的数组大小。但通常情况下,您只需要从程序中的其他信息了解数组的大小。谢谢,我将尝试此解决方案。结构并不总是保留在堆栈上。有一种常见的误解,认为值类型(结构是值类型)总是驻留在堆栈上。不是真的。在您的情况下,当您分配一个数组时,数组的内容(它是一个对象)将位于堆上。这里没什么好担心的。在结构的C#中声明它为float[]info
并用此属性标记它:[marshallas(UnmanagedType.ByValArray,SizeConst=3)]
在此处阅读所有关于UnmanagedType属性的内容: