2011/01/25

IDLファイルからCOMオブジェクト定義を生成して使用する

WindowsがAPIとして用意している機能の中には、COMオブジェクトの形で提供されている物があります。
これらのCOMオブジェクトを利用するために、NILScriptにはCOMユニットが用意されています。
以前のバージョンでは、メソッドの定義を全て自力で記述しなければならないので面倒でしたが、最近の更新で定義を自動化するスクリプトが同梱されるようになったので、手軽に利用できるようになっています
ここでは、COMを利用した処理を行うスクリプトを記述する手順を説明します。

まず、あらかじめWindows SDKをMicrosoftのサイトから入手してインストールしておいてください。
そして、利用したいAPIがCOMオブジェクトとして提供されていることが分かったら、
「C:\Program Files\Microsoft SDKs\Windows\v6.0A\Include\」などのディレクトリ内のファイルを全文検索するなどして、どのIDLファイルで定義されているかを調べます。

次に、コマンドプロンプトで以下のようなコマンドラインを実行してください。
ng tool/idlConv "C:\Program Files\Microsoft SDKs\Windows\v6.0A\Include\shobjidl.idl" > shobjidl.ng
すると、IDLファイル内の定義文を元に、以下のようなクラス定義が列挙されたスクリプトが生成されます。
var PersistFile=Persist.define("PersistFile","{0000010B-0000-0000-C000-000000000046}",{
    IsDirty:[],
    Load:[
        WideString,        // [in] LPCOLESTR pszFileName
        UInt,              // [in] DWORD dwMode
    ],
    Save:[
        WideString,        // [in, unique] LPCOLESTR pszFileName
        Int,               // [in] BOOL fRemember
    ],
    SaveCompleted:[
        WideString,        // [in, unique] LPCOLESTR pszFileName
    ],
    GetCurFile:[
        Pointer,          // [out] LPOLESTR *ppszFileName
    ]
});
IDLファイルには多くの定義が含まれるので、必要な定義だけを探して自分のスクリプトにコピーしてください。
この時、「.define」の前の派生元クラスの部分が「Unknown」以外になっている場合は、その派生元クラスの定義も見つけてきて派生先クラスの定義より前の部分に記述しておく必要があります。
また、大元の派生元クラスとなる「Unknown」はCOMユニットで定義されているので、以下のような行を一番最初に記述してインポートしておきます。
var {COM,Unknown,BStr}=require('COM');
「COM」や「BStr」もCOMユニットで定義されているクラスで、COMを利用するスクリプトでたまに必要になることがあるので、常に記述するようにしておくといいでしょう。
なお、idlConvによる変換は常に正しく行われるとは限らないので、必ずMSDNのリファレンスなどと照らし合わせて、正しく記述されているか確認してから利用してください。

次に、COMオブジェクトを利用した処理の記述方法を説明します。
以下は、targetPathで指定したファイルを開くショートカットファイルをlnkPathで指定したファイルに保存するという処理の例です。
try{
 var link=ShellLinkW.create();
 var pf=link.toPersistFile();
 link.SetPath(targetPath);
 pf.Save(lnkPath,0);
}finally{
 free(pf,link);
}
「ShellLinkW.create()」は、C++などでの「CoCreateInstance(CLSID_ShellLink,NULL,CLSCTX_INPROC_SERVER,IID_IShellLink,(LPVOID*)&link);」に相当します。
クラスメソッド「create()」によるインスタンス生成を行うためには、idlConvで生成された定義にクラスプロパティ「classID」の定義が付加されている必要があります。もしclassIDの定義が正しく生成されていなかった場合は、各自でIDLファイルなどから「CLSID_~」の定義を探してきて追加しておいてください。

「link.toPersistFile()」は、「link->QueryInterface(IID_IPersistFile, (LPVOID*)&pf)」に相当します。
このメソッドは、「PersistFile」クラスの定義を実行したときに自動的に追加されます。

link.SetPath()とpf.Save()は、COMオブジェクトのメソッド呼び出しです。通常のNILScriptやJavaScriptのメソッドと違い、最初の文字も大文字になっているので注意してください。

最後に、finallyブロック内で「free(pf,link)」を実行して、生成したオブジェクトを解放する必要があります。
これは「if(pf) pf->Release();if(link) link->Release()」に相当します。

このように、最初の定義が多少面倒ですが、処理本体は比較的簡潔に記述できるようになっています。
COMオブジェクトとして提供されているAPIには様々なものがあるので、COMオブジェクトの使用方法を覚えておけば、役に立つことがあるでしょう。