struct-Array an C++-DLL übergeben



  • Eines vorneweg: Ich habe von C# keine Ahnung, muss nun aber kurzzeitig damit arbeiten.

    Ich möchte eine C++-Funktion einer DLL aus C# heraus aufrufen und ein struct-Array übergeben. Die C++-Funktion braucht einen Pointer auf folgenden Typ:

    typedef struct tag_dblcol {
      double Rnom,Gnom,Bnom;
      double Reff,Geff,Beff;
    } T_Colors;
    

    In C# habe ich versucht, diesen nachzubilden:

    public struct T_Colors
    	{
    	  public double Rnom;
    	  public double Gnom;
    	  public double Bnom;
    	  public double Reff;
    	  public double Geff;
              public double Beff;
    	}
    

    Dann bilde ich folgendermaßen ein Array:

    T_Colors[] pColors = new T_Colors[140];
    

    Das Array beinhaltet korrekte Daten. Die C++-Funktion wird hier importiert und aufgerufen:

    [DllImport("Rgne.dll")] public static extern int StartCalc(out T_Colors[] pColors);
    
    //Aufruf
    Rgne.StartCalc(out pColors);  //hier sind die Daten noch in Ordnung
    

    Auf der C++-Seite jedoch kommt das Array nicht so wirklich an. Die Deklaration sieht so aus:

    int StartCalc(T_Colors *pColors) { /*...*/ }
    

    Das sieht irgendwie nach nicht initialisiertem oder auch falsch interpretiertem Speicher aus (Werte wie 7.63918e-313 statt 253.0). Was mache ich falsch? Wie kriege ich das blöde struct-Array 'rüber in die DLL?

    Ergänzung: Ich lese das Array in einer Schleife, und er scheint immer bei Element 118 (von 140) eine Schutzverletzung hervorzurufen. Ist das vielleicht ein Hinweis darauf, dass der C#- und der C++-Typ incht die gleiche Länge haben?



  • Nimm mal das "out" bei der Deklaration weg. Ich dachte zwar selbst, dass mehr nötig ist, aber das scheint zu reichen.



  • Ok, das sorgt tatsächlich dafür, dass ich die Daten zumindest verarbeiten kann. Allerdings wird nun logischerweise eine Kopie erzeugt (ist 'ne andere Speicheradresse). Ich müsste das Array eigentlich per Referenz übergeben, da an der aufrufenden Stelle noch mit den kalkulierten Daten gearbeitet werden muss. Gibt es da keine Möglichkeit?



  • _matze schrieb:

    Ok, das sorgt tatsächlich dafür, dass ich die Daten zumindest verarbeiten kann. Allerdings wird nun logischerweise eine Kopie erzeugt (ist 'ne andere Speicheradresse). Ich müsste das Array eigentlich per Referenz übergeben, da an der aufrufenden Stelle noch mit den kalkulierten Daten gearbeitet werden muss. Gibt es da keine Möglichkeit?

    Hm, eigentlich sollte es so gehen. Du veränderst ja nicht das Array (= den Zeiger) sondern die enthaltenen Daten.



  • Laut einem Kollegen verarbeitet C# solche Arrays of structs wohl anders als C++, nämlich nicht als zusammenhängenden Speicherblock, sondern als Array of Pointers to Structs, wenn ich das richtig verstanden habe (und wenn es stimmt). Das wäre zumindest eine Erklärung für meinen Datensalat => ich sehe eigentlich Speicheradressen. Eine andere Erklärung habe ich nicht, ein double ist in C# ebenso 64 Bit groß, die Speicheradresse von pColors ist auf der C++ dieselbe wie vorher in C#, und schon der erste Wert stimmt nicht überein. Eine Lösung weiß der Herr Kollege allerdings auch nicht...



  • _matze schrieb:

    Laut einem Kollegen verarbeitet C# solche Arrays of structs wohl anders als C++, nämlich nicht als zusammenhängenden Speicherblock, sondern als Array of Pointers to Structs

    Nein, das ist falsch. .NET unterscheidet zwischen Werte- und Referenztypen. Referenztypen werden in der Tat wie beschrieben verwaltet (das bezieht sich dann aber nicht nur auf Arrays). Structs hingegen sind immer Wertetypen und entsprechen (für solche Belange) ziemlich genau den ganz normalen C++-Datentypen (wohingegen Referenztypen eine Art verwalteter Zeiger sind).



  • gut, ich kriege es so wohl nicht hin. Ich werde als nächstes versuchen, das Array aufzusplitten in 6 double-Arrays. Vielleicht habe ich da mehr Erfolg... Zumindest einzelne int und double Werte per Referenz waren bislang kein Problem...



  • Aber ist ein Array von Wertetypen nicht selbst ein Referenztyp?



  • ich hatte mal ein ähnliches problem, finde den code aber leider nicht mehr. ich kann mich wage erinnern einen struktur prototypen als class in c# deklariert zu haben, welche das selbe layout wie die c struktur besaß (besitzte? :s). an die importierte c funktion habe ich dann einen IntPtr auf eine instanz dieser klasse übergeben.



  • sothis_ schrieb:

    ich hatte mal ein ähnliches problem, finde den code aber leider nicht mehr. ich kann mich wage erinnern einen struktur prototypen als class in c# deklariert zu haben, welche das selbe layout wie die c struktur besaß (besitzte? :s). an die importierte c funktion habe ich dann einen IntPtr auf eine instanz dieser klasse übergeben.

    Interessant! Werde ich morgen ausprobieren. Jetzt ist erstmal Feierabend! 😃



  • LordJaxom schrieb:

    Aber ist ein Array von Wertetypen nicht selbst ein Referenztyp?

    Ja, das ist richtig. Daher ist auch die explizite ref -Übergabe unnötig. Eventuell marshallt sich .NET da Mist zusammen, aber eigentlich sollte das für so'n Vanilla-Array wunderbar funktionieren.

    Ansonsten versuch's halt mal mit [MarshalAs(UnmanagedType.LPArray)] aber das sollte nichts ändern.



  • sothis_ schrieb:

    ich hatte mal ein ähnliches problem, finde den code aber leider nicht mehr. ich kann mich wage erinnern einen struktur prototypen als class in c# deklariert zu haben, welche das selbe layout wie die c struktur besaß (besitzte? :s). an die importierte c funktion habe ich dann einen IntPtr auf eine instanz dieser klasse übergeben.

    Das könnte sogar funktionieren, wenn man aufpasst, es ist aber riskant, und codelastig. Man muss sich nämlich einen pinned-Pointer besorgen, damit der GC nicht anfängt, die Klasse plötzlich woanders hinzuräumen. Außerdem ist es mit Kanonen auf Spatzen, denn hier soll ja mit Arrays gehandelt werden.



  • Konrad Rudolph schrieb:

    sothis_ schrieb:

    ich hatte mal ein ähnliches problem, finde den code aber leider nicht mehr. ich kann mich wage erinnern einen struktur prototypen als class in c# deklariert zu haben, welche das selbe layout wie die c struktur besaß (besitzte? :s). an die importierte c funktion habe ich dann einen IntPtr auf eine instanz dieser klasse übergeben.

    Das könnte sogar funktionieren, wenn man aufpasst, es ist aber riskant, und codelastig. Man muss sich nämlich einen pinned-Pointer besorgen, damit der GC nicht anfängt, die Klasse plötzlich woanders hinzuräumen. Außerdem ist es mit Kanonen auf Spatzen, denn hier soll ja mit Arrays gehandelt werden.

    in erster linie ging es doch um strukturen? iirc hatte ich die lösung auch irgendwo von einem microsoft forum und schien die 'richtige' zu sein wenn man zeiger/referentzen auf structs zwischen managed und unmanaged code übergibt. dies wie auch immer funktioniert aber tatsächlich nur wenn in der betreffenden struktur generische datentypen vorhanden sind, die sich 1:1 von c/c++ auf c# abbilden lassen, also z.B. int/Int, oder bool/Bool.

    edit: oh, und was meinst du mit codelastig? viel zu schreiben war es jedenfalls nicht 🙂



  • sothis_ schrieb:

    Konrad Rudolph schrieb:

    sothis_ schrieb:

    ich hatte mal ein ähnliches problem, finde den code aber leider nicht mehr. ich kann mich wage erinnern einen struktur prototypen als class in c# deklariert zu haben, welche das selbe layout wie die c struktur besaß (besitzte? :s). an die importierte c funktion habe ich dann einen IntPtr auf eine instanz dieser klasse übergeben.

    Das könnte sogar funktionieren, wenn man aufpasst, es ist aber riskant, und codelastig. Man muss sich nämlich einen pinned-Pointer besorgen, damit der GC nicht anfängt, die Klasse plötzlich woanders hinzuräumen. Außerdem ist es mit Kanonen auf Spatzen, denn hier soll ja mit Arrays gehandelt werden.

    in erster linie ging es doch um strukturen?

    Ja. Und? Zusammenhang?

    iirc hatte ich die lösung auch irgendwo von einem microsoft forum und schien die 'richtige' zu sein wenn man zeiger/referentzen auf structs zwischen managed und unmanaged code übergibt.

    Das kann ich mir nicht vorstellen, denn genau dafür ist ja das (automatische!) Marshalling zwischen Managed und Unmanaged Code da.

    edit: oh, und was meinst du mit codelastig? viel zu schreiben war es jedenfalls nicht 🙂

    Na doch, da Du ja eine unsafe-Methode hast, in der Du zuerst den Zeiger pinnen musst, hast Du auf jeden Fall mehr zu schreiben (= mehr Möglichkeiten, Fehler zu machen) als wenn Du die Methode direkt aufrufst.



  • Sollte man nicht auch immer diese "layout sequential" Property bei der Struktur mit angeben wenn man die mit PInvoke verwenden will?



  • hustbaer schrieb:

    Sollte man nicht auch immer diese "layout sequential" Property bei der Struktur mit angeben wenn man die mit PInvoke verwenden will?

    Ja, das ist in der Tat sauberer, hat aber mit dem Problem nichts zu tun, da ab Visual Studio 2005 (bzw. der damit verbundenen Version der Compiler) das Sequential-Layout die Standardeinstellung ist.



  • Konrad Rudolph schrieb:

    hustbaer schrieb:

    Sollte man nicht auch immer diese "layout sequential" Property bei der Struktur mit angeben wenn man die mit PInvoke verwenden will?

    Ja, das ist in der Tat sauberer, hat aber mit dem Problem nichts zu tun, da ab Visual Studio 2005 (bzw. der damit verbundenen Version der Compiler) das Sequential-Layout die Standardeinstellung ist.

    Ich nutze noch das VS2005. Also könnte das was mit dem Problem zu tun haben?



  • Nö daran wirds nicht liegen wenn das sowieso ab 2005 Standard ist (wusste ich nicht, obwohl ich 2005 verwende 🙂 ).
    Wenn du in/out brauchst kann ich dir leider auch nicht helfen.



  • hustbaer schrieb:

    Nö daran wirds nicht liegen wenn das sowieso ab 2005 Standard ist (wusste ich nicht, obwohl ich 2005 verwende 🙂 ).
    Wenn du in/out brauchst kann ich dir leider auch nicht helfen.

    Jetzt macht meine Frage auch keinen Sinn mehr. Gestern stand bei Konrad Rudolphs Post auch noch 2008... 😃



  • _matze schrieb:

    hustbaer schrieb:

    Nö daran wirds nicht liegen wenn das sowieso ab 2005 Standard ist (wusste ich nicht, obwohl ich 2005 verwende 🙂 ).
    Wenn du in/out brauchst kann ich dir leider auch nicht helfen.

    Jetzt macht meine Frage auch keinen Sinn mehr. Gestern stand bei Konrad Rudolphs Post auch noch 2008... 😃

    Sorry, das ist richtig. Ich hatte den Fehler in meinem Posting gleich bemerkt und editiert, daher habe ich mir den „Edit“-Hinweis geschenkt. Du bist mit Deiner Frage anscheinend genau so dazwischengekommen, dass Du noch die falsche Zahl gelesen hast, aber schon die richtige zitiert.


Anmelden zum Antworten