C# ObjektArray an C++ übergeben (Marshaling schlägt fehl)
-
Hallo zusammen,
folgende Grundvoraussetzung:
Ich habe eine in C++ geschriebene (unmanaged) DLL. Darin werden verschiedene geometrische Berechnungen durchgeführt, wozu auch die Klasse Gdiplus::Point verwendet wird (enthält zwei int32 Werte, die die Koordinaten des Punkts darstellen).
Einige Funktionen in dieser DLL erwarten ebensolche Point Objekte als Übergabe, manche als "Einzelobjekt", manche in einem Array, manche beides z.B.:int getPointPolygonSituation(GdiPPt *pt, GdiPPt *pptArr, int ppc) { // TEST std::cout << "C++ Ausgabe: " << std::endl; std::cout << "pt: " << pt->X << " " << pt->Y << std::endl; // -> richtige Übergabe for(int i=0 ; i<ppc ; i++) { std::cout << pptArr[i].X << " " << pptArr[i].Y << std::endl; // -> totaler Schrott } std::cout << "Zeiger der pts im Array: " << std::endl; for(int i=0 ; i<ppc ; i++) { std::cout << &pptArr[i] << std::endl; // -> ganz seltsame Zeiger } // TEST ENDE // hier passieren die eigentlichen Berechnungen, ist hier ja unwichtig }
Nun das Problem: Ich binde in C# die DLL ein und rufe genau diese Funktion auf. Dabei zeigt sich, was ich auch schon oben im Code notiert habe: Der einzelne GdiplusPoint kommt korrekt an, jedoch das Array enthält nur Schrott. Riesengroße Werte für X und Y. Und auch die Zeiger (die ja in dem Array stehen müssten) sind ganz seltsame Adressen. z.B. müssten die Abstände der Adressen ja mind. 8byte (zwei int32 werte) betragen, da sich die Punkte ja sonst überlappen - sie haben aber teilweise nur einen Abstand von 2 byte...
Ausgabe des Programms nach Übergabe eines Punkts(15,15) und eines Arrays mit fünf Punkten (10, 10), (20, 10), (20, 20), (10, 20), (10, 10):
C++ Ausgabe:
pt: 15 15
15335276 15335244
15335212 15335180
15335148 1697919801
1998032 192
100663355 100663356
Zeiger der pts im Array:
001E7E00
001E7E08
001E7E10
001E7E18
001E7E20
Ende der AusgabeEs ist mir echt rätselhaft, was ich noch falsch mache, ich hab schon tausend verschiedene Einstellungen beim Marshalling ausprobiert, aber es kommt immer Bullshit rüber. (nur der einzelne GdiPoint ist wie gesagt immer richtig)
Noch zur Info:
Habe in C# den GdiPlus::Point zur Übergabe so nachgebaut:[StructLayout(LayoutKind.Sequential)] internal class GdiPPt { [MarshalAs(UnmanagedType.I4)] public Int32 x; [MarshalAs(UnmanagedType.I4)] public Int32 y; public GdiPPt(Point p) { x = p.X; y = p.Y; } public static implicit operator GdiPPt(Point p) { return new GdiPPt(p); } }
Dll-Import und Marshalling:
[DllImport(GEOMETRY, CallingConvention = CALL_CONV, CharSet = CHARSET, EntryPoint = "getPointPolygonSituation")] private extern static Int32 getPointPolygonSituation(GdiPPt pt, [MarshalAs(UnmanagedType.LPArray)] GdiPPt[] pptArr, Int32 ppc);
Auch mit MarshalAs(UnmanagedType.SafeArray) oder ganz ohne Marshalling passiert immer nur Müll... ich weiß nicht weiter.
Hoffe ihr könnt mir helfen...Was ich vielleicht noch erwähnen sollte: Wenn ich Structs anstatt Klassen benutze, um die Points darzustellen, funktioniert alles einwandfrei... aber das löst ja mein Problem nicht
-
Nanopixel schrieb:
Was ich vielleicht noch erwähnen sollte: Wenn ich Structs anstatt Klassen benutze, um die Points darzustellen, funktioniert alles einwandfrei... aber das löst ja mein Problem nicht
Warum nicht?
C# hat eben nun mal sehr eingeschränke Möglichkeiten ein Marschalling zu definieren...
Wenn Du wirklich oft solche Dinge machen musst, dann rat ich Dir von C# ab und schreibe lieber in C++/CLI eine Wrapper-Klasse, welche ein Managed-Interface nach aussen hat und intern die unmanaged Funktionen verwendet.
Das macht das ganze auch wesentlich übersichtlicher!
-
Hallo Jochen Kalmbach.
Das löst mein Problem insofern nicht, da GdiPlus intern mit Point Klassen arbeitet. sicher könnte ich ihm einen Batzen Point Strukturen übergeben und dann intern auf Gdiplus::Point ummodeln. Aber das ist ja nicht gerade elegant - und performant erst recht nicht (und das muss es sein).
Übrigens ist das C# Programm auch als Wrapper für die C++ DLL gedacht, so dass ich es einfach in anderen C# Programmen nutzen kann. Meinst Du wirklich es ist sinnvoller, da einen managed C++ Wrapper zu schreiben, wenn der häufigste Gebrauch in C# stattfindet?Grüße, N
-
Nanopixel schrieb:
Übrigens ist das C# Programm auch als Wrapper für die C++ DLL gedacht, so dass ich es einfach in anderen C# Programmen nutzen kann. Meinst Du wirklich es ist sinnvoller, da einen managed C++ Wrapper zu schreiben, wenn der häufigste Gebrauch in C# stattfindet?
Das hat doch mit dem gebraucht nichts zu tun!
Das Ausschlagende ist: Musst Du viel Interop in der Assembly machen oder nur sehr wenig InterOp und dies auch nur sehr simplel?
Falls das erstere der Fall ist, dann führt kein Weg an C++/CLI vorbei. Die daraus erstellte DLL kannst Du natürlich immer (oder nur) in C# verwenden...
-
Mag zwar sein, dass dieses Vorgehen auch zum Ziel führt, aber das kann doch nicht die allgemein übliche Vorgehensweise sein wenn man vor derartigen Problemen steht.
Dieses Vorgehen löst doch das eigentliche Problem nicht, und zwar die Frage, warum lassen sich "einzelne" managed Klasseninstanzen von C# in unmanaged C++ übertragen, aber ein Array aus denselbigen nicht. Darum dreht sich doch alles und ich kann einfach nicht glauben, dass dafür keine Lösungsmöglichkeit exisitiert außer einen plumpen unhandlichen Wrapper zu schreiben...
Stand denn bisher niemand vor dem gleichen Problem?! (laut google Recherche anscheinend nicht...)
-
Das marshalling von Arrays von C#-Klassen funktioniert nicht so einfach, weil es sich in Wirklichkeit um Referenzen handelt. Wenn du also deinen Array übergibst, kommt am Zielort eine Liste von Zeigern auf Speicher im Besitz des GC an. Damit man aus unmanaged Code Zugriff darauf bekommt, müsste man beim Marshalling ein nicht verwaltetes Speicherabbild generieren oder den Speicher jeder Referenz im Array pinnen. Dann könnte der Zugriff erfolgen. Schliesslich müsste der gesammte Prozess rückgängig gemacht werden, d.h. das Speicherabbild zurückgeschrieben werden oder den Pin-Status jeder Referenz im Array aufgehoben werden. Wie du dir sicher vorstellen kannst, sind solche Aufrufe aber sehr teuer, da das pinnen den GC in seiner Arbeit behindert. Den Array zu kopieren ist auch nicht viel besser, da die Objekte selbst nicht in einem zusammenhängenden Speicherbereich sind. Soviel dazu weshalb vielleicht keine standardmässige Lösung für das Problem existiert.
Nanopixel schrieb:
Mag zwar sein, dass dieses Vorgehen auch zum Ziel führt, aber das kann doch nicht die allgemein übliche Vorgehensweise sein wenn man vor derartigen Problemen steht.
Möglicherweise hörst du es nicht gern, aber genau für solche Fälle benutzt der vernünftige Mensch C++/CLI. Damit hast du die volle Kontrolle über managed/unmanaged Interoperation. Zwar wirst du auch dort keine zusätzlichen Attribute für das Marshalling finden, aber zumindest sind dort unmanaged Klassen keine Fremdkörper im System.
-
Ach ja, da fällt mir gerade noch was auf :p
Nanopixel schrieb:
Das löst mein Problem insofern nicht, da GdiPlus intern mit Point Klassen arbeitet.
Schon klar arbeitet GdiPlus intern mit Klassen. Was du aber noch nicht zu verstehen scheinst ist, dass in C++ struct und class abgesehen von den standardmässigen Zugriffsrechten gleich sind. Wenn du in C++ class oder struct schreibst, kommt in C# dem ein struct am nächsten. Schreibst du aber in C# class, so gibt es dafür in C++ keine direkte Entsprechung (ausser in C++/CLI natürlich ref class). Am ehesten vergleichbar wäre es mit einem komplexen smart pointer auf eine C++ class oder struct. Die Schlüsselwörter in C++ und C# sehen vielleicht gleich aus, haben aber fundamental verschiedene Bedeutungen. Wenn du das einmal begriffen hast, wird dir klar dass deine obige Aussage leider völliger Schwachsinn ist.