C++ DLL erstellen die mit C# aufrufbar ist

  • C++

Es gibt 37 Antworten in diesem Thema. Der letzte Beitrag () ist von RodFromGermany.

    C++ DLL erstellen die mit C# aufrufbar ist

    Hey Leute, ich versuch gerade etwas rum zu spielen und schaff es nicht ganz.

    Vorweg, ich hab bis heute noch nie etwas mit C++ gemacht.
    Ich möchte eine simple DLL erstellen die ich mitteln DLLImport in C# benutzen kann.
    Erstmal nur eine kleine Funktion zum testen.

    Hab mir im Netz paar Beispiele angeschaut zu C++, Grundlagen usw, das nötigste halt was ich dazu brauche.

    Ich poste einfach mal den Codeausschnitt.

    Habe insgesamt 3 Dateien in einen neuen Win32-Projekt angelegt. Header.h, main.cpp und body.cpp
    Der main-Teil:

    C-Quellcode

    1. #include "Header.h"
    2. #include "body.cpp"
    3. extern "C" __declspec(dllexport) double Hashes(double x1, double x2)
    4. {
    5. myclass mc(double x1, double x2);
    6. return mc.Summe();
    7. }

    Ist auch der Teil wo ich net weiter komme.
    Hier wird mir das "mc" unterringelt und gesagt:
    "Die Verknüpfungsspezifikationen sind inkompatibel"

    Hier die anderen beiden Teile (header.h):

    C-Quellcode

    1. #pragma once
    2. class myclass
    3. {
    4. public:
    5. myclass(double x1, double x2);
    6. double Summe();
    7. private:
    8. double x;
    9. double y;
    10. };


    Und hier der body-Teil:

    C-Quellcode

    1. #pragma once
    2. #include "Header.h"
    3. myclass::myclass(double x1, double x2)
    4. {
    5. x = x1;
    6. y = x2;
    7. }
    8. double myclass::Summe()
    9. {
    10. return x+y;
    11. }



    Wie gesagt, ich hab kein Plan ob es so überhaupt richtig ist:)
    Danke schonmal im Voraus.

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

    Der main-Teil muss eher so aussehen:

    C-Quellcode

    1. #include "Header.h"
    2. // include von body nicht nötig
    3. extern "C" __declspec(dllexport) double _stdcall Hashes(double x1, double x2) {
    4. myclass mc(x1, x2); // keine Typangaben in Aufrufen
    5. return mc.Summe();
    6. }


    Es scheint, als ob du (wieder mal?) mit YouTube-Tutorials anfängst. Daher wieder derselbe Rat: Lass das und lies ein vernünftiges Buch: Die C++ Programmiersprache (vom Entwickler der Sprache)
    Gruß
    hal2000
    Vielen dank, hatte mittlerweile das Problem gelöst, jedoch scheitere ich an ein weiteres.
    Jetzt bringt er mir den Fehler beim C# Projekt, das eine externe Komponente einen Fehler verursacht hat.

    Zu deinen zusätzlichen Befehl "_stdcall", das hatte ich davor, aber da hat mir beim Aufruf das VS den Fehler gebracht, das der Einstiegspunkt nicht gefunden wurde...

    Anscheinend ist es net so einfach:)

    n1nja schrieb:

    Anscheinend ist es net so einfach
    Ist zwar VB, aber gugst Du hier.
    Eine Empfehlung:
    Mach Dir ein CLR-Projekt, das ist managed C++.
    Da kommt eine Assembly raus, die Du wie jede andere .NET-DLL verweisen und usingen kannst.
    Dort kannst Du direkt in native C++ weiterschreiben.
    Und:
    Wenn Du dazu eine native DLL anlegst, kannst Du diese per Lib dem CLR-Projekt hinzufügen, Resultat ist eine .NET-Assemply, deren nativen Teil Du nicht fokussieren kannst.
    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!
    @n1nja: Damit musst du aufpassen. Du verlässt den Managed-Kontext und kannst damit alles zerstören. Du musst unbedingt aufpassen, den Kontext in einem validen Zustand zu belassen. Wenn man weiß, wie das geht, ist es relativ einfach.

    Ich poste einfach mal einen Ausschnitt eines aktuellen Projekts von mir. Da auch gleich mit einem Beispiel für strings. Die müssen nämlich besonders Behandelt werden. Weiter oben wurde bereits die Calling-Convention __stdcall erwähnt. Ich würde davon abraten, quasi willkürlich irgendwelchen Funktionen eine Calling-Convention aufzubinden, wenn es nicht nötig ist. Zwar basiert die komplette WINAPI auf __stdcall, jedoch muss man sich das Leben nicht kompliziert machen, wenn .NET auch __cdecl (also der Standard für externe Funktionen) beherrscht.

    Hier mal ein Beispiel der C++-Funktion:

    C-Quellcode

    1. extern "C" __declspec(dllexport) bool TcpChannel_RegisterIp(int channelId, char *ip)
    2. {
    3. // ... Code ...
    4. return channel->RegisterIp(ip);
    5. }


    Der entsprechende Part für .NET sieht so aus, in C#:

    C-Quellcode

    1. [DllImport("Hook.dll", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)]
    2. [return: MarshalAs(UnmanagedType.U1)]
    3. private static extern bool TcpChannel_RegisterIp(int channelId, [MarshalAs(UnmanagedType.LPStr)]string ip);


    Du siehst, dass die Calling-Convention per Attribut definiert wird. Des Weiteren wird das Charset gesetzt, welches bei mir ANSI ist (bei dir wohl auch - ersichtlich in den Projekteinstellungen -> Zeichensatz. Du hast da wohl Multibyte stehen). Das return: MarshalAs(UnmanagedType.U1) ist wichtig, da diese Funktion einen boolschen Wert zurückgibt. Der Hink daran: MSVC speichert bool Werte anders ab als .NET das tut. Daher muss man ihm mitteilen, wie das Layout im Speicher aussieht. Der Parameter ip muss ebenfalls mit einem MarshalAs-Attribut versehen werden. LPStr steht für Long-Pointer-String. .NET weiß so Bescheid, wie der String bei einem PInvoke-Aufruf übergeben werden muss.

    @RodFromGermany: hat schon recht, C++/CLI ist deutlich einfacher. Jedoch würde ich dir aus zwei Gründen von der Verwendung abraten:
    a) Microsoft entwickelt C++/CLI nicht mehr weiter und gab die Unterstützung dafür auf
    b) Lernst du mit der Verwendung von nativem C++ die Hürden aber auch Vorteile kennen (vorausgesetzt dass hier ist ein Hobbyprojekt - bei produktiven Applikationen solltest du natürlich nicht einfach so "im Leeren herumtappen")

    Viel Spaß noch ;-)
    To make foobar2000 a real random music player, I figured out the only way to achieve this is to use Windows Media Player.

    At some point in time, you recognize that knowing more does not necessarily make you more happy.
    Ok super danke euch beiden.

    Werde beide Versionen mal testen von euch wenn ich zu Hause bin:)

    Ich wollte auf jedenfall mit C++ irgendwann anfangen.
    Glaube sowas am Anfang ist zwar. Ich empfehlenswert aber ich bastel mir gerade mein eigenes LoginSystem mit allen Drum und dran über Sockets:)

    Da ich dann wichtige Sicherheiten drin haben muss wollte ich mal sehen wie weit ich das in C++ machen kann:)
    @Chrisber Deine Möglichkeiten funktionieren soweit ganz gut, mit Strings müsste ich sowieso arbeiten:)

    Aber ich bekomm den Fehler "Das ich versucht habe im geschützten Speicher zu lesen bzw zu schreiben"
    Habe nach den Fehler gesucht und Google spuckt mir aus, das es meist ein Fehler ist, was mit der Bit-Anzahl zu tun hat, wenn man mit Strings arbeitet.

    Daraufhin hab ich versucht es mit "Int" zu machen, kommt aufs selbe Problem hinaus.

    Nächste Versuch:
    Hab es in Unsafe versucht, aber wieder das gleiche:)

    Glaube bin jetzt schon am verzweifeln...

    Hier der Codeteil von C#

    C-Quellcode

    1. [DllImport("D:\\Dropbox\\Visual Studio\\Visual Studio 2012\\Projects\\CDll\\Debug\\CDll", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)]
    2. [return: MarshalAs(UnmanagedType.LPStr)]
    3. private static extern string GetText([MarshalAs(UnmanagedType.LPStr)]string s1, [MarshalAs(UnmanagedType.LPStr)]string s2);
    4. static void Main(string[] args)
    5. {
    6. unsafe
    7. {
    8. string tmp = GetText("hey", "du");
    9. Console.WriteLine(tmp);
    10. Console.ReadLine();
    11. }
    12. }


    Falls erwünscht hier die "main" von der DLL:
    Spoiler anzeigen

    C-Quellcode

    1. #pragma once
    2. #include <string>
    3. #include "Header.h"
    4. using namespace std;
    5. extern "C" __declspec(dllexport) string GetText(string s1, string s2)
    6. {
    7. myClass mc(s1, s2);
    8. string tmp = mc.Summe();
    9. return tmp;
    10. }



    Gibts dazu auch ne "simple" Lösung?! - Rechnerneustart habe ich bereits versucht.
    Hi,

    du kannst nicht mit Klassen der STL arbeiten. Du musst native Typen verwenden. Kleine Regel:
    • Übergabe eines Strings von C# nach C++: C# = [MarshalAs(UnmanagedType.LPStr)]string, C++ = const char *
    • Übergabe von Speicher, damit C++ einen String beschreiben kann, den du mit C# auslesen möchtest: C# = [MarshalAs(UnmanagedType.LPStr)]StringBuilder, C++ = char *


    Beispiel.

    Deklaration in C#:

    C-Quellcode

    1. [DllImport("Hook.dll", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)]
    2. [return: MarshalAs(UnmanagedType.U1)]
    3. private static extern bool TcpChannel_GetIp(int channelId, [MarshalAs(UnmanagedType.LPStr)]StringBuilder ip, int max);


    Implementierung in C++:

    C-Quellcode

    1. extern "C" __declspec(dllexport) bool TcpChannel_GetIp(int channelId, char *ip, int max)
    2. {
    3. // du kannst intern benutzen was du willst, also auch std::string
    4. std::string meinString = "lol-test";
    5. if (max < meinString.length() || ip == nullptr)
    6. {
    7. return false;
    8. }
    9. // ... Code ...
    10. // max gibt die Kapazität des StringBuilders an
    11. // kopiert den Inhalt von meinString nach ip, also in den Speicher des StringBuilders
    12. std::strncpy(ip, meinString.c_str(), max);
    13. return true;
    14. }


    Beispielaufruf in C#:

    C-Quellcode

    1. var ip = new StringBuilder(256); // 256 == max, also maximale Länge des Strings
    2. if (!TcpChannel_GetIp(_channelId, ip, ip.Capacity))
    3. {
    4. throw new RuntimeException("dunno");
    5. }
    6. Console.WriteLine("IP: {0}", ip.ToString());


    Hoffe, dass du das so verstehst. Daran siehst du aber schon, wie kompliziert es werden kann ;-)
    To make foobar2000 a real random music player, I figured out the only way to achieve this is to use Windows Media Player.

    At some point in time, you recognize that knowing more does not necessarily make you more happy.
    Hab den Thread jetzt nur grob überschlagen, aber so wird das Nichts (nicht viel zumindest. Guck da mal drüber [C++, C++/CLI, C#] PEViewer (Win32/Win64) die Funktionen sind komplett in C++ geschrieben und werden mit CLI in C# genutzt. P/Invoke hat viele Nachteile, erstens ists langsam (mus ja drölfzigtausend mal gemarshallt werden), du musst dich selbst um die zu marshallenden Typen kümmern, kannst schlecht STL-Klassen marshallen und Strings musste immer aufn Heap pushen oder in nen Puffer schreiben. Mach dir ein C++/CLI Projekt und exportier im nativen C++-Projekt eine Klasse nicht einzelne Funktionen (istja C++ nicht C ^^), so kannst du dann die komplette Klasse wrappen und hast gescheite CLR Typen und nicht char*.

    Gonger96 schrieb:

    Strings musste immer aufn Heap pushen

    Dann machst du was falsch ;-)

    Gonger96 schrieb:

    erstens ists langsam

    Stimmt. Aber trotzdem nicht immer zu vermeiden.
    To make foobar2000 a real random music player, I figured out the only way to achieve this is to use Windows Media Player.

    At some point in time, you recognize that knowing more does not necessarily make you more happy.
    C-Strings (also char* bzw. wchar_t*) muss man entweder aufn Heap packen und irgendwann später wieder löschen oder in einen Puffer schreiben, ich wüsste nicht wie man per P/Invoke sowas anders marshallen könnte. In CLI würde mans inetwa so machen:
    C++

    C-Quellcode

    1. #pragma once
    2. #include <iostream>
    3. #include <string>
    4. using namespace std;
    5. class NativeClass
    6. {
    7. public:
    8. NativeClass(wstring _arg1, wstring _arg2) : arg1(_arg1), arg2(_arg2) {};
    9. ~NativeClass(void) {};
    10. wstring concat_args() const {return arg1+arg2;}
    11. private:
    12. wstring arg1, arg2;
    13. };

    Wrapper

    C-Quellcode

    1. #pragma once
    2. #include "NativeClass.h"
    3. #include <iostream>
    4. #include <string>
    5. using namespace std;
    6. using namespace System;
    7. using namespace System::Runtime::InteropServices;
    8. public ref class CLIClass
    9. {
    10. public:
    11. CLIClass(String^ arg1, String^ arg2)
    12. {
    13. nat_class = new NativeClass(wstring(static_cast<wchar_t*>(Marshal::StringToHGlobalUni(arg1).ToPointer())), wstring(static_cast<wchar_t*>(Marshal::StringToHGlobalUni(arg1).ToPointer())));
    14. };
    15. ~CLIClass(void)
    16. {
    17. if(nat_class) delete nat_class;
    18. };
    19. String^ ConcatArgs()
    20. {
    21. return gcnew String(nat_class->concat_args().c_str());
    22. };
    23. private:
    24. NativeClass* nat_class;
    25. };

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

    Darf ich davon ausgehen, dass Du dieses Beispiel mal eben ignoriert hast?

    RodFromGermany schrieb:

    Ist zwar VB, aber gugst Du hier.
    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!
    Ja ich hab es leider nur kurz überflogen, hab mich es jetzt 10 ma durchgelesen und danach die Samples geladen und muss sagen.
    So ist es viel einfacher zu verstehen.

    Aber hilft mir das nun wirklich, richtig mit C++ zu arbeiten?!
    Ja so soll das in ein paar Jahren auch aussehen:)
    Ist mein Ziel...

    Gut dann werde ich erstmal die Grundlagen weiter verfolgen und mich in die Materie einlesen.

    Könnt ihr mir noch nen guten Tipp geben welche Bereiche zum Anfang gut geeignet sind?!
    Fang mit dem einfachen Handling an:
    Zahlen-Übergabe hin, hin und zurück
    String-Übergabe hin (zurück ist da etwas tricky)
    Pointer auf (Byte-, Integer-) Felder hin, im Feld Werte ändern, das könnte zu einer Bildverarbeitung werden.
    Mach zunächst nur ganz kurze Routinen (10-15 Zeilen), die Du voll überblicken kannst.
    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!
    Ok.

    Ist es auch möglich eine Function zu schreiben die gleichzeitig einen String, Int etc übergibt und auch wieder zurück?!
    So wie es in .Net übrig is :)

    Also sprich String oder anderen Typen in die DLL übertragen --> auswerten --> String wieder zurückgeben.

    Hab es gestern versucht, aber irgendwie kommen da chinesische zeichen zurück bzw in der DLL wird der geschrieben Wert falsch gelesen.

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

    Ja so zu sagen.

    Hab es gestern probiert in der DLL eine Variable zu speichern und deine beiden Funktionen zu benutzen, aber wie gesagt ich hab keine Ahnung wie ich den übertragenen String vergleichen kann?!

    so zb:
    [code=c]
    private string Compare(string value)
    {
    if (value == "Hey"){
    return "True";
    }else{
    return "False";
    }
    }
    [/C]

    Natürlich hab ich es mit Marhsal etc umgewandelt.
    Strings kannste ganz normal über == vergleichen, std::string & std::wstring überladen den Operator. Warum returnst du einen bool als string ? Wie benutzt du diese Funktion, per CLI (C++ Interop) ? Per P/Invoke kannst du keine STL-Typen marshallen.