2009-08-06 13 views
0

私は次のように定義されたCコールバックしました:C#のCコールバック関数の問題点 - ポインタに値を渡す方法?

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

Cで、このコールバックを使用してのexemple:

 
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; 
} 

それから私は、このコールバックは、C#で実装しました:

[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); 

コールバックの定義:

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); 
コールバックを設定します

方法:

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); 
     } 
    } 

そして最後の問題は、コールバックメソッド、:

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

問題がResponseMsgだから、実際にC.でPOINTERで、ありますProcesMessageでは、DLLが文字列を取得するメモリ(ポインタ)のスペースをResponseMsgに設定する必要があります。

単純にResponseMsg = "REPLY"を設定することはできません。これは、メソッドが終了した時点で、文字列がすでに破棄されていたメモリが終了するためです。

どうすればいいですか?アドバイスをお願いします歓迎!!

ありがとうございます!

+0

これは、一目見ただけで私にはOKに見えます。あなたのC#ハンドラでResponseMsgを設定すると、Cの最後に何が戻ってきますか?デバッグエラー?ごみ?何もない? –

+0

'ref'を使うと二重間接指定ができると思います。実際には' char * 'を渡すのではなく、' char ** 'を渡します。 –

+0

ああ、申し訳ありませんが、本当に必要なものであるという事実を逃しました! –

答えて

-1

方法について:

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

R Ubben、コード内のどの点で使用すればいいですか? –

0

これはP /呼び出しは、このことにより、暗示珍しいメモリ管理セマンティクスで混乱した理由。あなたは実際には未知の手段で割り当てられた文字列を呼び出し元に返します。呼び出し元はそれを解放しません(少なくとも私が見る限り)。これはむしろ問題のあるデザイン(スレッドセーフではなく、おそらくはリエントラントではない)であり、典型的なものではありません。

Cコードは文字列データに直接ポインタを受け取っていないので、実際には文字列では何もできません。 C#文字列はUnicodeであり、文字列をANSIとしてマーシャリングするように要求したため、(Unicode-ANSI変換の結果を含む)「コピー」が作成され、返されます。私はこれの寿命やこれを制御する方法について全く知らないし、ドキュメンテーションには何の保証もない。

だから、あなたの最善の策は、(一度だけ、すべての呼び出しのため、おそらく、あなたのCコードの場合と同じ)バッファの配列をバイトを文字列に変換するため、Encoding.GetBytesを割り当てるMarshal.AllocHGlobalを使用して、自分でこれを管理することであるように見える、とMarshal.Copy結果のバイトを割り当てられたバッファにコピーします。

+0

P/Invokeレイヤによるメモリ割り当ては、かなり詳細に文書化されています。この場合、デフォルトのOLEタスクメモリアロケータを使用します.C側で解放するにはCoTaskMemFreeを使用する必要があります。 –

+0

Pavel、ご意見ありがとうございます。しかし、私はあなたが話しているこれらの男(AllocHGlobal、Marshal.Copyなど)とは決して仕事しませんでした。あなたはそれをすべてtogheterにするのを助けてくれますか? Txs !!! –

+0

ベン、好奇心の念を抱いて、それを記述するリンクを教えてください。いずれにせよ、私はCコードが 'CoTaskMemFree'に行くのではないかと疑いますが、それはそれが何であるか(つまり修正できない)ようです。 –

1

これは私がこれを再現したものです。多分私の実験が助けになるでしょう。

C#コード:

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"; 
    } 
} 

(DLLで)Cコード:

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

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

これは正常 "こんにちは" プリントアウトします。

注:CoTaskMemFreeを使用して、P/Invokeレイヤによって割り当てられたメモリを解放する必要があります。返された文字列をC#コールバックを呼び出すメソッドより長く保ちたい場合は、返されたメモリを解放する前に別の場所にコピーすることを検討してください。

0

ResponseMsgのタイプをStringBuilderに変更し、容量がレスポンスを格納するのに十分であることを確認してください。

0

C#コード:

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"); 
    } 
} 

(DLL)でCコード:

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); 
} 
関連する問題