2010/08/12

ネイティブコード関数からのコールバック

DLLなどが提供するネイティブコード関数の中には、他の関数へのポインタを引数として受取る物があります。
NILScriptでは、このようなコールバックで使用するためにJavaScriptの関数をネイティブコード関数でラップする機能も用意されています。
ここでは、この関数エクスポート機能の使い方について説明します。

まず、最初に関数の引数と返り値の型を指定して、関数ポインタ型のクラスを生成し、変数に代入しておきます。
下記は、Windowユニットで使用されている例です。

var EnumWindowsProc=StdCallFunction.define([UInt,Int],Int);

引数の型には、WideStringやMBStringなどの文字列系の型や、「new Struct()」で生成した構造体型を表すクラスなどを指定する事も出来ます。
「StdCallFunction」の他にも、「CFunction」や「ThreadSafeStdCallback」、「ThreadSafeCallback」というクラスがあります。
StdCallが付いているのが呼び出し規約がstdcallのもので、それ以外がcdeclのものです。ThreadSafeの意味については、後で説明します。

次に、下記のようにしてこの関数型を引数に取るネイティブコード関数のラッパー関数を生成します。

var EnumWindows=user32.proc('EnumWindows',[EnumWindowsProc,UInt],Int);

その後、コールバック関数型を指定した引数にJavaScriptの関数を渡して、ラッパー関数を呼び出します。

var handles=[];
EnumWindows(function(handle,lp){
 handles.push(handle);
 return(1);
},0);

この例では、EnumWindows()が呼び出されている間に、存在するトップレベルウィンドウの数だけコールバック関数が呼び出されます。

なお、コールバックの呼び出しが、コールバック関数を与えた関数から返った後に実行される可能性がある場合、関数を直接渡してはならないことに注意してください。
このような場合、以下のように関数型クラスの「from()」メソッドにコールバック関数を渡して関数ポインタオブジェクトを生成し、コールバック関数の呼び出しが起こる可能性がある限り参照が維持されるように、変数やプロパティに代入しておく必要があります。

//定義部分(抜粋)
var LowLevelMouseProc=StdCallFunction.define([Int,UInt,UInt],Int);
var SetWindowsHookEx=user32.proc("SetWindowsHookExW"
      ,[UInt,LowLevelMouseProc,UInt,UInt],UInt,true);

//関数ポインタオブジェクトの生成とコールバックの登録
var func=LowLevelMouseProc.from(function(code,wp,LP){/*** 略 ***/});
var hHook=SetWindowsHookEx(14,func,Main.handle,0);

/* 略(コールバックが呼び出される可能性のある処理(GetMessage()の呼び出し)) */

//コールバックの登録解除と関数ポインタオブジェクトの解放
hHook&&UnhookWindowsHookEx(hHook);
func&&func.free();

こうしておかないと、JavaScript関数やラッパー関数のメモリが途中で解放されてしまい、参照エラーや予期せぬ動作を引き起こしてしまうからです。
コールバック関数の登録解除処理を実行し、コールバック関数が呼ばれる可能性が無くなったら、関数ポインタオブジェクトの「free()」メソッドを呼び出して、関数ポインタオブジェクトが指しているラッパー関数のメモリ解放などを行います。

最後に、「ThreadSafe」の意味について説明します。
ThreadSafeの付いていない種類のコールバック関数では、関数ポインタオブジェクト生成時のスレッドのスクリプト実行コンテキストが使い回されるため、別のスレッドから呼び出されると不具合が生じてしまいます。
一方、ThreadSafeの付いている種類のコールバック関数では、毎回スクリプト実行コンテキストを新規生成して、それを使用してエクスポートされた関数を呼び出すため、別のスレッドからも問題なく呼び出せます。
ThreadSafe版を単一スレッド上から呼び出しても問題はありませんが、呼び出し時に多くのコストがかかるので、なるべくならThreadSafeでない方を使うことが望ましいでしょう。
実際の所は、ThreadSafeのコールバック関数が必要になるケースはそれほど多くはなく、NILScriptの標準機能の中では、今の所Thread.create()でしか使用されていません。

0 件のコメント:

コメントを投稿