Aufruf-Konventionen ändern - wie geht das?



  • (Anm. vorab: Dies ist kein Cross-Posting, da es sich hier tatsächlich um eine neue Problematik handelt)

    Hallo zusammen,

    wie ja schon aus dem Beitrag C++ Dll-in C# verwenden zu ersehen war, kämpfe ich mit den Tücken der beiden Systeme. Insbesondere bei einer Callback-Funktion, die aus einer C++-DLL heraus aufgerufen wird, aber in C# geschrieben ist.

    Insbesonder geht es darum, dass die Aufrufkonventionen beider Systeme unterscheidlich sind. So werden in C++ standardmäßig die Übergabeparameter nach einem Funktionsaufruf vom Aufrufer wieder vom Stack entfernt. In C# dagegen macht es jedoch standardmäßig der Aufgerufene.
    Um das Problem zu lösen, gibt es für die Implementierung von C++-DLLs in C# Aufrufkonventionen (wie z.B. CallingConvention.Cdecl oder CallingConvention.StdCall), die bei der Import-Deklaration der einzelnen Funktionen angegeben werden müsen.

    Soweit so gut. Doch leider gelten diese Aufruf-Konentionen ebend nur für den Import von Funktionen aus einer DLL. Wie schaut das aber nun in meinem Fall aus? Wie sage ich einer in C# programmierten Methode, dass sie von einer C++-Komponente als Callback-Funktion aufgerufen wird und deshalb die Parameter nicht selbst vom Stack zu nehmen hat?

    Ich würde mich freuen, wenn sich jemand damit auskennt und mir einen Tipp geben könnte.

    Wes



  • Hier nun die Lösung:

    Es gibt keinen direkten Weg, um die Aufrufkonventionen unter C# für diesen Fall zu ändern. Aber es existieren trotzdem noch 2 Wege, die möglich sind.

    Der 1. Weg ist eine nachträgliche Manipulation des IL-Codes der C#-Applikation, die die DLL verwenden will, und wird hier beschrieben:
    http://www.codeproject.com/csharp/cdeclcallback.asp?df=100&forumid=248333&exp=0&select=1317814

    Allerdings ist das eine äußerst fragwürdige Methode, da sie während der Entwicklung einen erheblichen Mehraufwand bedeutet.

    Die Alternative ist folgende:
    Die Callback-Funktion wird in der C++-DLL als __stdcall deklariert.

    Dll-Header-Datei

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

    Diese Deklaration bewirkt, dass die Übergabeparameter IMMER von der aufgerufenen Funktion selbst entfernt werden. C++-Aufrufe können das so berücksichtigen.

    Zu beachten ist dann lediglich (für C++-Applikationen, die diese DLL verwenden wollen), dass auch Callback-Funktionen, die unter C++ angelegt und eingebnden werden, ebenfalls so zu deklarieren sind. Aber das ist kaum eine Mühe und bringt auch keine Nachteile.

    So kann die Callback-Möglichkeit sowohl von C++- als auch von C#-Anwendungen ohne Änderung in der DLL genutzt werden.

    Gruß,
    Wes


Anmelden zum Antworten