C++ Dll-in C# verwenden



  • Mal was anderes: Der erste Parameter wird doch als Referenz auf int übernommen. Könnte mir vorstellen dass das nicht mit dem in C# als Wert übergebenen Argument zusammenpasst. Vermutlich wird die Funktion den Wert als Adresse interpretieren und beim Zugriff auf den eigentlichen Wert knallt's dann.



  • Neenee, das funktioniert schon alles. Mein Problem liegt woanders. In der DLL werden beim 1. Aufruf eine Reihe von globalen Objekten instanziert und dorther kommt auch die Exception. Seltsamerweise aber nur, wenn die DLL von C# aus aufgerufen wird. Eine C++ Applikation hat damit keine Probleme...

    Der Funktionsaufruf an sich funktioniert so wie angegeben einwandfrei und ohne Fehler. Hab ich mit einer kleinen Testanwendung schon durchgeprüft. Trotzdem Danke für den Tipp 🙂



  • Im Übrigen - auch das mit der Callback-Funktion konnte ich nun lösen.
    Wie das geht, hab ich hier gefunden:

    http://msdn2.microsoft.com/de-de/library/843s5s5x(VS.80).aspx





  • Hmm...
    Wenn ich das recht verstehe, müsste man mit StringBuilder doch vorher schon wissen, wie lang der String werden könnte, oder? Wenn das so ist, empfinde ich das für einen Parameter-Rückgabewert doch aber eher als Nachteil. Die C++-DLL kann sich nicht um die Speicherorganisation des StringBuilder-Objekts kümmern. Und die Größe des Textes kann beliebig sein...

    In meinem Fall handelt es sich in der DLL tatsächlich um einen Null-terminierten Text, der auch nach Aufruf der externen Funktion noch im Speicher weiterexistiert. Aus diesem Grund wird auch nur ein Zeiger auf den Textanfang zurückgegeben. Meines Erachtens dürfte dafür ein string-Objekt doch ausreichend sein. Oder verstehe ich da etwas falsch?



  • Ja:

    Der wichtigste Unterschied zwischen den beiden Klassen besteht darin, dass Zeichenfolgen unveränderlich sind, während der Inhalt von StringBuilder-Puffern durch den Aufgerufenen geändert und zurück in den Aufrufer kopiert werden kann.

    Du kannst natürlich auch einen IntPtr per ref oder out übergeben, und dann den Speicher parsen bis du auf die Nullterminierung stösst. Wenn du aber ungefähr sagen kannst, wie lang deine Zeichenkette maximal ist, würde ich einen Stringbuilder empfehlen.



  • Ok, es geht weiter...

    Ein Problem besteht bei Aufruf der Callback-Funktion, wenn Parameter mit übergeben worden sind. Eine Callback-Funktion ohne Parameter wird anstandslos aufgerufen, kehrt zurück zur DLL ohne Fehlermeldung.
    Sobald ich jedoch die Callbackfunktion mit einem Parameter versehe (der Einfachheit halber ein int-Parameter), wird bei der Rückkehr zur DLL folgende Fehlermeldung erzeugt:

    The value of ESP was not properly saved across a function call.  
    This is usually a result of calling a function declared with one calling 
    convention with a function pointer declared with a different calling convention.
    

    Wenn ich das richtig sehe, besagt die Meldung, dass der Stack nach Rückkehr aus der Callback-Funktion nicht mehr stimmt. Genauer gesagt, dass die Größe des übergebenen Parameters in der Deklaration der Callbackfunktion (C++) auf der einen Seite, und die Größe, die sie letztendlich in der dann definierten Callbackfunktion auf der anderen Seite (C#) nicht übereinstimmen.

    Wie kann das denn sein? Sind die Aufrufmechanismen für Methoden bei beiden Systemen denn unterschiedlich?

    Damit es nachvollziehbar wird, gebe ich hier einmal den kompletten Quelltext der DLL und des aufrufenden C#-Programms an.

    DLL-Header

    #pragma once
    
    #ifdef DLLCSTEST_EXPORTS
    #define DLLCSTEST_API extern "C" __declspec(dllexport)
    #else
    #define DLLCSTEST_API __declspec(dllimport)
    #endif
    
    //	Callback-Funktionstyp deklarieren
    typedef void tCallback (int Wert1);
    typedef tCallback* tpCallback;
    
    namespace MyNamespace
    {
      // exportierte Funktionen
      DLLCSTEST_API int fnDllCsTest(int a);
      DLLCSTEST_API void SetCallback(tpCallback Callback);
    }
    

    DLL-Body

    #include "stdafx.h"
    #include "DllCsTest.h"
    
    tpCallback g_pCallback = NULL;
    
    BOOL APIENTRY DllMain( HANDLE hModule, 
                           DWORD  ul_reason_for_call, 
                           LPVOID lpReserved)
    {
      return TRUE;
    }
    
    // Callback-Funktionen setzen
    void MyNamespace::SetCallback(tpCallback Callback)
    {
      g_pCallback = Callback;
    }
    
    // Testfunktion, um Callback aufzurufen
    int MyNamespace::fnDllCsTest(int a)
    {
      if (g_pCallback)
      {
        g_pCallback(a);  // Aufruf der Callback-Funktion
      }
      return a;
    }
    

    C# - Hauptprogramm

    using System;
    using System.Collections.Generic;
    using System.Windows.Forms;
    
    namespace WindowsApplication1
    {
      static class Program
      {
        /// <summary>
        /// Der Haupteinstiegspunkt für die Anwendung.
        /// </summary>
        [STAThread]
        static void Main()
        {
          Application.EnableVisualStyles();
          Application.SetCompatibleTextRenderingDefault(false);
    
          // Callback-Funktionszeiger erzeugen und übergeben
          Callback myCallback = new Callback(Program.DllCallback);
          DllCsTest.SetCallback(myCallback);
    
          int a;
          // Callback-Aufruf auslösen
          a = DllCsTest.fnDllCsTest(1);
    
          Application.Run(new Form1());
        }
    
        public static void DllCallback(int Wert1)
        {
          return;  // der Inhalt von Wert1 stimmt an dieser Stelle
        }
      }
    }
    

    C# - DLL-Anbindung

    using System;
    using System.Collections.Generic;
    using System.Runtime.InteropServices;
    
    // Callback-Funktionstyp definieren
    public delegate void Callback(int Wert1);
    
    class DllCsTest
    {
      [DllImport("DllCsTest.dll")]
      public static extern int fnDllCsTest(int a);
      [DllImport("DllCsTest.dll")]
      public static extern void SetCallback(Callback pFn);
    }
    

    Testweise kann man ja mal den Wert1 der Callback-Funktion weglassen, damit man sieht, dass der Aufruf an sich korrekt funktioniert.
    Der Wert, der an die Callback-Funktion übergeben wird, hat übrigens innerhalb der Callback-Funktion den korrekten Inhalt.

    Tja, kann mir da jemand einen Tipp geben, woran das Problem liegen könnte?
    Besten Dank schon mal im Voraus,
    Wes



  • Ergänzung:

    Hier noch etwas für die Insider, die auch ein wenig Assembler können...
    Ich hab mir mal den Assembler-Code angeschaut und die Register vor und nach dem Aufruf. Und da habe ich folgendes festgestellt:

    - die zu übergebenden Parameter werden vor Aufruf der Callback-Funktion ordnungsgemäß auf den Stack gelegt.
    - die Callback-Funktion unter C# fragt die Parameter ordnungsgemäß vom Stack ab
    - Und jetzt kommts! Die Parameter werden noch VOR dem Rücksprung ins aufrufende Programm vom Stack entfernt! Schlimm deshalb, weil das normalerweise doch erst NACH dem Rücksprung ins aufrufende Programm erledigt werden soll (und auch wird)!
    - nach Rückkehr, werden die Parameter nochmals vom Stack genommen, was natürlich nun zu einem völlig falschem Stackpointer führt!

    Wie kann denn sowas sein? Gibt es dafür irgendwelche Schalter oder Optionen, mit denen man das korrigieren kann? Eventuell gibt es ja noch bestimmte Aufrufkonventionen, die dem C++ Programm sagen, dass es sich bei der aufgerufenen Funktion um eine aus C# handelt und darum anders zu handhaben ist...

    Ich bin da echt ein bisschen ratlos...



  • Gut, nun zum Abschluss und damit nachfolgende Sucher auch eine Lösung bekommen:

    Wie das mit den Aufrufen und der korrekten Behandlung des Stacks geht, dazu hab ich im Thread Aufruf-Konventionen ändern - wie geht das? was geschrieben.

    Ein weiterer Punkt war ja noch, dass die Parameter nicht korrekt übergeben worden sind. Vor allem, wenn es sich um Pointer (IntPtr) oder strings oder Rückgabewerte handelt, gab es massiv Probleme. Die Lösung liegt darin, dass ALLE Parameter, die an eine C++-DLL-Funktion übergeben werden, als ref deklariert sein müssen und nicht als out! Anderenfalls werden z.B. die Inhalte der Pointer und nicht die Pointer selbst als int-Wert übergeben, was natürlich falsch ist.

    Gruß,
    Wes



  • Irgendeine Chance die zwei Beiträge in die FAQ zu bekommen ?

    Sicherlich wird das nicht häufig gefragt, schaut aber nach sehr viel Knobeln aus, so das jemand mit gleichen Problemen auf die Arbeit von Wesley67 zurück greifen kann. (Das Forum wird ja nun gut Inidizert bei Google)


Anmelden zum Antworten