Implementieren callback-C# - Funktionen für C++ - DLL
Ich Schreibe eine DLL-wrapper für meine C++ - Bibliothek zum Aufruf von C#. Dieser wrapper soll auch callback-Funktionen genannt, aus der Bibliothek und in C# implementiert. Diese Funktionen haben zum Beispiel std::vector<unsigned char> als output-Parameter. Ich weiß nicht, wie diese zu machen. Wie gebe ich einen buffer unbekannter Größe von C# zu C++ über eine callback-Funktion?
Nehmen wir mal dieses Beispiel
CallbackFunction FunctionImplementedInCSharp;
void FunctionCalledFromLib(const std::vector<unsigned char>& input, std::vector<unsigned char>& output)
{
//Here FunctionImplementedInCSharp (C# delegate) should somehow be called
}
void RegisterFunction(CallbackFunction f)
{
FunctionImplementedInCSharp = f;
}
Wie sollte CallbackFunction
definiert werden und was ist der code, der in FunctionCalledFromLib?
Eines der Dinge, die dumm mich ist: wie lösche ich einen Puffer erstellt von C# in C++ - code?
Du musst angemeldet sein, um einen Kommentar abzugeben.
Gibt es einige Dinge, die Sie beachten sollten. Die erste ist, dass, wenn Sie anrufen, eine .NET delegieren von nicht verwaltetem code, dann, wenn Sie nach einigen ziemlich engen Grenzen, werden Sie in Schmerzen.
Ideal, Sie können erstellen Sie einen Delegaten in C# übergeben Sie es in verwalteten code, der Marschall es in eine Funktion Zeiger, halten auf für so lange, wie Sie möchten, dann rufen Sie es mit keine negativen Auswirkungen. Die .NET-Dokumentation so sagt.
Ich kann Euch sagen, das ist einfach nicht wahr. Schließlich, Teil Ihrer Stellvertretung oder Ihrer thunk bekommen wird Müll gesammelt, und wenn Sie die Funktion aufrufen Zeiger aus nicht verwaltetem code ein, den Sie zugestellt bekommen, in Vergessenheit zu geraten. Mir ist egal, was Microsoft sagt, ich habe folgte Ihr Rezept auf das schreiben und beobachtete Funktionszeiger Holen Sie sich in Müll, vor allem in server-side-code Hintern.
Gegeben, dass der effektivste Weg, um die Verwendung von Funktionszeigern ist so:
Gegeben, daß, angenommen wir haben die folgende in C#:
und dann in managed C++ (nicht C++/CLI):
Ich nicht getan habe dies vor kurzem in C++/CLI - syntax ist anders - ich denke, dass es am Ende so Aussehen:
Beim verlassen dieser Routinen, die Belegung veröffentlicht wird.
Wenn Sie wirklich, wirklich brauchen, um haben Funktionszeiger hängen herum für eine Weile, bevor Sie anrufen, ich habe das folgende in C++/CLI:
Was passiert, ist, dass sich die Delegierten nicht über thunks, die übergänge mehr, denn Sie sind implizit. Sie sind frei zu hängen, um in der Schwebe, bewegt durch die GC, wie gebraucht. Wenn Sie aufgerufen wird, wird der Delegat wird gepinnt von der CLR und freigesetzt werden. Ich habe auch gesehen, wie diese Methode fehlschlagen, insbesondere im Fall von code, statisch registriert Rückrufe an den Anfang der Zeit und erwartet, dass Sie zu bleiben um bis zum Ende der Zeit. Ich habe gesehen, dass dies nicht in ASP.NET code-behind-sowie server-side-code für Silverlight arbeiten durch WCF. Es ist ziemlich nervenaufreibend, aber die Art und Weise, es zu beheben ist umgestalten von API, dass eine späte(r) - Bindung an Funktionsaufrufen.
Geben Sie ein Beispiel, Wann dies passieren wird - angenommen, Sie haben eine Bibliothek, die eine Funktion enthält, wie dieses:
und die Erwartung ist, dass beim aufrufen einer API, die den Speicher reserviert, es werden vektorielle ab in die mitgelieferte
f_AllocPtr
. Ob Sie es glauben oder nicht, Sie können schreiben Sie diese in C#. Es ist süß:RegisterPointerHandleAndArray stopft sich die Triplett-Weg für sichere Aufbewahrung. So, wenn die entsprechende freie aufgerufen wird, können Sie dies tun:
Und das ist natürlich dumm, weil allokierten Speicher ist jetzt angepinnt in der GC-heap und fragment in die Hölle - aber der Punkt ist, dass es machbar ist.
Aber noch einmal, ich habe persönlich gesehen, diese scheitern, es sei denn, der eigentliche Zeiger sind kurzlebig. Dies bedeutet in der Regel das einwickeln der API, so dass Sie beim aufrufen einer routine, führt eine bestimmte Aufgabe, registriert Rückrufe, hat die Aufgabe, und dann zieht die Rückrufe aus.
Als von Visual Studio 2013 mindestens, ist ein sicherer Weg, um pass Rückrufe von C# zu C++ und C++ speichern Sie Sie und rufen Sie asynchron später aus nicht verwaltetem code. Was Sie tun können, ist, erstellen Sie eine verwaltete C++/CX-Klasse (z.B. mit dem Namen "CallbackManager") zu halten, die callback-Delegaten Referenzen in einer Karte kodiert, aus einer enum-Wert für jeden. Dann Ihre nicht verwalteten code abrufen kann ein managed delegate-Referenz von managed C++/CX CallbackManager Klasse über die Delegierten der zugehörigen enum-Wert. So dass Sie nicht haben, zu speichern raw-Funktion Zeiger und so müssen Sie nicht sorgen zu machen über die Stellvertretung, die verschoben oder garbage Collection: es bleibt in der verwalteten Heaps über den gesamten Lebenszyklus.
Auf der C++ Seite in CallbacksManager.h:
In CallbacksManager.cpp wir implementieren von managed C++/CX Klasse zugreifen, die von C# und unsere unmanaged C++ - code:
Die Delegaten-Instanzen gespeichert bleiben, in dem verwalteten heap durch unsere CXCallbacksManager Klasse, so, jetzt ist es einfach und sicher zu speichern callbacks an die C++ Seite für nicht verwalteten code aufrufen, später asynchron. Hier ist der C# - Seite registrieren zwei Rückrufe:
Schließlich, hier ist gewusst wie: aufrufen von Ihr registrierten C# Rückrufe von nicht verwaltetem C++ - code:
Es funktioniert, weil, obwohl Sie nicht speichern kann ein managed delegate-Referenz als member-variable innerhalb einer unmanaged Klasse, können Sie immer noch abrufen und aufrufen einer verwalteten delegieren von nicht verwaltetem code, die, was die beiden oben genannten native C++ - Methoden zu tun.
Als es stellt sich heraus, die Antwort auf die ursprüngliche Frage ist ziemlich einfach, sobald Sie wissen es, und die ganze callback-Problem war kein Problem. Der input-buffer-parameter ersetzt, der mit dem parameter-pair-Mädchen
unsigned char *input, int input_length
, und die Ausgabe-Puffer-parameter ersetzt, der mit dem parameter-pair-Mädchenunsigned char **output, int *output_length
. Der C# - Delegat sollte so etwas wie diesesUnd wrapper in C++ sollte so etwas wie dieses
Der letzten Zeile ist der Letzte Teil des mini-puzzle. Muss ich call CoTaskMemFree, oder wird der marshaller tun es für mich automatisch?
Als für den schönen essay von sockel, ich hoffe, umgehen das ganze problem durch die Verwendung einer statischen Funktion.
Gibt es keinen Punkt, um mit C++/cli.
Und hier ist ein reales Beispiel von meinem Projekt.
Für die übertragung von PNG-Daten von C# auf die native cairo-API.
Können Sie sehen, wie die C-Funktion einen Zeiger cairo_read_func_t ist in C# implementiert und dann verwendet als callback für cairo_image_surface_create_from_png_stream.
Hier ist ein ähnliches Beispiel.
std::vector
. Diese Schnittstelle ist nicht standardisiert, es könnte sogar den Wechsel zwischen verschiedenen compiler-Versionen des gleichen Herstellers, undC#
Sie nicht unterstützt, mitDllImport
Attribut. In meinem FallC++/CLI
ist ein muss.