C-Style DLL-Schnittstelle mit C++/CLI und Stringübergaben



  • Ich war nie wirklich in der C-Welt unterwegs (bin direkt mit C++ eingestiegen und von dort aus in andere OO-Sprachen wie C#) und C++/CLI will ich jetzt als Bridge zwischen C++ Builder und .net Core dlls nutzen (und da die VC++-ABI nicht unbedingt der vom C++ Builder entspricht, eben über eine C-Style DLL-Schnittstelle). Beide Seiten sind 64bit.

    Nahezu alle Beispiele die ich gefunden habe nutzen ausschließlich Zahlen für die Schnittstellen, mit geht es aber um in/out von Strings (genauer Unicode-Strings, wchar_t). Hier bin ich mir nicht 100% sicher in der Anwendung ohne Memoryleaks zu verursachen, ich würde auch gerne alle Allozationen in der C++ Builder Anwendung übernehmen, bei out-Strings den Buffer auf der C++ Builder-Seite anlegen.

    Wie kopiert man den Inhalt eines managed Strings in einen per wchar_t** übergebenen Buffer, ohne auf C++/CLI-Seite dazu Speicher zu allokieren? Ich habe nur ein Beispiel gefunden wo der String aus der C++/CLI-Anwendung als return-Wert übergeben und dann über einen 2ten Aufruf anschließend freigegeben wird. Ich bekomme zudem für diesen Fall im VS auch noch einen Fehler wegen nicht implementierter abstrakter Methode bei Verwendung von IntPtr (siehe unten).

    Die Schnittstelle soll im wesentlichen so aussehen:

    #ifndef NetWrapperC_HEADER
    #define NetWrapperC_HEADER
    
    #ifdef NETWRAPPER_EXPORTS
    #define NETWRAPPER __declspec(dllexport)
    #else
    #define NETWRAPPER __declspec(dllimport)
    #endif
    
    #ifdef __cplusplus
    extern "C"
    {
    #endif
    
    NETWRAPPER void MachWas(wchar_t* input, wchar_t** output, int64_t output_len);
    
    #ifdef __cplusplus
    }
    #endif
    
    #endif
    

    Was ich gefunden habe würde zu einer Schnittstelle wie...

    NETWRAPPER wchar_t* MachWas(wchar_t* input);
    NETWRAPPER void FreeString(wchar_t* text);
    

    ...führen, was ich eigentlich nicht will. Zumal ich auch Fehlerrückgaben im C-Stil behandeln will, sprich wohl auf einen int (besser wohl ein _int64) als Returnwert umstellen werde.

    Mit der zweiten Variante würde, wenn ich alles richtig verstanden habe, mein cpp-Code wie folgt aussehen:

    wchar_t* MachWas(
        wchar_t* text)
    {
        System::String^ input = gcnew System::String(text);
        System::String^ output = DotNetDLL::Machwas(input);
    
        // Im Beispiel wurde wie folgt der Returnwert gebildet, bin bereits über die doppelte Konvertierung stutzig.
        return (wchar_t*)(void*)System::Runtime::InteropServices::Marshal::StringToHGlobalUni(output);
    }
    
    void FreeStringResult(
        wchar_t* const text)
    {
        // Nicht nur das ich eigentlich einen solchen Aufruf vermeiden will, macht mich hier auch eine Anzeige im VS nervös: "IntPtr...is a unimplemented interface method"
        System::Runtime::InteropServices::Marshal::FreeHGlobal(System::IntPtr::IntPtr(text));
    }
    

    Lieber wäre mir eben, wenn ich die Signatur so habe, das der Buffer vom aufrufenden alloziert wurde, und der Inhalt des Rückgabestrings da ohne Speicherallokation hineinkopiert werden würde. Hat dazu jemand ein Beispiel?

    Nachtrag: In der Signatur noch die Buffer-Länge ergänzt.



  • Hallo,

    schau dir das C++ Beispiel unter IntPtr.ToPointer an (dafür explizit die "Language"-Combobox von "C#" auf "C++" stellen).

    Und anstatt der "reverse-loop" kannst du dann einfach wcsncpy(...) aufrufen, s. strncpy, _strncpy_l, wcsncpy, _wcsncpy_l, _mbsncpy, _mbsncpy_l.

    Es gibt außerdem einen expliziten Cast-Operator von IntPtr auf void * (daher ist in deinem Code kein ToPointer()-Aufruf notwendig - ich finde dies jedoch lesbarer), s. IntPtr.Explicit Operator.


Anmelden zum Antworten