1

かなり大きな C API 用の .NET (c#) ラッパーがあります。このラッパーでは、ユーザーはネイティブ コードから繰り返し呼び出されるコールバックを提供できます。

コールバックは次のようになります。

[UnmanagedFunctionPointer(CallingConvention.StdCall, CharSet = CharSet.Unicode)]
public delegate void LogCallBack(SlmStream str, string wtsr, IntPtr handle);

ユーザーは次の方法でコールバックを入力できます。

public void SetLoggingCallback(LogCallBack lcallback,
                               IntPtr      handle)
{
  SlmReturn ret = (SlmReturn)Native.SlmSetLoggingCallbackW(ModelPtr,
                                                           lcallback,
                                                           handle);

  if( ret != SlmReturn.SlmRetOk )
  {
    throw new SlmException(ret,ret.ToString());
  }
}

最終的に呼び出している:

[DllImport("sulum20.dll",CallingConvention = CallingConvention.StdCall , CharSet = CharSet.Unicode)]
public static extern int SlmSetLoggingCallbackW(IntPtr      ModelPtr,
                                                LogCallBack lcallback,
                                                IntPtr      handle);

あるユーザーは、次の方法でコールバック ルーチンを呼び出しました (簡略化)。

string temp;
    SetLoggingCallback((str, wtsr, handle) => { temp = wtsr; Console.WriteLine(temp);  }, IntPtr.Zero);

これにより、一部のプラットフォームではアプリケーションがクラッシュし、他のプラットフォームではクラッシュしません。

だから私の質問は残っています、これは有効ですか?

C/C++ の世界から来て、1 つのことが私を困惑させます。

コールバックの範囲外で作成されたクラスのインスタンスにアクセスするコードは有効ですか (つまり、「string temp」)? 私の推測では、マーシャリングを制御できるようにするには、マーシャリングがそれらを入力/出力パラメーターとして持つ必要があるということです。handle パラメーターを使用して自分でマーシャリングを試みることを検討しましたが、やり過ぎかどうかはわかりません。

更新 1:

多分これは私がGCHandleを必要とするものですか?

更新 2:

コールバックは、ネイティブ コード ala から呼び出されます。

      if( logcallback_ != NULL )
      {
        (logcallback_)(cstream_,chbuf_,logcallbackhandle_);
      }

更新 3:

  string temp;
               LogCallBack logCallback = (str, wtsr, handle) =>
                                             {
                                                 temp = "Hello";
                                             };
               smodel.SetLoggingCallback(logCallback, IntPtr.Zero);

同じクラッシュが発生します。

更新 4:

typedef void (ISLMCALL *SlmLogCallBackW)(enum SlmStream,const wchar_t*, void *handle);

更新 5

また、試行して失敗しました:

 var logCallback = new LogCallBack(TargetMethod);
               smodel.SetLoggingCallback(logCallback, IntPtr.Zero);

    private string _test;

   private void TargetMethod(SlmStream str, IntPtr wtsr, IntPtr handle)
   {
       _test = "Hello";
   }

解決 :

GCHandler を使用してデリゲートを存続させ、ガベージ コレクションが行われないようにします。

4

1 に答える 1

1

デリゲートを見てみましょう。

[UnmanagedFunctionPointer(CallingConvention.StdCall, CharSet = CharSet.Unicode)]
public delegate void LogCallBack(SlmStream str, string wtsr, IntPtr handle);

まず第一に、私はそれが何でSlmStreamあるかを知らないので、それがどのようにマーシャリングされているかについてコメントすることはできません. それは確かに起こりうる失敗のベクトルです。

IntPtrハンドルは問題ありません。おそらく、ある種のポインターであるネイティブ側で、おそらくvoid*.

このコードの最も明白な問題は、文字列パラメーターwtsr. マーシャラーは、ワイド文字のヌル終了配列へのポインターを渡すことを前提としています。ネイティブ用語でそれはwchar_t*です。ただし、マーシャラーは、ネイティブ メモリを破棄する責任も負います。そして、メモリが COM ヒープから割り当てられたと想定し、CoTaskMemFree.

あなたのネイティブ コードは、COM ヒープに null で終了する文字配列を割り当てていない可能性が高いと思います。これは確かに一部のプラットフォームでのクラッシュを説明しますが、他のプラットフォームでは説明できません.

この問題を解決するには、いくつかの方法があります。

  1. ネイティブ コードを取得して COM ヒープから割り当て、マネージ コードに割り当てを解除させます。
  2. ネイティブ コードが割り当てと割り当て解除を行う場合は、デリゲートでパラメーターを として宣言し、デリゲートIntPtrを呼び出しMarshal.PtrToStringUniてマネージド文字列に変換します。

あなたのコメントは、オプション 2 が正しい解決策であることを教えてくれます。代理人は次のようになります。

[UnmanagedFunctionPointer(CallingConvention.StdCall, CharSet = CharSet.Unicode)]
public delegate void LogCallBack(SlmStream str, IntPtr wtsr, IntPtr handle);

そして、次のように実装します。

(str, wtsr, handle) => { Console.WriteLine(Marshal.PtrToStringUni(wtsr));  }

明らかにしなければならないもう 1 つのことは、ネイティブ コードがデリゲートを呼び出したときにまだ存在するように、デリゲートが存続していることを確認することです。

于 2013-06-18T14:15:08.387 に答える