Kha*_*led -1 c# c++ arrays pinvoke marshalling
我能够通过装饰它成功地将引用类型对象从 C# 传递到 C++StructLayout(LayoutKind.Sequential)
C# 中的类:
StructLayout(LayoutKind.Sequential)
public class Point
{
[DataMember]
public double X { get; set; }
[DataMember]
public double Y { get; set; }
}
Run Code Online (Sandbox Code Playgroud)
C++ 中的等价物:
struct Point
{
public:
double x;
double y;
};
Run Code Online (Sandbox Code Playgroud)
C++中的函数体:
extern "C" __declspec(dllexport) void Translate(Point* pt, double dx, double dy)
{
pt->x += dx;
pt->y += dy;
}
Run Code Online (Sandbox Code Playgroud)
以及 C# 中的等效项:
[DllImport("myDll.dll", CallingConvention = CallingConvention.Cdecl)]
public extern static void Translate(Point pt, double dx, double dy);
Run Code Online (Sandbox Code Playgroud)
到目前为止我没有遇到任何问题,当我在接下来的两个函数之间传递这些点的数组时,问题就会发生:
extern "C" __declspec(dllexport) double GetArea(Point** vertices,int count, bool isClosed)
{
double area = 0;
if (!isClosed || count < 3) return 0;
int j;
for (int i = 0; i < count; i++)
{
// j = i+1 except when i = count then j = 0
j = (i + 1) % count;
area += vertices[i]->x * vertices[j]->y;
area -= vertices[i]->y * vertices[j]->x;
}
area /= 2;
return fabs(area);
}
Run Code Online (Sandbox Code Playgroud)
和 C# 版本:
[DllImport("myDll.dll", CallingConvention = CallingConvention.Cdecl)]
public extern static double GetArea(Point[] vertices, int count, bool isClosed);
Run Code Online (Sandbox Code Playgroud)
有没有一种特定的方法来编组这个数组?但保持 Point 类不变?
调用方法是这样的:
public static double GetArea()
{
Point[] vertices = { new Point(100, 100), new Point(0, 0), new Point(0, 100) };
bool isClosed = true;
return NativeMethods.GetArea(vertices, vertices.Length, isClosed); //this is the imported C++ method
}
Run Code Online (Sandbox Code Playgroud)
您根本不应该真正互操作类(除了那些明确支持的类,例如string数组......)。.NET 的本机互操作是为 C 风格的函数和结构设计的,而不是为 C++ 设计的。因此,要么将本机代码更改为 C 风格,要么使用 C++/CLI 之类的代码为 C++ 对象和函数提供托管接口。
处理互操作的最简单方法是使用编组器,并将其更改Point为值类型,而不是引用类型:
struct Point
{
public double x;
public double y;
}
[DllImport("myDll.dll", CallingConvention = CallingConvention.Cdecl)]
public extern unsafe static double GetArea(Point[] vertices, int count, bool isClosed);
Run Code Online (Sandbox Code Playgroud)
这当然需要Point是一个 blittable 类型 - 只需将其struct改为aclass就足够了。当vertices是指向值数组的指针时,这就是您所要做的。同样,您需要将其设为 C++ 端的值,而不是指针(并且函数的签名为GetArea(Point *vertices, ...),而不是使用指向指针的指针)。
手动编组数组有两个主要选项 - 不安全代码和IntPtr. 如果您需要整理一些更复杂的东西,这可能很有用
不安全签名很简单:
[DllImport("myDll.dll", CallingConvention = CallingConvention.Cdecl)]
public extern unsafe static double GetArea(Point** vertices, int count, bool isClosed);
Run Code Online (Sandbox Code Playgroud)
创建和传递数组的方法与 C 中的相同 - 我无法真正提供任何具体信息,因为它完全取决于谁拥有什么内存以及该内存的结构。您可以轻松使用现有的 .NET 数组,只需固定它们:
public static unsafe double CallGetArea(Point[] vertices, bool isClosed)
{
if (vertices.Length == 0) throw new ArgumentException("Vertices empty.", "vertices");
fixed (Point* pVertices = &vertices[0])
{
return GetArea(pVertices, vertices.Length);
}
}
Run Code Online (Sandbox Code Playgroud)
这假设您只想传递一个Point值数组(签名将具有Point*)。您不能真正将指针传递给这样的数组 -pVertices是只读的。再说一次,这仅在Point可 blittable 时才有效。
签名IntPtr会丢弃类型信息:
[DllImport("myDll.dll", CallingConvention = CallingConvention.Cdecl)]
public extern unsafe static double GetArea(IntPtr vertices, int count, bool isClosed);
Run Code Online (Sandbox Code Playgroud)
同样,该调用与 C 类似,您还必须执行大量烦人的编组器调用来进行读取和写入,而不是使用类似 C 的语言来执行此操作。
如果您确实想坚持创建一个指向 的指针数组的指针Point,那么事情会变得有点复杂。编组器不支持指针数组,因此您必须手动进行编组。当然,这涉及大量不必要的复制和分配 - 因此,如果您出于性能原因尝试这样做,请不要这样做。基本思想是这样的:
unsafe double CallGetAreaPointers(Point[] vertices, bool isClosed)
{
var ipArray = Marshal.AllocHGlobal(vertices.Length * sizeof(Point));
var ipItems = new Stack<IntPtr>();
try
{
Point** pArray = (Point**)ipArray.ToPointer();
for (var i = 0; i < vertices.Length; i++)
{
var ipItem = Marshal.AllocHGlobal(sizeof(Point));
ipItems.Push(ipItem);
Marshal.StructureToPtr(vertices[i], ipItem, false);
pArray[i] = (Point*)ipItem.ToPointer();
}
GetArea(pArray, vertices.Length, isClosed);
}
finally
{
IntPtr ipItem;
while ((ipItem = ipItems.Pop()) != IntPtr.Zero) Marshal.FreeHGlobal(ipItem);
Marshal.FreeHGlobal(ipArray);
}
}
Run Code Online (Sandbox Code Playgroud)
(免责声明:这只是一个后台代码;如果您决定这样做,请确保您做得正确,这只是一个一般想法)
请注意,为了简单起见,我仍然使用Pointas struct- 它只允许您在 C++ 端使用指针。如果您想完全将其设为 C# 中的引用类型,最简单的方法是创建一个PointStruct类型(如果您愿意,可以是私有类型,也没关系)并将类字段复制到结构体中。当然,在任何一种情况下,单独分配每个Point实例都没有什么意义——一个简单的值数组将以完全相同的方式工作,同时作为连续的内存位,使用和清理更简单、更便宜。