Von C# auf C++ DLL zugreifen - Methoden mit Referenzen



  • Hallo zusammen,

    ich bin gerade dabei mich in das Thema C++ DLLs in C# einzuarbeiten.
    Und zwar habe ich eine alte C++ DLL, deren Funktionen ich nicht ändern kann/darf. Um diese DLL habe ich eine managed C++ DLL gebastelt, die die selben Funktionen zur Verfügung stellt.

    Nun gibt es in der DLL leider Funktionen, die Referenzen auf Variablen erwarten um diese ändern zu können.

    Nun habe ich in der ursprünglichen DLL eine Funktion

    short getParameters( bool& isA, bool& isB, bool isC );
    

    In meiner managed C++ DLL sieht diese Funktion wie folgt aus

    short getParameters( bool% isA, bool% isB, bool% isC )
    {
       // call native DLL
       return nativeDLL::getParameters( isA, isB, isC );
    }
    

    Der zugriff in C# erfolgt dann so

    short getParametersFromDLL( ref bool isA, ref bool isB, ref bool isC )
    {
       // call managed DLL
       return managedDLL.getParameters( isA, isB, isC );
    }
    

    Leider führt das dann zu dem Fehler beim Compilieren der managed DLL.

    C2664 cannot convert parameter from bool to bool &.

    Ändere ich nun die Funktion in der managed DLL auf

    short getParameters( bool& isA, bool& isB, bool& isC )
    {
        // call native DLL
        return nativeDLL::getParameters( isA, isB, isC );
    }
    

    baut zwar die DLL aber in C# habe ich das Problem

    Argument 1: cannot convert from 'bool' to 'bool*'

    Kann mir jemand helfen? Das muss doch bestimmt irgendwie machbar sein, oder?



  • Muss der Umweg über C++/CLI sein ?
    Ansonsten einfach:

    short getParameters( bool& isA, bool& isB, bool isC );
    
    [DllImport("DllName", EntryPoint = "FunctionName")]
    short getParameters(ref bool isA, ref bool isB, bool isC);
    


  • @Almeida:

    Wenn Du ohnehin eine C++/CLI Zwischenschicht hast (so verstehe ich Deinen Code), könntest Du es auch so machen:

    namespace MyNamespace
    {
       public ref class WrapperClass
       {
       public:
          WrapperClass(void)
          {
             m_instance = new NativeDLL(/*...*/);
          }
    
          ~WrapperClass(void)
          {
             if(m_instance)
             {
                delete m_instance;
                m_instance = NULL;
             }
          }
    
          !WrapperClass(void)
          {
             if(m_instance)
             {
                delete m_instance;
                m_instance = NULL;
             }
          }
    
          short getParameters(bool% isA, bool% isB, bool% isC)
          {
             pin_ptr<bool> isA_ptr = &isA;
             pin_ptr<bool> isB_ptr = &isB;
             pin_ptr<bool> isC_ptr = &isC;
             return m_instance->getParameters(*isA_ptr, *isB_ptr, *isC_ptr);
          }
    
       private:
          NativeDLL *m_instance;   
       }
    }
    

    Auf der C#-Seite lässt es sich dann direkt wie eine "normale" .NET Klasse verwenden:

    using(wrapper = new MyNamespace.WrapperClass())
    {
       wrapper.getParameters(/*...*/)
    }
    


  • Hallo Leute,

    vielen Dank für eure Antworten!

    @DarkShadow44:
    Soweit ich mich auskenne muss dieser Umweg sein, um eine unmanaged C++ DLL verwenden zu können.

    @DeathCubeK:

    vielen, vielen Dank! Das funktioniert so. Kann es sein, dass ich nach dem Aufruf der

    m_instance->getParameters(*isA_ptr, *isB_ptr, *isC_ptr);
    

    den Zustand der pin_ptr wieder irgendwie auf die ursprünglichen %-Variablen übertragen muss?
    Also so etwas

    short getParameters(bool% isA, bool% isB, bool% isC)
    {
        pin_ptr<bool> isA_ptr = &isA;
        pin_ptr<bool> isB_ptr = &isB;
        pin_ptr<bool> isC_ptr = &isC;
    
        short result = m_instance->getParameters(*isA_ptr, *isB_ptr, *isC_ptr);
    
        &isA = isA_ptr;
        &isB = isB_ptr;
        &isC = isC_ptr;
    
        return result;
    }
    

    Und noch eine Frage. Es gibt, in der DLL, noch die Funktion

    short saveState( void*& statePtr );
    

    Gibt es einen Weg diese Funktion zu verwenden? Wenn mich nicht alles täuscht ist doch IntPtr intern auch ein void* oder? Kann ich diesen irgendwie verwenden?

    short saveState( IntPtr t_statePtr )
    {      
        pin_ptr<void> a_statePtr = &t_statePtr;
    
        return m_instance->( *a_statePtr );
    }
    

    liefert mir leider den Fehler

    C2664: cannot convert parameter 1 from 'cli::pin_ptr<Type>' to 'void *&'
    


  • Almeida schrieb:

    Kann es sein, dass ich nach dem Aufruf der

    m_instance->getParameters(*isA_ptr, *isB_ptr, *isC_ptr);
    

    den Zustand der pin_ptr wieder irgendwie auf die ursprünglichen %-Variablen übertragen muss?

    Nein, das ist nicht notwendig.

    Der** pin_ptr **gibt dir ja einen Zeiger auf die tatsächlichen Daten (und verhindert außerdem, dass der Garbage Collector diese Daten verschiebt), so dass Änderungen die Du über diesen Zeiger an den Daten vor nimmst sofort sichtbar werden.

    Was Du alternative machen könntest, wenn Du es nicht mit Zeigern "in place" machen willst (sondern durch hin und her kopieren), wäre natürlich:

    short getParameters(bool% isA, bool% isB, bool% isC)
    {
        bool a = isA;
        bool b = isB;
        bool c = isC;
    
        short result = m_instance->getParameters(a, b, c);
    
        isA = a;
        isB = b;
        isC = c;
    
        return result;
    }
    

    Almeida schrieb:

    short saveState( IntPtr t_statePtr )
    {      
        pin_ptr<void> a_statePtr = &t_statePtr;
     
        return m_instance->( *a_statePtr );
    }
    

    Versuch es mal so:

    short saveState(IntPtr %t_statePtr)
    {      
       void *temp = t_statePtr.ToPointer();
       short result = m_instance->saveState(temp);
       t_statePtr = IntPtr(temp);
    }
    

    Deiner Beschreibung nach, erwartet saveState() ja eine Referenz auf einen Pointer, d.h. diese Funktion wird den Pointer vermutlich neu zuweisen. Daher sollte auch t_statePtr eine ref-Parameter sein.

    Was Du mit einem IntPtr auf der C#-Seite Anfangen kannst bleibt allerdings die Frage...



  • Hi DeathCubeK,

    wow, super! Das hat wieder funktioniert! Vielen Dank auch!

    Mit dem Pointer will ich auch nicht viel anfangen. Es soll nur der Zustand des Pointers gespeichert werden.

    Vielleicht als Hintergrund: meine ursprüngliche DLL ist eine 32 Bit DLL, die jetzt in einem 64 Bit Programm verwendet werden soll.
    Hierzu sollen die Funktionen der DLL über einen WCF Service bereitgestellt werden. Das 64 Bit Programm ist wieder in C++.



  • Almeida schrieb:

    Vielleicht als Hintergrund: meine ursprüngliche DLL ist eine 32 Bit DLL, die jetzt in einem 64 Bit Programm verwendet werden soll.
    Hierzu sollen die Funktionen der DLL über einen WCF Service bereitgestellt werden. Das 64 Bit Programm ist wieder in C++.

    Klingt nach ziemlich viel Aufwand. Kann man die Bibliothek nicht einfach nach 64-Bit portieren?

    (Sollte, sofern halbwegs "sauber" programmiert wurde und sofern man an den Quellcode heran kommt, ja nicht das große Problem sein)



  • Almeida schrieb:

    Hierzu sollen die Funktionen der DLL über einen WCF Service bereitgestellt werden. Das 64 Bit Programm ist wieder in C++.

    Dann implementier' doch gleich den Service-Contract in C++/CLI.



  • Hi zusammen,

    @DeathCubeK:
    leider kann man die Bibliotheken nicht nativ portieren. Dort hängt so viel uralter Kram mit dran, wo leider kein Zugriff auf die Sourcen besteht.
    Deine Einschätzung ist auch richtig. Es ist verdammt viel Aufwand, der noch dadurch erschwert wird, dass ich bisher noch kein CLI verwendet habe und WCF auch neues ist.

    @hustbaer:
    Du meinst den WCF-Service in C++/CLI schreiben? Mir wurde bisher überall empfohlen das nicht zu machen, da es über C# viel einfacher/schneller sein soll.
    Bist du da anderer Meinung?



  • Almeida schrieb:

    Soweit ich mich auskenne muss dieser Umweg sein, um eine unmanaged C++ DLL verwenden zu können.

    Nein, das muss man nciht verwenden.

    Ich würde ja immernoch dazu raten, C++/CLI komplett wegzulassen. Ich glaube schon dass der Service in C# einfacher umzusetzen ist, und die Anbindung an die native DLL ebenfalls. 🙂



  • DarkShadow44 schrieb:

    Ich würde ja immernoch dazu raten, C++/CLI komplett wegzulassen. Ich glaube schon dass der Service in C# einfacher umzusetzen ist, und die Anbindung an die native DLL ebenfalls. 🙂

    Wie greifst Du denn aus C# heraus direkt, d.h. ohne den Umweg über C++/CLI, auf eine "native" C++ (nicht C) Bibliothek zu? 😕

    Und damit meine ich wirklich dass Du eine native C++ Klasse, die aus der DLL exportiert wird, instantiieren willst, um anschließend Methoden des erzeugten Objekts aufzurufen. Vllt müssen sogar mehrere Objekte dieser Klasse erzeugt und verwaltet werden.

    Dass es mit reinen C Bibliotheken relativ einfach geht ist mir übrigens klar (keine Klassen, keine Namesppaces, kein Name-Mangling, etc). Mich würde daher die C#-Syntax für native C++ Bibliotheken/Klassen interessieren, sofern es das gibt...

    M$ selbst scheint hier den Weg über CLI/C++ vorzusehen:
    http://msdn.microsoft.com/en-us/library/ms235281.aspx



  • DeathCubeK schrieb:

    Wie greifst Du denn aus C# heraus direkt, d.h. ohne den Umweg über C++/CLI, auf eine "native" C++ (nicht C) Bibliothek zu? 😕

    Schau mal auf Codeproject - How to marshal a C++ class, da wird das ganz gut erklärt.
    Da die Dll ja nicht verändert werden darf, würde ich die Funktionen ganz einfach über den gemangelten Namen importieren, dann einen kleinen nativen Part der die Klassen erstellt/zerstört. Ok, auch das lässt sich in C# mache, aber wie verlässlich das ist kann ich dir nicht sagen. 😃

    class __declspec(dllexport) NativeClass
    {
    private:
    	int member1, member2;
    public:
    	NativeClass(int member2_new)
    	{
    		member1 = 42;
    		member2 = member2_new;
    	}
    
    	~NativeClass()
    	{
    	}
    	int getMember1(int mult)
    	{
    		return member1*mult;
    	}
    	int getMember2(int mult)
    	{
    		return member2*mult;
    	}
    };
    
    class Program
        {
            [DllImport("native.dll", EntryPoint = "?getMember1@NativeClass@@QAEHH@Z", CallingConvention = CallingConvention.ThisCall)]
            extern static int getMember1(IntPtr obj, int mult);
    
            [DllImport("native.dll", EntryPoint = "?getMember2@NativeClass@@QAEHH@Z", CallingConvention = CallingConvention.ThisCall)]
            extern static int getMember2(IntPtr obj, int mult);
    
            [DllImport("native.dll", EntryPoint = "??0NativeClass@@QAE@H@Z", CallingConvention = CallingConvention.ThisCall)]
            extern static int Contructor(IntPtr obj, int member2);
    
            [DllImport("native.dll", EntryPoint = "??1NativeClass@@QAE@XZ", CallingConvention = CallingConvention.ThisCall)]
            extern static int Destructor(IntPtr obj);
    
            static void Main(string[] args)
            {
                IntPtr obj = Marshal.AllocHGlobal(100); // Speicher anfordern der sicher größer ist als der den die Klasse braucht
                Contructor(obj, 33);
                int mem1 = getMember1(obj, 2);
                int mem2 = getMember2(obj, 2);
                Destructor(obj);
                Marshal.FreeHGlobal(obj);
            }
        }
    

    Finde ich persönlich die schönste Lösung, da komplett im managed Bereich gelöst. Allerdings wie gesagt leicht unschön.



  • Also, die "gemangelten" Methoden-Namen (einschließlich Konstruktor und Destruktor) alle von Hand importieren zu müssen und dann anschließend bei jedem Methoden-Aufruf einen nicht-typisierten Zeigen mit schleppen zu müssen, um den** thiscall zu emulieren, finde ich jetzt irgendwie alles andere als eine "schöne" Lösung. Das ist in etwa der Ansatz, mit dem man in reinem C "Klassen" nachbauen würde (Stichwort "C mit Klassen"). Fehleranfällig ist es allemal. Einmal den falschen IntPtr **übergeben und es kracht ziemlich sicher...



  • Natürlich verwendest du die Methoden so auch nicht direkt, sondern baust einen Wrapper dafür. 😉
    Dafür musst du den Service nicht in C++/CLI implementieren, und auch nicht eine weitere DLL verwalten die nur als Zwischenschicht dient.

    DeathCubeK schrieb:

    Also, die "gemangelten" Methoden-Namen (einschließlich Konstruktor und Destruktor) alle von Hand importieren zu müssen [...] finde ich jetzt irgendwie alles andere als eine "schöne" Lösung.

    Stimmt schon, wobei du dir die recht einfach rauskopieren kannst. Den Wrapper musst du ja auch so oder so von Hand schreiben. Solange sich die DLL nicht verändert bleiben die Namen ja gleich, du musst es also nur einmal machen. Kommt halt auch drauf an wie komplex die Klassen sind.

    Und nur weil es für mich die schönste Lösung ist heißt es ja nicht dass jeder so denken muss. In dem Link den ich gepostet habe stehen alle Möglichkeiten drin die man hat, jetzt musst der TS nur noch aussuchen was am besten passt. 🙂



  • Wenn Du eh eine Wrapper-Klasser baust, kannst Du das meiner Meinung nach in C++/CLI einfacher, sauberer und vor allem Typ-sicher machen. Die C++ Klasse lässt sich in C++/CLI ganz wie aus C++ gewohnt ansprechen, d.h. ohne dass man sich um Name-Mangling kümmern oder irgendwelche This-Pointer emulieren muss. Und nach außen hin lässt sich die Wrapper-Klasse dann wie eine "echte" C# Klasse benutzen. Aus meiner Sicht eine ziemlich Runde Sache. JNI, zum Beispiel, finde ich da deutlich umständlicher im Ansatz.

    Der Pferdefuß ist in der Tat dass man eine weitere DLL als "Zwischenschicht" bekommt. Da man aber sich aber mit der "nativen" DLL ohnehin externe Abhängigkeiten einhandelt, solle das in der Regel eher verschmerzbar sein...


Anmelden zum Antworten