まずは、ショートカットファイルの作成をしてみましょう。ショートカットファイルの作成にはCOMを操作して行なう必要があります。COMを操作するよい手本なので、これを行なってみることにします。
アプリケーションからショートカットを作成するには、Windowsが提供するシェルリンクと呼ばれるCOMオブジェクトを使います。
シェルリンクは、オブジェクトに関連付けられた別のオブジェクトにアクセスするための手段を提供するオブジェクトで、シェルが管理しているファイル、フォルダ、ドライブなどのオブジェクトに対して、間接的なアクセス手段(ショートカットなど)を提供します。
シェルリンクファイルは、拡張子が .lnk のバイナリ形式のファイルです。シェルリンクは、オブジェクトのデータを、このファイルに保存します。
シェルリンクオブジェクトを作成して保存する場合、システムコンポーネントが提供するシェルリンクオブジェクトを作成し、IShellLink インターフェースと IPersistFile インターフェースの機能を使用することになります。
手順は以下のようになります。
COMライブラリを初期化する。(LOLLIPOPモジュールにより自動的に行なわれる)
シェルリンクオブジェクトを作成し、IShellLink インターフェースへのポインタを取得する。
IShellLink::SetPath メソッドにより、リンク先ファイルをオブジェクト関連付ける。
IShellLink::QueryInterface メソッドを呼び出して、IPersistFile インターフェースへのポインタを取得する。
IPersistFile::Save メソッドにより、シェルリンクオブジェクトを保存する。
IShellLink、IPersistFile インターフェースを解放する。
COMライブラリを閉じる。(LOLLIPOPモジュールにより自動的に行なわれる)
シェルリンクではCOMライブラリが使われるので、これを初期化しておかなくてはなりません。また、使用後にライブラリのクローズも行なわなくてはなりません。
COMライブラリの初期化は CoInitialize 関数または CoInitializeEx 関数によって行なわれます。また、COMライブラリのクローズは CoUninitialize 関数を呼び出すことで行なわれます。
しかし、LOLLIPOP モジュールを使う場合には、モジュールをインクルードすることによって自動的に CoInitializeEx 関数が呼び出される仕組みになっています。また、プログラム終了時にはモジュールのクリーンアップ機能によって CoUninitialize 関数が自動的に呼び出される仕組みになっています。したがって、スクリプト中でこれらの関数を明示的に呼び出す必要はありません。
#include "llmod.as" #include "rrmod/com/lollipop.as"
COM では、オブジェクトを作成するのに、CoCreateInstance 関数を呼び出します。この関数は、指定されたクラスIDのオブジェクトを作成して、指定されたインターフェースIDのインターフェースポインタを作成します。
実際には、この作業はLOLLIPOPモジュールが提供する createobj 命令によって行ないます。
s2 と s3 には、それぞれ文字列形式のCLSIDおよびIIDを指定します。これにより、変数 v1 にインターフェースポインタが取得されます。
今回はシェルリンクオブジェクトを作成して、IShellLink インターフェースを取得しようとしているので、それぞれのクラス ID とインターフェース ID を文字列の形で指定します。
シェルリンクオブジェクトのクラス ID は
{00021401-0000-0000-C000-000000000046}
と定義されています。
また、 IShellLink インターフェースについてですが、これには IShellLinkA と IShellLinkW の2種類の定義があります。名前からだいたい想像がつくと思いますが、 IShellLinkA は、インターフェースでANSI文字列を扱うメソッドが提供され、 IShellLinkW は、Unicode文字列を扱うメソッドが提供されるというものです。本来COMでは、文字列をすべてUnicodeで渡すということが決められているのですが、シェルがシステムの中核をなすものであるためか、シェルが提供するCOMインターフェースの中には、他の多くの Win32 API と同じく、Windows 9x ではANSI版のインターフェースしかサポートしていないものがあり、この IShellLink もその1つです。したがって、ここでは、このANSI版のインターフェース IShellLinkA を使用します。インターフェースIDは
{000214EE-0000-0000-C000-000000000046}
です。これらをパラメータとすることで、シェルリンクオブジェクトが作成されて、 IShellLinkA インターフェースのインターフェースポインタを取得できます。
; シェルリンクオブジェクトのクラスID #define CLSID_ShellLink "{00021401-0000-0000-C000-000000000046}" ; IShellLink インターフェースのインターフェースID #define IID_IShellLinkA "{000214EE-0000-0000-C000-000000000046}" ; オブジェクトの作成 createobj pShellLink, CLSID_ShellLink, IID_IShellLinkA
IShellLink インターフェースポインタが取得できると、このインターフェースが持つメソッドを呼び出すことができるようになります。ここでは、指定したパスのファイルをオブジェクトへ関連付けるために、SetPath メソッドを呼び出します。
HRESULT STDMETHODCALLTYPE SetPath ( LPCSTR pszFile; );
スクリプト中でのメソッドの呼び出しは、LOLLIPOP モジュールで定義されている icall 命令を使います。
SetPath メソッドのインデックスは 20 なので、それを指定して呼び出します。
; 関連付けるリンク先ファイルを指定 sdim targetfile, 260 targetfile = "C:\\Program Files\\hsp261\\hsp2.exe" ; IShellLink::SetPath メソッド呼び出し getptr pm, targetfile ; ファイル名文字列のアドレス icall pShellLink, 20, pm, 1
ここでは、リンク先ファイル名の指定しかしていませんが、以下のメソッドを使って、いろいろな属性を設定することができます。これらは各自で試してみてください。
次に、シェルリンクオブジェクトをファイルとして保存します。これには、オブジェクトが提供する IPersistFile インターフェースの機能を使用します。
あるオブジェクトのインターフェースから、そのオブジェクトの提供するほかのインターフェースを取得したい場合には、取得したいインターフェースの IID を指定して、QueryInterface を呼び出します。このメソッドはもともと IUnknown の持つメソッドですが、すべてのインターフェースは IUnknown から派生しているので、このメソッドを持ちます。
icall 命令を用いることもできますが、LOLLIPOP モジュールではこのメソッドを呼び出すための queryinterface 命令を提供しているので、それを使うことにします。
v2 にもととなるオブジェクトのインターフェースポインタを指定し、 s3 には取得したいインターフェースの、文字列形式のインターフェースIDを指定することで、変数 v1 に取得したインターフェースポインタが格納されます。
IPersistFile のインターフェース ID は
{0000010b-0000-0000-C000-000000000046}
なので、これを指定します。
; IPersistFile インターフェースのインターフェース ID #define IID_IPersistFile "{0000010b-0000-0000-C000-000000000046}" ; IPersistFile インターフェースポインタ取得 queryinterface pPersistFile, pShellLink, IID_IPersistFile
IPersistFile インターフェースを取得したら、Save メソッドを呼び出して、オブジェクトをファイルとして保存します。
HRESULT Save( LPCOLESTR pszFileName, //ファイル名文字列のアドレス BOOL fRemember //ダーティーフラグのクリア指定 );
このメソッドのインデックスは 6 です。 pszFileName にはオブジェクトファイル(ショートカットファイル)のファイル名を、 fRemember には、ダーティーフラグをクリアするかどうかの指定をします。今回は fRemember に 1 (TRUE) を指定して、ダーティーフラグをクリアします。(クリアしなくても問題はないですけど。)
ここで、ファイル名のパラメータの型が LPCOLESTR になっていることに気付いたでしょうか。これは、Win32 環境では Unicode 文字列(ワイド文字列)のアドレスを示すものです。 IPersistFile インターフェースは IShellLink インターフェースのようにANSI版インターフェースが提供されてはいません。したがって、まずファイル名をUnicodeに変換しておかなくてはなりません。
Unicode文字列への変換は、 MultiByteToWideChar 関数を使います。
int MultiByteToWideChar( UINT uCodePage, // コードページ DWORD dwFlags, // フラグ PCSTR pMultiByteStr, // 変換元の文字列アドレス int cchMultiByte, // 文字列の長さ PWSTR pWideCharStr, // バッファアドレス int cchWideChar // バッファサイズ );
しかし、ここではこの関数は使用しないことにします。代わりに、 LLMOD モジュールで提供されている to_uni 命令を使用します。この命令は内部で MultiByteToWideChar 関数を呼び出しています。 to_uni 命令を使うにはあらかじめ "unicode.as" をインクルードしておくことが必要です。
v1 には、変換後の Unicode 文字列を格納するための変数を指定します。変換後の Unicode 文字列を格納できるだけの領域を確保しておく必要があります。命令実行後、この変数は数値型変数として扱われることになりますが、文字列変数として確保されたもの(sdim で確保された変数)を指定して問題ありません。 v2 には、変換する文字列を格納した文字列変数を指定し、 n3 には文字列のサイズを指定します。 n3 に -1 を指定すると、文字列の長さが自動的に判別されます。変換後、 stat には変換された文字数が格納されます。
これで文字列を Unicode に変換できたら、それを IPersistFile::Save メソッドに渡せば OK です。
#include "unicode.as" ; ショートカットファイル名(カレントディレクトリに作成) sdim lnkfile, 260 lnkfile = curdir + "\\HSP_ShortCut.lnk" ; Unicode に変換する sdim w_lnkfile, 520 ; Unicode を格納する変数 to_uni w_lnkfile, lnkfile, -1 ; IPersistFile::Save メソッド呼び出し getptr pm, w_lnkfile ; ファイル名(Unicode) pm.1 = 1 ; ダーティーフラグクリア icall pPersistFile, 6, pm, 2
COM オブジェクトが提供するインターフェースは、不要になったら解放する、というのが原則です。
オブジェクトは参照カウントを持っていて、アプリケーションが CoCreateInstance 関数(createobj 命令)や QueryInterface メソッド(queryinterface 命令)や AddRef メソッドを呼び出すことによってインターフェースを取得することで参照カウントが1つずつ加算されていきます。
逆に、 Release メソッドを呼び出すことによってオブジェクトの参照カウントは1つずつ減らされていき、参照カウントが 0 になったところでオブジェクトはメモリから解放されます。COM では、オブジェクトを作成してインターフェースを取得した場合は必ず Release メソッドを呼び出して、アプリケーション終了時にはオブジェクトが解放されている状態にしなければなりません。
Release メソッドの呼び出しは、LOLLIPOP モジュールで定義されている release 命令で行なうことができます。
今回は2つのインターフェースポインタ(IShellLink と IPersistFile)を取得しているので、この2つのインターフェースで Release メソッドを呼び出さなくてはなりません。 Release メソッドを呼び出す順番は、インターフェースを取得した順と逆の順番で行なうのが一般的です。(実際には、インターフェースが不要になったときに解放すればよいですが。)
インターフェースポインタを解放した後は、ポインタを格納していた値に入っている値を使うことはできません。間違って使ってしまわないよう、インターフェースポインタが入っていた変数をゼロにクリアしておくことをお勧めします。
; IPersistFile::Release メソッド release pPersistFile : pPersistFile = 0 ; IShellLink::Release メソッド release pShellLink : pShellLink = 0 end
上で記述してきたスクリプトでは、関数やメソッドが失敗したときのエラー処理を行なっていません。
createobj 命令でオブジェクトを作成したり、queryinterface 命令でインターフェースを取得するとき、何らかのエラーが発生した場合にはパラメータ v1 に指定した変数が 0 になるので、そこから判断しましょう。
icall 命令でインターフェースメソッドを呼び出すと、そのときの戻り値が変数 dllret に格納されるようになっています(LLMODモジュール互換)。COM のインターフェースが持っているメソッドは、そのほとんどが HRESULT 型の戻り値を返します。この32ビット値の最上位ビットが、メソッドが成功したか失敗したかを示しています。最上位ビットが 0 のときはメソッドが成功したことを、最上位ビットが 1 のときはメソッドが失敗したことを示します。符号付き32ビット値では最上位ビットは符号フラグですから、戻り値が 0 以上なら成功、負の値なら失敗ということになります。
したがって、HRESULT 型の戻り値を返すメソッド呼出し後の dllret の値が 0 よりも小さい場合には、エラーが発生したことになります。この場合には適切なエラー処理が必要になります。
#include "llmod.as" #include "unicode.as" #include "rrmod/com/lollipop.as" ; クラスIDの定義 #define CLSID_ShellLink "{00021401-0000-0000-C000-000000000046}" ; インターフェースIDの定義 #define IID_IShellLinkA "{000214EE-0000-0000-C000-000000000046}" #define IID_IPersistFile "{0000010b-0000-0000-C000-000000000046}" sdim targetfile, 260 sdim lnkfile, 260 ; リンク先ファイル名・ショートカットファイル名 targetfile = "C:\\Program Files\\hsp261\\hsp2.exe" lnkfile = curdir + "\\HSP_ShortCut.lnk" ; オブジェクトの作成 createobj pShellLink, CLSID_ShellLink, IID_IShellLinkA if pShellLink == 0 { dialog "シェルリンクオブジェクトを作成できません。", 1 goto *lb_quit } ; IShellLink::SetPath メソッド呼び出し getptr pm, targetfile ; ファイル名文字列のアドレス icall pShellLink, 20, pm, 1 if dllret < 0 { dialog "ファイルを関連付けできません。", 1 goto *lb_quit } ; IPersistFile インターフェースポインタ取得 queryinterface pPersistFile, pShellLink, IID_IPersistFile if pPersistFile == 0 { dialog "IPersistFile インターフェースを取得できません。", 1 goto *lb_quit } ; Unicode に変換する sdim w_lnkfile, 520 ; Unicode を格納する変数 to_uni w_lnkfile, lnkfile, -1 ; IPersistFile::Save メソッド呼び出し getptr pm, w_lnkfile ; ファイル名(Unicode) pm.1 = 1 ; ダーティーフラグクリア icall pPersistFile, 6, pm, 2 if dllret < 0 { dialog "オブジェクトを保存できません。", 1 goto *lb_quit } dialog "ショートカットファイルを作成しました。" *lb_quit ; 終了処理(インターフェース解放) if pPersistFile { release pPersistFile pPersistFile = 0 } if pShellLink { release pShellLink pShellLink = 0 } end