Klasse aus C++ Dll importieren

  • VB.NET
  • .NET (FX) 4.5–4.8

Es gibt 12 Antworten in diesem Thema. Der letzte Beitrag () ist von chris_2091.

    Klasse aus C++ Dll importieren

    Hallo zusammen,

    ich habe eine c++ Dll in Visual Studio geschrieben, die eine Kommunikation zwischen ROS(Robotersteuerung) und meinem Programm in vb.net herstellt.
    Daher reicht es nicht nur einzelne Funktionen aus der DLL zu importieren sondern eine ganze Klasse.
    In der Klasse brauche ich einige Eigenschaften und muss einiges an Klassen instanziieren.

    Vorher habe ich einzelnene Funktionen wie folgt geschrieben

    C-Quellcode

    1. __declspec(dllexport) void __stdcall sum(int a, int b, int &c) {//einfache Funktion zum Testen der DLL
    2. c=a+b;
    3. }

    und in vb.net

    VB.NET-Quellcode

    1. <DllImport("libfreespace.dll", CallingConvention:=CallingConvention.Cdecl)>
    2. Public Shared Function freespace_util_getTemperature(ByRef meOutPkt As MotionEngineOutput, ByRef sensor As MultiAxisSensor) As Int32
    3. End Function


    allerdings habe ich nicht viel dazu im Internet gefunden, wie eine c++ Klasse nach vb.net exportiert wird.
    Ich denke, die folgenden Möglichkeiten bestehen meiner Meinung nach:

    1) die Klasse in c++ schreiben und eine Funktionen exportieren die die Klasse instanziiert und eine die es löscht.
    2) die Klasse in vb.net schreiben und die ganze Funktionen die nötig sind in die Klasse importieren
    3) irgendwie die ganze Klasse doch importieren

    Welche Möglichkeiten gibt es und welche funktionieren den am besten?

    *Topic verschoben*

    Dieser Beitrag wurde bereits 1 mal editiert, zuletzt von „Marcus Gräfe“ ()

    chris_2091 schrieb:

    die Klasse in c++ schreiben und eine Funktionen exportieren die die Klasse instanziiert und eine die es löscht.
    There is no way to directly use a C++ class in C# code.
    In general (across programming languages), a pointer is a number that represents a physical location in memory. A nullpointer is (almost always) one that points to 0, and is widely recognized as "not pointing to anything". Since systems have different amounts of supported memory, it doesn't always take the same number of bytes to hold that number, so we call a "native size integer" one that can hold a pointer on any particular system. - Sam Harwell
    Du hast mit allen 3 Punkten recht(Ich bin beeindruckt)

    1. Ist dabei die gängigste herangehensweise.Vergiss wie immer nicht export "C" um das name-mangling zu verhindern. Nennt man auch gerne C Bridge/Wrapper.
    Dabei wirst du aber im Prinzip diesselbe Klasse noch einmal schreiben, man kann sich natürlich ein paar Vereinfachungen machen wie z.b. das automatische binding von this an den ersten Parameter, da es jedoch bisher keine Compile-Time reflection gibt doch ziemlich nervig in C#
    Jedoch wirst du wie bei 2 für jeden Call innerhalb einer Klasse, wieder einen C-Call machen, welcher den this-Ptr als erstes Argument nimmt(Wenn C++ mit c++20 evtl. - endich - compile time reflection bekommt automatisierbar)
    2. Auch eine Mögliche herangehensweise, aber ansich eigt. genau dasselbe wie 1. außer du hast eine richtige reine C-Library.
    3. github.com/mono/CppSharp

    Es gibt sogar noch 4.
    4. C++/CLI verwenden

    Gerne von .Net/Windows entwicklern verwendet wird C++/CLI, mMn weder die Zukunft noch aktuell besonders toll.
    Am gängigsten ist wohl 1/2, da die meisten Libraries die man findet entweder reine C-Libraries sind, oder eine C-Bridge enthalten(Natürlich können auch Libraries in anderen nativen Sprachen so verwendet werden)
    3. Würde ich (bisher) eher als Hilfsprojekt für Mono sehen, kann aber tatsächlich keine Aussage darüber machen, wie gut es tatsächlich funktioniert. Vlt. funktioniert es viel besser als ich jetzt vermuten würde^^

    Also ich persönlich würde zu 1 tendieren, da du dann schön OOP in Cpp programmieren kannst, aber trotzdem ne C-Bridge hast(Oder dann zur Not halt 2 ohne Cpp und OOP).
    Eine der Hauptvorteile dabei ist, dass es relativ allgemein bleibt und von allen möglichen Sprachen verwendet werden kann von D, .Net, Java, Python bis zu allem anderen wichtigem :D
    Dabei dann gerne classname_methodname(classname* that,...)
    bzw dann classname_methodname(that as IntPtr,...)


    Noch als Tipp nebenbei:
    mach dir nen Macro für Exports/Impots

    C-Quellcode

    1. #ifdef _WIN32
    2. #ifdef BUILD_DLL
    3. #define DLL_EXPORT __declspec(dllexport)
    4. #else
    5. #define DLL_EXPORT __declspec(dllimport)
    6. #endif
    7. #else
    8. #define DLL_EXPORT
    9. #endif

    Musst in deinem Projekt, in dem du buildest noch BUILD_DLL definieren, dass beim include über ne anderes C/C++ Projekt das dann auch gefunden wird(MSVC halt), und für andere Compiler, wird da dann einfach nichts hingesetzt, da diese standardmäßig alle symbole exportieren. In CMAKE gibt es für MSVC inzwischen auch die Option alle Symbole zu exportieren(hat bei mir bisher nur noch nie funktioniert wie ich wollte).

    Und wenn dus schaffst die Library Plattformunabhängig zu programmieren. Würde ich dir empfehlen nicht auf DLL Import zurückzugreifen sondern etwas eigenes zu schreiben über LoadLibrary/dlopen bzw. GetProcAddress/dlsym. Ist zwar erst etwas mehr aufwand, aber lohnt sich meiner Meinung nach im Endeffekt, da du dann nur die C-Library pro Platform builden musst, aber der Code auf .Net Seite nur einmal geschrieben wird und immer gleich bleibt. Hast auch den Vorteil von 32-Bit vs 64-Bit. Gleich Inklusive. Natürlich musst dir halt nen Wrapper schreiben, der quasi beim start einmal sich die pointer holt. Ich hab das über Reflection gelöst. Mit welchem ich über ne Klasse geh und die delegaten raussuch, die nen Attribut haben ähnlich DllImport.
    Wär natürlich nice, wenn man direkt static extern methods machen könnte, aber dafür müsst man leider den Code hier ändern:
    https://github.com/dotnet/coreclr/blob/master/src/md/compiler/custattr_emit.cpp#L1088
    Und an der Runtime für sowas rumschrauben ist dann doch overkill(und vorallem auch nicht wirklich portabel und somit ziemlich unsinnvoll)^^

    Edit: was sicher, dass du C++/CLI genommen hast, wenn du eine Win32 Bibliothek als template genommen hast, wird zwar der MSVC genommen, aber nicht C++/CLI, somit normales natives C++. Was ich auch für Sinnvoll halte. C++/CLI hätte MS ruhig versteckt lassen können, sorgt nur dafür, dass Leute das verwenden, weil sie irwen mal sagen hören haben, das C++ besser ist. Und kombinieren dann die Nachteile von .Net mit den Nachteilen von Cpp :D(Ja ein paar Vorteile werden auch kombiniert, C++/CLI ist trotzdem kacke)
    Ich wollte auch mal ne total überflüssige Signatur:
    ---Leer---
    Vielen Dank für die ausführliche Antwort @jvbsl !!

    jvbsl schrieb:

    was sicher, dass du C++/CLI genommen hast, wenn du eine Win32 Bibliothek als template genommen hast

    Ja ich habe eine Win32 als Vorlage für meine DLL genommen.

    Also wenn ich das richtig verstanden habe, schreibe ich die Klasse ganz normal in C++ und importiere das in eine C Wrapper, die als DLL gebaut wird und diese DLL verwende ich dann in meinem Programm?
    Den C-Wrapper baust du direkt in die Library mit ein. Natürlich kannst du das auch trennen und dann beim erstellen der C-Bridge die statische Cpp Lib linken. Ist evtl. auch gar nicht so unsinnvoll, jedoch find ich kannst für die Shared-Lib immer den C-Wrapper einbauen, sonst hast du mMn zu viele unterschiedliche Versionen einer Datei...
    Ich wollte auch mal ne total überflüssige Signatur:
    ---Leer---
    @RodFromGermany danke für den Link!

    Ich habe aber noch eine Frage zum Import und zwar muss der 1.Methode irgendwie die Klasse übergeben werden.
    also so

    VB.NET-Quellcode

    1. <DllImport("testKlasse_DLL.dll", CallingConvention:=CallingConvention.StdCall)>
    2. Public Shared Function erzeugeTest() As testKlasse
    3. End Function

    natürlich könnte man eine Sub nehmen und einfach einen Zeiger übergeben

    VB.NET-Quellcode

    1. <DllImport("testKlasse_DLL.dll", CallingConvention:=CallingConvention.StdCall)>
    2. Public Shared Sub erzeugeTest(ByRef klasse as testklasse)
    3. End Sub

    soll man eine Dummy-Klasse erstellen?
    Oder habe ich da was falsch verstanden?
    C++ Klassen und .Net Klassen sind schlichtweg nicht kompatibel, andere Art von VTable, selbst die Vererbung ist anders. Und auch Klassen unterschiedlicher C++ Kompiler sind nicht miteinander kompatibel. Deshalb musst du auf IntPtr basis arbeiten. Jede wrapper klasse hält sich selbst als Handle.

    VB.NET-Quellcode

    1. Class BlaBla
    2. <DllImport("testKlasse_DLL.dll", CallingConvention:=CallingConvention.StdCall)>
    3. Public Shared Function create_blabla() As intPtr ' Kann natürlich auch woanders deklariert werden
    4. End Sub
    5. <DllImport("testKlasse_DLL.dll", CallingConvention:=CallingConvention.StdCall)>
    6. Public Shared Sub blabla_dosomething() ' Kann natürlich auch woanders deklariert werden
    7. End Sub
    8. Private handle as IntPtr
    9. Public Sub New()
    10. handle = create_blabla()
    11. End Sub
    12. Publi Sub DoSomething(x as Int32)
    13. blabla_dosomething(handle,x)
    14. End Sub
    Ich wollte auch mal ne total überflüssige Signatur:
    ---Leer---

    jvbsl schrieb:

    Deshalb musst du auf IntPtr basis arbeiten. Jede wrapper klasse hält sich selbst als Handle.
    Also sowas, wie hier: stackoverflow.com/questions/31…-sharp-code/315064#315064 ?
    In general (across programming languages), a pointer is a number that represents a physical location in memory. A nullpointer is (almost always) one that points to 0, and is widely recognized as "not pointing to anything". Since systems have different amounts of supported memory, it doesn't always take the same number of bytes to hold that number, so we call a "native size integer" one that can hold a pointer on any particular system. - Sam Harwell
    Vielen Dank! @jvbsl
    @Radinator dein Link ist sehr hilfreich

    Super das funktioniert, also man muss für jede Klassenfunktion, eine eigene Funktion schreiben, die man dann in vb.net importieren kann.
    In C++ würde das so aussehen
    Spoiler anzeigen

    C-Quellcode

    1. class test {
    2. public:
    3. int A;
    4. test() {
    5. A = 10;
    6. }
    7. int sum(int a, int b) {//einfache Funktion zum Testen der DLL
    8. return a + b;
    9. }
    10. int getA() {
    11. return A;
    12. }
    13. };
    14. extern "C" __declspec(dllexport) test* __stdcall erzeugeTest() {//Erzeuge einer Instanz
    15. return new test;
    16. }
    17. extern "C" __declspec(dllexport) void __stdcall loescheTest(test* mytest) {//Löschen der Instanz
    18. delete mytest;
    19. }
    20. extern "C" __declspec(dllexport) int __stdcall rufeSum_auf(test* mytest, int a, int b) {//Aufrufen der Fuktion Sum
    21. int c;
    22. c = mytest->sum(a, b);
    23. return c;
    24. }
    25. extern "C" __declspec(dllexport) int __stdcall rufegetA_auf(test* mytest) {//Aufrufen der Fuktion getA
    26. return mytest->getA();
    27. }

    und in vb.net dann so:
    Spoiler anzeigen

    VB.NET-Quellcode

    1. <DllImport("testKlasse_DLL.dll")>
    2. Public Shared Function erzeugeTest() As IntPtr
    3. End Function
    4. <DllImport("testKlasse_DLL.dll", CallingConvention:=CallingConvention.StdCall)>
    5. Public Shared Sub loescheTest(ByVal value As IntPtr)
    6. End Sub
    7. <DllImport("testKlasse_DLL.dll", CallingConvention:=CallingConvention.StdCall)>
    8. Public Shared Function rufeSum_auf(ByVal value As IntPtr, ByVal a As Int32, ByVal b As Int32) As Int32
    9. End Function
    10. <DllImport("testKlasse_DLL.dll", CallingConvention:=CallingConvention.StdCall)>
    11. Public Shared Function rufegetA_auf(ByVal value As IntPtr) As Int32
    12. End Function

    Dieser Beitrag wurde bereits 2 mal editiert, zuletzt von „chris_2091“ ()