String-Konversion von c++ zu c#



  • Hallo!
    Ich versuch grad eine wrapper-dll zu schreiben um c++-Dll in c# einzubinden.
    Und scheitere schon bei der einfachsten Sache..
    Eine C++-Funktion gibt char* zurück (genauer: die Version der API (der DLL))
    Das abbba niet kompatibel zu c#-Strings.
    Ich find einfach nicht raus, wie ich in c++ char* so konvertieren muss, damit es auf c#-Strings passt.
    Im Moment kriebe ich immer einen Error:

    File: memcpy_s.c Line 55
    Expression dst != NULL

    c# Strings sind UNI-CODE oder ?? Was ist das Pendant in C++ ??

    DANKE !!!

    ps: Ich habe suche genutzt aber nichts gefunden.


  • Administrator

    1. Zeig doch mal her, was du bisher gemacht hast.
    2. Wie rufst du die C++ Funktion auf? Machst du womöglich C++/CLI statt C#? Dann wärst du im falschen Forum. Wenn du P/Invoke betreibst, dann wäre die Lösung anders als bei C++/CLI.

    Grüssli



  • Mach anstatt char* ein BSTR (das ist ein WinAPI-Datentyp). Der mappt einfach auf System.String in .NET.


  • Administrator

    GPC schrieb:

    Mach anstatt char* ein BSTR (das ist ein WinAPI-Datentyp). Der mappt einfach auf System.String in .NET.

    Bisschen gefährliche Aussage, ohne zu Wissen was der Threadautor macht. Ich seh es schon kommen, dass er die C++ Funktion wie folgt abändert:

    BSTR foo()
    {
      static char bar[] = "Hello World!";
      return (BSTR)bar;
    }
    

    -> BAM CRASH BUM

    🤡

    Grüssli



  • Hallo ! Danke für die Antworten!!

    Also, ich hab ein DLL+Lib+Header "SR_API" in C++, von einer Firma, an der ich nix ändern kann. Leider brauchen die Funktionen jede Menge Pointer, riesige Structs, char* usw.
    Da ich C# so verstehe, dass Pointer und ähnliches quasi verboten sind, hab ich mir gedacht, ich schreib eine 2te DLL in C++ die den Code der Ursprungs-DLL so schachtelt, dass die Steuer- und Daten-Variablen ohne unsafe usw. für C# verständlich sind. Und diese DLL habe ich jetzt in ein C#-Projekt eingebunden. Bis jetzt funktionierts auch, naja fast alles (ich hab neben der SRAPI noch nen kleinen Test "Box" eingebaut um zu sehen ob das überhaupt funzt):

    // convSRAPI.cpp : Defines the entry point for the DLL application.
    #include "stdafx.h"
    #include "assert.h"
    #include <string>
    #pragma comment(lib,"SR_API.lib")
    #include "SR_API.h"
    #ifdef _MANAGED
    #pragma managed(push, off)
    #endif
    extern "C" __declspec(dllexport)void BoxProperties(double Length, double Height,double Width, double& Area, double& Volume);
    extern "C" __declspec(dllexport)bool getVersion_Api(std::string& apiversion);
    BOOL APIENTRY DllMain( HMODULE hModule,
                           DWORD  ul_reason_for_call,
                           LPVOID lpReserved
    					 )
    {
        return TRUE;
    }
    double BoxArea(double L, double H, double W)
    {
        return 2 * ((L*H) + (L*W) + (H*W));
    }
    double BoxVolume(double L, double H, double W)
    {
        return L * H * W;
    }
    void BoxProperties(double L, double H, double W, double& A, double& V)
    {
        A = BoxArea(L, H, W);
        V = BoxVolume(L, H, W);
    }
    bool getVersion_Api(std::string& apiversion) 
    {
    	std::string strtest;
    	char* version;
    	version = SR_API_GetAPIVersion(); // Funktion aus der SR_API return char*!!
    	strtest = version;
    	apiversion = strtest;
    	return true;
    }
    #ifdef _MANAGED
    #pragma managed(pop)
    #endif
    

    Wie man sieht convertiere ich hier erstmal char* zu std:string, hab aber auch schon anderes versucht...
    Das wird kompiliert und als DLL in meinem Testprogramm in C# aufgerufen:

    namespace WindowsApplication1
    {
        //Dies ist nur ein TEST
        static class Program
        {
            static double FL; static double VO; static double zeiger;
            static string APIVER; static string text; static bool good;
            [DllImport("convSRAPI.dll",EntryPoint ="BoxProperties")]
            public static extern void BoxProperties(double Length, double Height,double Width, out double Area, out double Volume);
            [DllImport("convSRAPI.dll", EntryPoint = "getVersion_Api")]
            public static extern bool getVersion_Api(out string apiversion);
            /// <summary>
            /// The main entry point for the application.
            /// </summary>
            [STAThread]
            static void Main()
            {
                BoxProperties(1, 1, 2,out FL,out VO);
                zeiger = FL;
                zeiger = VO;
                good = getVersion_Api(out APIVER);
                text = APIVER;
                Application.EnableVisualStyles();
                Application.SetCompatibleTextRenderingDefault(false);
                Application.Run(new Form1());
            }
        }
    }
    

    Funktioniert auch, bis zu dem Punkt an der apiversion zurückgegeben werden soll:
    File: memcpy_s.c Line 55
    Expression dst != NULL

    KA wies weitergeht.. 😕 Ich schätze der Fehler bedeutet, dass ich was zusammenkopiere was verschiedenen Typ hat.
    ps: Bevor ihr fragt, warum so seltsam oder so: Ich weiss einfach keinen anderen Weg. Hab noch niemals zuvor sowas gemacht. 😞



  • Dravere schrieb:

    GPC schrieb:

    Mach anstatt char* ein BSTR (das ist ein WinAPI-Datentyp). Der mappt einfach auf System.String in .NET.

    Bisschen gefährliche Aussage, ohne zu Wissen was der Threadautor macht. Ich seh es schon kommen, dass er die C++ Funktion wie folgt abändert:

    BSTR foo()
    {
      static char bar[] = "Hello World!";
      return (BSTR)bar;
    }
    

    -> BAM CRASH BUM

    🤡

    Grüssli

    😃 👍 YMMD

    @ghostwhisperer
    Wie gesagt, ich würde ein BSTR zurückgeben anstatt eines std::string. So einen kannst du mit SysAllocString anlegen. Alternativ kannst du es auch bei char* belassen und mit byte-Arrays in C# rumfrickeln 🤡



  • Eine Funktíon

    int bla(char* foo, int bar);
    

    würde man in C# so einbinden, falls foo nicht durch den Funktionsaufruf gesetzt wird:

    [DllImport("meine.dll")]
    private static extern int bla(
       [MarshalAs(UnmanagedType.LPStr)] [b]String[/b] foo,
       int bar
      );
    

    ...und so falls foo durch den Funktionsaufruf gesetzt wird:

    [DllImport("meine.dll")]
    private static extern int bla(
       [MarshalAs(UnmanagedType.LPStr)] [b]StringBuilder[/b] foo,
       int bar
      );
    

    Ein generell recht guter Artikel zu P/Invoke:
    http://msdn.microsoft.com/en-us/magazine/cc164123.aspx#S3

    Edit:
    ...wenn der char-Pointer keine Zeichenkette darstellt sondern einfach irgendwelche Bytes, gehts z.B. so:

    [DllImport("meine.dll")]
    private static extern int bla(IntPtr foo, int bar);
    
    // Aufruf z.B.:
    IntPtr foo=IntPtr.Zero;
    int returnValue=bla(foo,bar);
    
    // 1024 Bytes aus foo nach nenByteArray kopieren:
    byte[] nenByteArray=new byte[1024];
    Marshal.Copy(foo, nenByteArray, 0, 1024);
    
    // ...oder Marshal.ReadByte(...)
    


  • Sorry aber ich nix verstehn..
    Nichts davon hat funktioniert.
    zu BSTR: Wie geht das, wenn man BSTR als Parameter im Funktionsaufruf braucht ?? Nur

    func(BSTR string)
    

    geht gar nicht, da BSTR kein Standardtyp ist.

    Belasse ichs bei char* und nehme in csharp

    char[] text
    

    läuft das Prog zwar, aber das Array bleibt leer (Dimension=0). Ich nehme an, da

    char*
    

    ja eigentlich ein Zeiger ist? Auf c++ Seite steht ja

    char*& text
    

    (das & für out oder??).

    Bei

    private static extern int bla([MarshalAs(UnmanagedType.LPStr)] String foo, int bar);
    

    bekomme ich mehrere Fehlermeldungen: Type expectet, ; expectet
    ist DAS richtig ??->

    public static extern bool getVersion_Api(out [MarshalAs(UnmanagedType.LPStr)] String apiversion);
    


  • 1. Benutze auf bei deiner C (bzw. C++) API rohe Char* o.ä.
    2. Benutze auf der C# Seite die Klasse StringBuilder.

    http://stackoverflow.com/questions/597558/p-invoke-with-out-stringbuilder-lptstr-and-multibyte-chars-garbled-text



  • ghostwhisperer schrieb:

    ist DAS richtig ??->

    public static extern bool getVersion_Api(out [MarshalAs(UnmanagedType.LPStr)] String apiversion);
    

    Out bei P/Invoke sähe so aus:

    public static extern bool getVersion_Api([Out, MarshalAs(UnmanagedType.LPStr)] String apiversion);
    

    ...hat allerdings glaube ich nicht ganz dieselbe Bedeutung wie das "normale" out. Ich habs bisher immer weggelassen und damit bislang keine Probleme gehabt. (Vllt. hat da ja hier noch jemand Ahnung was es damit auf sich hat?)

    Edit: Und in deinem Falle natürlich wie schon erwähnt wurde StringBuilder statt String nehmen! Den StringBuilder vorher mit genügend Kapazität ausstatten:

    // Platz für 1024 Zeichen:
    StringBuilder sb=new StringBuilder(1024);
    

  • Administrator

    geeky schrieb:

    ...hat allerdings glaube ich nicht ganz dieselbe Bedeutung wie das "normale" out. Ich habs bisher immer weggelassen und damit bislang keine Probleme gehabt. (Vllt. hat da ja hier noch jemand Ahnung was es damit auf sich hat?)

    Das Keyword out fügt eine zusätzliche Indirektion hinzu, während das Attribut Out nur eine Kennzeichnung für den Marshaler ist, dass er den Wert gefälligst auch zurückkopiert, sonst würde er die Sache womöglich ignorieren. So ganz kurz und knapp zusammengefasst 🙂

    Ich würde ganz auf alle Lösungen hier verzichten und gar nicht eine zwischen DLL verwenden. Man kann dies ganz einfach lösen:

    [DllImport(
      @"SR_API.dll",
      CallingConvention = CallingConvention.Cdecl,
      ExactSpelling = true,
      EntryPoint = @"SR_API_GetAPIVersion")]
    private static extern IntPtr GetSrApiVersion();
    
    public static string GetApiVersion()
    {
      IntPtr ptr = GetSrApiVersion();
      return Marshal.PtrToStringAnsi(ptr);
    }
    

    http://msdn.microsoft.com/en-us/library/7b620dhe.aspx

    Das Leben kann doch so einfach sein 🙂

    Grüssli



  • const char* sname = (char*)(void*)Marshal::StringToHGlobalAnsi(name);



  • Das wird ihm 6 Jahre später sicherlich weiterhelfen.


Log in to reply