Nun versuche ich diese Strukturen in meiner eigenen c# DLL nachzubauen um dann mit meiner DLL auf die c++ DLL zuzugreifen.
Hierzu habe ich folgendes.
public struct tagApiPolygonObjectV1
{
public tagApiObjectHeader header;
public tagApiPolygonParmsV1 parms;
public ushort pointCount;
public point[] points;
}
public struct point
{
public double x;
public double y;
}
Mit folgendem Code habe ich versucht die dynamische allokierung des Point-arrays nachzubauen.
Das Problem ist, daß in der C++ Deklaration die Struktur dynamisch ist (d.h. die Größe sich durch den Wert in pointCount bestimmt).
Mit der C# Deklaration 'point[] points' definierst du aber nur eine Referenz auf ein Array, d.h. die Größe deiner C#-Struktur bleibt konstant).
Ich denke, du mußt dafür stattdessen Marshal.AllocHGlobal(...) zum Reservieren der Struktur verwenden und die Größe dann entprechend selber ausrechnen.
public struct tagApiPolygonObjectV1
{
public tagApiObjectHeader header;
public tagApiPolygonParmsV1 parms;
public ushort pointCount;
public IntPtr points;
}
der Member points ist kein Zeiger auf ein PointArray sondern ein in die Struct eingebettetes Array von konstanter Größe 1.
Dadurch kann man wie bei einem Array auf die Punkte zugreifen. Es muss natürlich, wie Th69 schon geschrieben hatte, für jeden weiteren Punkt auch mehr Speicher allokiert werden:
Tja, dann wirds mit .NET extrem unschön. Eingebettete Felder müssen für PInvoke konstant sein. Da muss die ganze Struktur über IntPtr verarbeitet werden.
Dieser Beitrag wurde 1 mal editiert, zum letzten Mal von svenson am .
public sealed class Polygon
{
public Header header;
public Parms parms;
public PointD[] points;
}
[StructLayout(LayoutKind.Sequential, Size = 1)]
public struct Header { }
[StructLayout(LayoutKind.Sequential, Size = 1)]
public struct Parms { }
public struct PointD
{
public double x;
public double y;
}
internal sealed class PolygonMarshaler : ICustomMarshaler
{
private unsafe struct Poly
{
public Header header;
public Parms parms;
public ushort pointCount;
public fixed PointD points[1];
}
private static ICustomMarshaler m = new PolygonMarshaler();
private static ICustomMarshaler GetInstance(string s)
{
return m;
}
#region ICustomMarshaler Member
public void CleanUpManagedData(object ManagedObj)
{
}
public void CleanUpNativeData(IntPtr pNativeData)
{
Marshal.FreeHGlobal(pNativeData);
}
public unsafe int GetNativeDataSize()
{
return sizeof(Poly);
}
public unsafe IntPtr MarshalManagedToNative(object ManagedObj)
{
if (!(ManagedObj is Polygon))
return IntPtr.Zero;
Polygon polygon = (Polygon)ManagedObj;
int size = sizeof(Poly);
if ((polygon.points != null) && (polygon.points.Length > 1))
size += ((polygon.points.Length - 1) * sizeof(PointD));
Poly* ptr = (Poly*)Marshal.AllocHGlobal(size);
ptr->header = polygon.header;
ptr->parms = polygon.parms;
ptr->pointCount = (ushort)(polygon.points == null ? 0 : polygon.points.Length);
for (int i = 0; i < ptr->pointCount; i++)
ptr->points[i] = polygon.points[i];
return (IntPtr)ptr;
}
public unsafe object MarshalNativeToManaged(IntPtr pNativeData)
{
if (pNativeData == IntPtr.Zero)
return null;
Polygon result = new Polygon();
Poly* ptr = (Poly*)pNativeData;
result.header = ptr->header;
result.parms = ptr->parms;
result.points = new PointD[ptr->pointCount];
for (int i = 0; i < ptr->pointCount; i++)
result.points[i] = ptr->points[i];
return result;
}
#endregion
}
Hier fehlen noch die konkreten Implementierungen von Header und Parms.
Ich hoffe du kannst damit einen Einstieg finden. Vielleicht lässt sich das auch eleganter nur unter der Verwendung der unsafe struct Poly lösen, kommt ganz drauf an, ob und wie du die eigentliche Klasse verwenden willst.
Die Funktion wird in einem Callback aufgerufen, das mir u.a. die Anzahl der Punkte mitteilt.
Jetzt möchte ich die DLL in einem WPF C# Projekt nutzen. Meine Ansätze bis jetzt waren:
So implmentierte ich die Struktur in C#:
//[System.Runtime.InteropServices.StructLayoutAttribute (System.Runtime.InteropServices.LayoutKind.Sequential)]
public struct TScanPointCloud
{
public int PointCnt;
public TScanPoint[] scanPoints;
public TScanPointCloud(int pointCount)
{
PointCnt = new int();
PointCnt = pointCount;
scanPoints = new TScanPoint[pointCount];
}
};
Das Callback in dem ich die Funktion aufrufe sieht wie folgt aus:
private unsafe static void pointsCallback(int New3DPointCnt, IntPtr pUserData)
{
Param param = new Param();
// Set this Param to the value of the Param in unmanaged memory.
param = (Param)Marshal.PtrToStructure(pUserData, typeof(Param));
if (New3DPointCnt > 0)
{
tScanPointCloud parameter = new tScanPointCloud(New3DPointCnt);
IntPtr pnt = Marshal.AllocHGlobal(Marshal.SizeOf(parameter));
// Copy the struct to unmanaged memory.
Marshal.StructureToPtr(parameter, pnt, false);
get3DDataCloudUpdate(param.Handle, pnt);
tScanPointCloud result = new tScanPointCloud();
result = (TScanPointCloud)Marshal.PtrToStructure(pnt, typeof(tScanPointCloud));
Marshal.FreeHGlobal(pnt);
}
}
Bei der Zeile Marshal.StructurToPtr(... bekomme ich dann die ArgumentException (siehe Anhang).
Ich habe leider nicht so einfach die Möglichkeit (wie onfire84) das Struktur-Array aus der Struktur rauszunehmen und muss idealerweise eine anderer Möglichkeit finden...
Wär toll wenn jemand weiterhelfen kann! Danke im Voraus!
1. Warum ist das StructLayoutAttribute auskommentiert? Ohne Sequentielles Layout funktionierts nicht.
2. Die Struktur in C# hat irgendwie nichts mit der in C++ zu tun. Wenn du einen Pointer auf eine Struktur hast ist es in C# ein IntPtr.
Also musste tScanPointCloud in C# so aussehen:
[System.Runtime.InteropServices.StructLayoutAttribute (System.Runtime.InteropServices.LayoutKind.Sequential)]
public struct TScanPointCloud
{
public int PointCnt;
public IntPtr Point;
}
Dann musst dir Point ebenfalls erstmal mit PtrToStructure() marschallen.
Was sind tVector3Df ud tPoint3Df ? Ebenfalls Strukturen? Dann musst diese ebenfalls in C# definieren und marschallen.
Ist alles ein bisschen umständlich, aber funktioniert.
Dieser Beitrag wurde 1 mal editiert, zum letzten Mal von Scavanger am .
[System.Runtime.InteropServices.StructLayoutAttribute (System.Runtime.InteropServices.LayoutKind.Sequential)]
public struct tPoint3Df
{
public float x, y, z;
};
[System.Runtime.InteropServices.StructLayoutAttribute (System.Runtime.InteropServices.LayoutKind.Sequential)]
public struct tVector3Df
{
public float dx, dy, dz;
};
[System.Runtime.InteropServices.StructLayoutAttribute (System.Runtime.InteropServices.LayoutKind.Sequential)]
public struct tScanPoint
{
public IntPtr Position;
public IntPtr NormVect;
public float Quality;
public tScanPoint(IntPtr pos, IntPtr vec)
{
Position = pos;
NormVect = vec;
Quality = (float)0.0;
}
};
[System.Runtime.InteropServices.StructLayoutAttribute (System.Runtime.InteropServices.LayoutKind.Sequential)]
public struct tScanPointCloud
{
public int PointCnt;
public IntPtr Point;
}
Sollte der Konstruktor von tScanPoint so aussehen? Brauche ich den überhaupt? Du hast ja für tScanPointCloud auch keinen vorgesehen. Aber wie definiere ich dann die dynamische Grösse, die sich ja bei jedem Callback - Aufruf ändert?
tPoint3Df point = new tPoint3Df();
IntPtr pntPoint = Marshal.AllocHGlobal(Marshal.SizeOf(point));
Marshal.StructureToPtr(point, pntPoint, false);
tVector3Df vector = new tVector3Df();
IntPtr pntVector = Marshal.AllocHGlobal(Marshal.SizeOf(vector));
Marshal.StructureToPtr(vector, pntVector, false);
tScanPoint scanPoint = new tScanPoint(pntPoint, pntVector); // ???
IntPtr pntScanPoint = Marshal.AllocHGlobal(Marshal.SizeOf(scanPoint));
Marshal.StructureToPtr(scanPoint, pntScanPoint, false);
tScanPointCloud parameter = new tScanPointCloud(New3DPointCnt); // ???