C++-Klasse in VB.Net verwenden

  • VB.NET

Es gibt 20 Antworten in diesem Thema. Der letzte Beitrag () ist von Infinity.

    C++-Klasse in VB.Net verwenden

    Hi,

    ich habe eine C++-Klasse (normales C++, keine managed C++) und möchte diese in VB verwenden. Ich habe schon im Internet einiges gefunden, aber so ganz funktioniert noch nicht.

    Damit ich die C++-DLL in der die Klasse enthalten ist in VB verwenden kann, habe ich in den Projekteinstellungen die "Common Language Runtime-Unterstützung" aktiviert und vor das "class" in den Headerdateien ein "public" gesetzt, damit ich auf die Klassen auch von außen zugreifen kann.
    Das ganze hab ich dann kompiliert und in VB als Verweis zum Projekt hinzugefügt. Ich sehe jetzt die Klassen auch in der Intellisense und kann auch eine neue Instanz erstellen, allerdings kann ich nicht auf die Klassenmember zugreifen (außer auf die, die von Object geerbt werden).

    Was habe ich falsch gemacht bzw. wie bekomme ich es hin, die Klassenmember verwenden zu können?

    Danke schon mal im Voraus :)
    Glaube nicht, aber auf pinvoke.net findest du alle WinAPI-Funktionen. Ansonsten sollte es eine Dokumentation geben. Welche Dll willst du denn verwenden?

    Dieser Beitrag wurde bereits 1 mal editiert, zuletzt von „Infinity“ ()

    Ich frage mich dies zwar auch schon sehr lange doch mir ist nichts bekannt. Das einzige was in diese RICHTUNG gehen könnte wäre ein com interface?


    Opensource Audio-Bibliothek auf github: KLICK, im Showroom oder auf NuGet.
    Ich habe ja im Prinzip ein COM-Interace erstellt, indem ich in Visual C++ eine Dynamische Klassenbibliothek mit der Kompilierungsoption "Common Language Runtime-Unterstützung" erstellt habe. Richtig? Nur beim Einbinden der Dll bzw. vielleicht schon beim Erstellen habe ich einen Fehler gemacht, sodass ich nicht auf die Klassenmember zugreifen kann.
    Was meinst du mit über die CRL abwickeln? Soll ich mit Hilfe einer Klasse die in managed C++ geschrieben ist, die "normale C++ Klasse" in .NET verfügbar machen? Wie würde das gehen?

    Was mir noch aufgefallen ist: Die Klasse erscheint in der Intellisens gar nicht als Klasse, sondern als Structure.

    Infinity schrieb:

    Glaube nicht, aber auf pinvoke.net findest du alle WinAPI-Funktionen. Ansonsten sollte es eine Dokumentation geben. Welche Dll willst du denn verwenden?


    Ach von nem Spiel den Hackshield wollte ich mir mal genauer angucken, evtl. manipulieren. Ich meine... So schlimm ist das nun auch wieder nicht. :D
    Microsoft hat dafür COM angedacht. Also hast du zwei Möglichkeiten:

    1. Schreibe einen Wrapper für alle Funktionen der Klasse (inkl. Konstruktor und Destruktor) und compiliere den mit dem /clr-Flag. Setze dann einen Verweis auf den Wrapper. Das kann bei vielen Klassen lästig werden, aber für eine einzelne ist das noch vertretbar.
    2. Schreibe eine minimale COM-Komponente, bestehend aus den Standard-Exports DllRegister- / -UnregisterServer, DllCanUnloadNow und DllGetClassObject sowie einer ClassFactory. Generiere ein Interface aus allen Methoden deiner vorhandenen Klasse und gibt eine Instanz davon in QueryInterface zurück, wenn das Interface angefordert wird. Vorteil: Für weitere Klassen musst du nur noch ein Interface generieren und in QueryInterface aufnehmen.

    In .NET importierst du das Interface (ComImportAttribute) und rufst CoCreateInstance per P/Invoke auf.
    Gruß
    hal2000
    Danke für die Antwort.

    Da es sich nur um zwei relativ kleine Klassen handelt (wobei die eine von der anderen erbt) würde ich mich für die erste Methode entscheiden. Weitere Klassen wird es auch definitiv nicht geben.
    Dass man eine Wrapper-Klasse schreiben kann, habe ich mittlerweile auch schon gefunden, aber leider hat es an der Umsetzung gescheitert. Wie erreiche ich, das wiederum die Member der Wrapper-Klasse sichtbar sind? Muss ich bestimmte Schlüsselwörter verwenden (wie das public vor dem class, damit die Klassen überhaupt von Außen zugreifbar sind)? Die Beschreibungen im Internet waren teilweise sehr kompliziert und ich konnte sie nicht auf meinen Code übertragen, daher wäre ich für weitere Hilfe sehr dankbar :)

    Wenn ich eine COM-Komponente schreibe, hätte das dann den Nachteil, dass ich die DLL-Datei erst mit regsvr32.exe registrieren müsste?
    Hast Du den Code dieser C++-DLLs oder zumindest die Deklarationen der Funktionen?
    Die musst Du so formulieren, wie die Deklaration einer API-Funktion, dann kannst Du sofort darauf zugreifen.
    Auf einzelne Variable in dieser DLL kannst Du nicht zugreifen, da müsstest Du Dir Get-Set-Prozeduren machen.
    Falls Du diesen Code kopierst, achte auf die C&P-Bremse.
    Jede einzelne Zeile Deines Programms, die Du nicht explizit getestet hast, ist falsch :!:
    Ein guter .NET-Snippetkonverter (der ist verfügbar).
    Programmierfragen über PN / Konversation werden ignoriert!

    Infinity schrieb:

    Wenn ich eine COM-Komponente schreibe, hätte das dann den Nachteil, dass ich die DLL-Datei erst mit regsvr32.exe registrieren müsste?
    Ja, den hätte es.

    Für den Wrapper:

    Voraussetzungen: LIB- DLL- und Headerdatei der nativen Klasse.

    - Win32-Projekt (C++) erstellen, "DLL" wählen.
    - Headerdatei der nativen Klasse ins Projektmappenverzeichnis kopieren
    - (Projektmappen-Explorer: Ordner "Headerdateien") --> Hinzufügen --> Vorhandenes Element --> Headerdatei der nativen Klasse wählen
    - Projekt erstellen (erstellt den Debug-Ordner im Projektverzeichnis)
    - .lib-Datei der nativen Klasse in den automatisch erstellten Debug-Ordner kopieren
    - Projekteigenschaften --> Linker --> Eingabe --> Abhängigkeiten --> Bearbeiten: "$(OutDir)LIB_DATEI.lib" (ohne " ") --> OK
    - Projekteigenschaften: Folgende Flags setzen:
    - C++ --> Allgemein --> CLR-Unterstützung auf /clr
    - C++ --> Allgemein --> Debuginformationsformat auf /Zi (nicht /ZI)
    - C++ --> Codegenerierung --> Minimale Neuerstellung auf /Gm- (=Nein)
    - C++ --> Codegenerierung --> C++-Ausnahmen auf /EHa
    - C++ --> Codegenerierung --> Vollst. Laufzeitüberprüfungen auf "Standard".

    - In der am Anfang automatisch geöffneten Datei [Projektname].cpp folgendes einfügen:

    C-Quellcode

    1. #include "stdafx.h"
    2. #include "MyNativeClass.h"
    3. namespace CLRWrapper {
    4. public ref class Wrapper {
    5. private:
    6. MyNativeClass* instance;
    7. public:
    8. static Wrapper^ CreateNativeClass() {
    9. return gcnew Wrapper();
    10. }
    11. Wrapper() {
    12. instance = new MyNativeClass();
    13. }
    14. ~Wrapper() {
    15. if (instance) {
    16. delete instance;
    17. }
    18. }
    19. int Add(int a, int b) {
    20. if (instance) {
    21. return instance->Add(a, b);
    22. } else {
    23. return 0;
    24. }
    25. }
    26. // Weitere Funktionen der Klasse MyNativeClass immer nach demselben Schema implementieren, sofern sie verfügbar sein sollen.
    27. // Static-Funktionen in NativeClass sind auch hier static.
    28. };
    29. }

    - Projektmappe erstellen.
    - VB-Projekt öffnen, Verweis auf den Wrapper setzen
    - VB-Projekt erstellen (kopiert die Wrapper-DLL in das Ausgabeverzeichnis)
    - Die DLL der nativen Klasse in das Ausgabeverzeichnis des VB-Projekts kopieren (wichtig!)
    - Wrapper benutzen:

    VB.NET-Quellcode

    1. Imports CLRWrapper
    2. Module Module1
    3. Sub Main()
    4. Dim w = Wrapper.CreateNativeClass()
    5. Console.WriteLine("1+5=" & w.Add(1, 5))
    6. Console.ReadLine()
    7. End Sub
    8. End Module
    Gruß
    hal2000
    Danke für die ausführliche Beschreibung! Damit sollte ich es hinbekommen - werde es später ausprobieren.

    EDIT: Ich habe es jetzt ausprobiert und habe noch Probleme mit den Typen.

    Quellcode

    1. long Size() {
    2. if (instance) {
    3. return instance->size();
    4. } else {
    5. return 0L;
    6. }
    7. }

    Diese Funktion sollte normalerweise den Rückgabetyp Long/Int64 in VB bekommen, allerdings bekommt sie den Rückgabetyp Integer.

    Quellcode

    1. int Open(char *fileName) {
    2. if (instance) {
    3. return instance->open(fileName);
    4. } else {
    5. return 0;
    6. }
    7. }

    Die Funktion erscheint in VB garnicht erst in der Intellisense, wenn ich versuche sie trotzdem zu verwenden bekomme ich folgende Fehlermeldung:
    "Open" hat einen Rückgabetyp oder Parametertypen, die nicht unterstützt werden.

    Ich denke hier ist die Verwendung des char-Arrays das Problem.

    Durch das Hinzufügen des ref-Schlüsselworts habe ich eine Managed-C++-Klasse erstellt, oder? Dann müsste ich doch theoretisch den String-Type auf dem Framework verwenden können, den ich dann nur irgendwie in ein char-Array konvertieren muss.
    Mit der String-Klasse aus C++ (die leider der Code den ich verwende nicht benutzt) habe ich den selben Fehler.

    Die restlichen Funktionen (die allerdings alle nur int als Rückgabe- bzw. Parametertyp verwenden) gehen alle und liefern auch richtige Testwerte zurück. Auch beim Kompilieren der DLL-Datei selbst gibt keine Fehler oder Warnungen.

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

    Es scheint zu funktionieren, wenn man die .Net-Typen verwendet, also statt "int" "Int32" und statt "long" "Int64". Dann gibt die erste Funktion auch den richtigen Typ zurück. Bei der zweiten Funktion habe ich statt "char*" "String^" verwendet und mit folgendem Code die Typen konvertiert:

    Quellcode

    1. Int32 Open(String^ fileName) {
    2. if (instance) {
    3. array<Byte> ^chars = System::Text::Encoding::ASCII->GetBytes(fileName);
    4. pin_ptr<Byte> charsPointer = &(chars[0]);
    5. char *nativeCharsPointer = reinterpret_cast<char *>(static_cast<unsigned char *>(charsPointer));
    6. return instance->open(nativeCharsPointer);
    7. } else {
    8. return 0;
    9. }
    10. }

    Es scheint zu funktionieren.

    Aber bei einer Funktion hab ich noch Probleme, den .Net-Typ zu finden und dann richtig zu konvertieren:

    Quellcode

    1. Int32 Read(unsigned char* buf, Int32 numBytes) {
    2. if (instance) {
    3. return instance->read(&buf, numBytes);
    4. } else {
    5. return 0;
    6. }
    7. }


    Hat jemand eine Idee bzw. ist es überhaupt so die richtige Vorgehensweise?

    Infinity schrieb:

    Diese Funktion sollte normalerweise den Rückgabetyp Long/Int64 in VB bekommen, allerdings bekommt sie den Rückgabetyp Integer.

    long = int = Int32. Int64 ist in C++ "long long".

    Das String-Problem ist ein wenig schwerer, aber hier ein Hinweis: msdn.microsoft.com/de-de/library/1b4az623. An den char* kommst du mit der Methode c_str() auf dem std::string-Objekt. Benutze direkt die ANSI-Version, dann ersparst du dir die Konvertierung von Unicode in ANSI. Der Weg zurück ist übrigens einfach: Man kann den char* im Konstruktor von System:: String angeben.

    "ref class": stackoverflow.com/questions/4684365/managed-c-ref-class

    Und ganz wichtig: Das .NET Framework ist recht intelligent, was das Marshalling von Daten in unverwalteten Code angeht. Aber manchmal gehts auch in die Hose. Daher unbedingt folgende Artikelserie beachten: technet.microsoft.com/de-de/li…ah6xy75%28v=vs.80%29.aspx

    In deinem letzten Beispiel übergibst du einen unsigned char** - ist das wirklich das, was die Funktion will? Es soll wahrscheinlich ein Byte-Array gefüllt werden. Schreibe also folgendes:

    Quellcode

    1. // oder "ref Byte[] buffer" - mal testen.
    2. Int32 Read(System::Byte[] buffer, Int32 numBytes) {
    3. if (instance) {
    4. // Pseudocode! Da sind garantiert Syntaxfehler drin.
    5. pointer = Marshal.AllocHGlobal(numBytes)
    6. int bytesRead = instance->read(pointer, numBytes);
    7. Marshal.Read(buffer, numBytes) // oder Copy? kA
    8. Marshal.FreeHGlobal(pointer)
    9. return bytesRead;
    10. } else {
    11. return -1; // Fehlerwert für "nichts gelesen" (0 ist noch ein gültiger Wert!)
    12. }
    13. }
    Gruß
    hal2000

    Dieser Beitrag wurde bereits 3 mal editiert, zuletzt von „hal2000“ ()