1

私が書いているネイティブdllラッパーでは、IntPtrのすべての使用法(ハンドルをマーシャリングするため)をSafeHandlesに置き換えました。このように、正しく記述されたSafeHandle型はIntPtrと互換性があるという印象を受けました。

ただし、Marshal.GetFunctionPointerForDelegate呼び出しで例外がスローされるようになりました。

Cannot marshal 'parameter #n': SafeHandles cannot be marshaled from unmanaged to managed.

コールバックには引数リストにハンドルが含まれているため、デリゲートにはその場所にSafeHandleが含まれています(以前のIntPtrではありません)。だから私はこれをすることはできませんか?もしそうなら、コールバックをマーシャリングする必要があるとすると、SafeHandlesを使用するための私のオプションは何ですか?

ネイティブdllヘッダーの編集例を次に示します。

struct aType aType;
typedef void (*CallBackType)(aType*, int);
aType* create(); // Must be released
void   release(aType* instance);
int    doSomething(aType* instance, int argumnet);
void   setCallback(CallbackType func);

私に問題を引き起こしているのはコールバックです。C#側は次のようになりました。

delegate void CallBackType(IntPtr instance, int argument);

それで:

var funcPtr = Marshal.GetFunctionPointerForDelegate(del = new CallbackType(somefunc)):

NativeFunction.setCallback(funcPtr)

これは正常に機能し、常に実行されていました。ただし、ハンドルを管理するためのIntPtrからセーフハンドルに移行したかったので、交換のドロップであると読みました。ただし、上記のC#コードでIntPtrをSafeHandleサブクラスに置き換えると、報告される例外が発生します。

 delegate void CallBackType(MySafeHandle instance, int argument);
4

3 に答える 3

8

エラー メッセージは誤解を招きます。SafeHandles が作成されることになっているため、unmanaged から managed にセーフハンドルをマーシャリングすることは100% 可能です。たとえば、CreateFile がどのように定義されているかを確認してください。

[DllImport("kernel32.dll", CharSet=CharSet.Auto, SetLastError=true)]
private static extern SafeFileHandle CreateFile(string lpFileName, int dwDesiredAccess, 
    FileShare dwShareMode, SECURITY_ATTRIBUTES securityAttrs,
    FileMode dwCreationDisposition, int dwFlagsAndAttributes, IntPtr hTemplateFile);

コンパイラがエラー メッセージを生成する理由は、実際にはデリゲートの宣言方法にあります。私はあなたと同じ間違いを犯し、コールバック デリゲートを宣言したときに MySafeHandle 型をデリゲート パラメーターとして使用しようとしました (ここでは、アンマネージ コードがマネージ コードにコールバックします)。

delegate void TimerCallback(IntPtr pCallbackInstance, IntPtr context, MySafeHandle ptpTimer);

あなたとまったく同じエラーメッセージが表示されました。しかし、デリゲート シグネチャを IntPtr に変更すると、エラーが表示されなくなり、素朴な直感が間違っていることがわかります...

delegate void TimerCallback(IntPtr pCallbackInstance, IntPtr context, IntPtr ptpTimer);

ほら、出来上がり、エラーはなくなりました!あとは、デリゲートに渡される IntPtr を使用して正しい MySafeHandle オブジェクトを検索する方法を考えなければなりません...!

どの変更がエラーを修正したかを理解したら、エラーを修正する理由についての理論を思いつくこともできました。

理論: (未確認)

デリゲート シグネチャで IntPtr を使用する必要がある理由は、SafeHandles が特別だからです。SafeHandle としてマーシャリングするたびに、CLR マーシャラーは、不透明な IntPtr ハ​​ンドルを、問題の HANDLEを所有する新しい CLR SafeHandle オブジェクトに自動的に変換します。(SafeHandles は構造体ではなくオブジェクトであることに注意してください!)

デリゲートが呼び出されるたびに OS HANDLE の新しい所有者オブジェクトを作成した場合、デリゲートから戻るとオブジェクトがガベージ コレクションされるため、すぐに非常に大きな問題が発生します。

おそらく、コンパイラは単にこの間違いから私たちを救おうとしているのだと思います-独自の紛らわしい言葉遣いで?

于 2014-01-28T17:07:37.923 に答える
3

うーん...大声で考えているだけですが、ある種のインターラッパーを有効にする必要があると思います。SafeHandle基本的なマーシャリング中に P/invoke 実装で動作しますが、ここで行っているように「手動マーシャリング」では動作しません...このようなことを試してみてください。

internal delegate void InnerCallbackType(IntPtr instance, int argument);
public delegate void MyCallBackType(MySafeHandle instance, int argument);

public void SetCallback(Action<MySafeHandle, int> someFunc) 
{
    InnerCallbackType innerFunc = (rawHandle, rawArg) => 
    {
        someFunc(new MySafeHandle(rawHandle, true), rawArg);
    };
    var funcPtr = Marshal.GetFunctionPointerForDelegate(innerFunc);
    NativeFunction.setCallback(funcPtr);
}

SafeHandleそうすれば、マーシャリングを好きなように処理しながら、使用法に関して「型の安全性」を維持できます...

于 2013-01-25T18:39:38.697 に答える