2009-08-06 14 views
0

Ich habe ein C-Callback wie folgt definiert:Problem mit der C-Callback-Funktion in C# - Wie übergibt man einen Wert an einen Zeiger?

Int16 (CALLBACK *ProcessMessage)(Uint16 ServerId, 
    const char PTR *RequestMsg, Uint32 RequestSize, 
    char PTR **ResponseMsg, Uint32 PTR *ResponseSize, 
    Int16 PTR *AppErrCode); 

Ein exemple diesen Rückruf in C verwenden:

 
Int16 CALLBACK ProcessMessage(Uint16 ServerId, const char PTR *RequestMsg, Uint32 RequestSize, char PTR **ResponseMsg, Uint32 PTR *ResponseSize, Int16 PTR *AppErrCode) 
{ 
    printf("ProcessMessage() -> ServerId=%u\n", ServerId); 

    //**** SET THE VALUE FOR RESPONSEMSG (POINTER), THAT'S WHAT I NEED TO DO IN C# ****  
    sprintf(resp,"(%05lu) REPLY TEST", ServerId); 
    *ResponseMsg = resp; 

    printf("ProcessMessage() -> atribuido %p(p) a *ResponseMsg\n", *ResponseMsg); 
    *ResponseSize = strlen(*ResponseMsg); 
    *AppErrCode = -1; 
    return SS_OK; 
} 

Dann habe ich diesen Rückruf in C# implementiert:

[DllImport("Custom.dll", SetLastError = true)] 
    static extern Int16 SS_Initialize(
     UInt16[] ServerIds, 
     UInt16 ServerQty, 
     [MarshalAs(UnmanagedType.LPStr)] string Binding, 
     [MarshalAs(UnmanagedType.LPStr)] string LogPath, 
     UInt16 LogDays, 
     Int16 LogLevel, 
     UInt16 MaxThreads, 
     UInt16 MaxConThread, 
     ProcessMessageCallback callback); 

Rückruf-Definition:

public delegate Int16 ProcessMessageCallback(
     UInt16 ServerId, 
     [MarshalAs(UnmanagedType.LPStr)] string RequestMsg, 
     UInt32 RequestSize, 
     [MarshalAs(UnmanagedType.LPStr)] ref string ResponseMsg, 
     ref UInt32 ResponseSize, 
     ref Int16 AppErrCode); 

Methode, die die Callback-Sets:

public void Call_SS_Initialize(
     UInt16[] serverIds, 
     string binding, 
     string logPath, 
     UInt16 logDays, 
     Int16 logLevel, 
     UInt16 maxThreads, 
     UInt16 maxConThread 
     ) 
    { 
     Int16 ret; 
     try 
     { 
      pmc = new ProcessMessageCallback(ProcessMessage); 

      ret = SS_Initialize(
       serverIds, 
       Convert.ToUInt16(serverIds.ToList().Count), 
       binding, 
       logPath, 
       logDays, 
       logLevel, 
       maxThreads, 
       maxConThread, 
       pmc); 
     } 
    } 

Und schließlich die Callback-Methode, wo das Problem liegt: So

public Int16 ProcessMessage(
     UInt16 ServerId, 
     string RequestMsg, 
     UInt32 RequestSize, 
     ref string ResponseMsg, 
     ref UInt32 ResponseSize, 
     ref Int16 AppErrCode) 
    { 
     //Implement return to ResponseMsg POINTER 
    } 

Das Problem ist, ResponseMsg ist eigentlich ein POINTER in C. in der C# -Methode ProcesMessage, muss ich auf ResponseMsg einen Speicherplatz im Speicher (Zeiger) festlegen, woher die DLL die Zeichenfolge erhalten wird.

Ich kann ResponseMsg = "REPLY" nicht einfach festlegen, weil wenn die Methode beendet wird, der Speicher, in dem die Zeichenfolge bereits zerstört wurde.

Wie kann ich das tun ?? Bitte jede Beratung ist willkommen !!

Danke!

+0

Das sieht auf den ersten Blick für mich OK. Wenn Sie ResponseMsg in Ihrem C# -Handler festlegen, was erhalten Sie dann wieder am C-Ende? Ein Debugfehler? Müll? Nichts? –

+0

Ich glaube, dass 'ref' dort eine doppelte Indirektion gibt - d. H. Er gibt kein' char * 'dort, sondern' char ** '. –

+0

Ah, tut mir leid, verpasste die Tatsache, dass es in der Tat ist, was erforderlich ist! –

Antwort

-1

Wie wäre:

IntPtr p = Marshal.GetFunctionPointerForDelegate(pmc); 
+0

R Ubben, in welchem ​​Punkt des Codes sollte ich es verwenden, und wie ?? –

0

Der Grund, warum das verwirrt P/Invoke ungewöhnliche Speicherverwaltung Semantik durch diese impliziert ist. Sie geben tatsächlich eine Zeichenfolge zurück, die auf unbekannte Weise dem Anrufer zugewiesen wurde, der sie dann nicht freigibt (zumindest soweit ich sehen kann). Dies ist ein ziemlich problematisches Design (nicht threadsicher und möglicherweise nicht reentrant) und definitiv nicht typisch.

Es gibt nicht wirklich etwas, was Sie hier mit der Zeichenfolge tun können, weil der C-Code nicht tatsächlich einen Zeiger direkt auf Ihre Zeichenfolge Daten erhält. Da C# -Zeichenfolgen Unicode sind und Sie angefordert haben, dass die Zeichenfolge als ANSI gemarshallt wird, wird eine "Kopie" (mit dem Ergebnis der Umwandlung von Unicode in ANSI) erstellt und zurückgegeben. Ich habe keine Ahnung von der Lebensdauer und auch nicht, wie ich das kontrollieren kann, und ich sehe keine Garantien in der Dokumentation.

So, sieht aus wie die beste Wahl ist dies selbst zu verwalten, Marshal.AllocHGlobal mit dem Puffer (wahrscheinlich für alle Anrufe nur einmal, die gleiche wie Ihre C-Code der Fall ist) zuzuteilen, Encoding.GetBytes Strings zu konvertieren Arrays Byte und Marshal.Copy um die resultierenden Bytes in den zugewiesenen Puffer zu kopieren.

+0

Speicherzuordnung durch die P/Invoke-Ebene ist ziemlich gut dokumentiert. In diesem Fall verwendet es den Standard-OLE-Taskspeicherzuordner - daher sollte CoTaskMemFree verwendet werden, um es auf der C-Seite freizugeben. –

+0

Pavel, danke für deine Kommentare. Aber ich habe nie mit diesen Typen gearbeitet, von denen du sprichst (AllocHGlobal, Marshal.Copy etc). Könnten Sie mir bitte helfen, alles zusammen zu stellen? Txs !!! –

+0

Ben, aus Neugier, können Sie einen Link geben, wo es das beschreibt? In jedem Fall bezweifle ich, dass der C-Code "CoTaskMemFree" es geht, und es scheint, dass es das ist, was es ist (d. H. Nicht korrigiert werden kann). –

1

Hier ist, was ich getan habe, um dies neu zu erstellen. Vielleicht wird mein Experiment helfen.

Der C# -Code:

public delegate void ProcessMessageCallback(
     [MarshalAs(UnmanagedType.LPStr)] ref string ResponseMsg); 

static class test 
{ 
    [DllImport("test.dll")] 
    static extern void TestCallback(ProcessMessageCallback callback); 

    static public void Main(string[] args) 
    { 
     TestCallback(MyCallback); 
    } 

    static void MyCallback(ref string ResponseMsg) 
    { 
     ResponseMsg = "hi there"; 
    } 
} 

Der C-Code (in einer DLL):

#include <windows.h> 
#include "objbase.h" 

__declspec(dllexport) void TestCallback(
    void (* CALLBACK managedCallback(char **))) 
{ 
    char *test = NULL; 
    managedCallback(&test); 
    printf(test); 
    CoTaskMemFree(test); // NB! 
} 

Diese erfolgreich "hallo dort" ausdruckt.

Hinweis: CoTaskMemFree sollte verwendet werden, um den vom P/Invoke-Layer zugewiesenen Speicher freizugeben. Wenn Sie die zurückgegebene Zeichenfolge länger als die Methode beibehalten möchten, die Ihren C# -Rückruf aufruft, ziehen Sie in Betracht, sie an einen anderen Speicherort zu kopieren, bevor Sie den zurückgegebenen Speicher freigeben.

0

Versuchen Sie, den Typ von ResponseMsg zu einem StringBuilder zu ändern, und stellen Sie sicher, dass die Kapazität ausreicht, um die Antwort zu speichern.

0

Der C# -Code:

public delegate void ProcessMessageCallback(StringBuilder ResponseMsg); 

static class test 
{ 
    [DllImport("test.dll")] 
    static extern void TestCallback(ProcessMessageCallback callback); 

    static public void Main(string[] args) 
    { 
     TestCallback(MyCallback); 
    } 

    static void MyCallback(StringBuilder ResponseMsg) 
    { 
     ResponseMsg.Append("hi there"); 
    } 
} 

Der C-Code (in einer DLL):

typedef int(__stdcall * Callback)(char *message); 

__declspec(dllexport) void TestCallback(Callback callback) 
{ 
    char* test = (char*)malloc(10000); 
    test[0] = '\0'; 
    callback(test); 
    printf(test); 
    free(test); 
} 
Verwandte Themen