シェルリンクとショートカットの作成

ショートカット

まずは、ショートカットファイルの作成をしてみましょう。ショートカットファイルの作成にはCOMを操作して行なう必要があります。COMを操作するよい手本なので、これを行なってみることにします。

シェルリンク

アプリケーションからショートカットを作成するには、Windowsが提供するシェルリンクと呼ばれるCOMオブジェクトを使います。

シェルリンクは、オブジェクトに関連付けられた別のオブジェクトにアクセスするための手段を提供するオブジェクトで、シェルが管理しているファイル、フォルダ、ドライブなどのオブジェクトに対して、間接的なアクセス手段(ショートカットなど)を提供します。

シェルリンクファイルは、拡張子が .lnk のバイナリ形式のファイルです。シェルリンクは、オブジェクトのデータを、このファイルに保存します。

シェルリンクオブジェクト作成と保存の手順

シェルリンクオブジェクトを作成して保存する場合、システムコンポーネントが提供するシェルリンクオブジェクトを作成し、IShellLink インターフェースと IPersistFile インターフェースの機能を使用することになります。

手順は以下のようになります。

  1. COMライブラリを初期化する。(LOLLIPOPモジュールにより自動的に行なわれる)

  2. シェルリンクオブジェクトを作成し、IShellLink インターフェースへのポインタを取得する。

  3. IShellLink::SetPath メソッドにより、リンク先ファイルをオブジェクト関連付ける。

  4. IShellLink::QueryInterface メソッドを呼び出して、IPersistFile インターフェースへのポインタを取得する。

  5. IPersistFile::Save メソッドにより、シェルリンクオブジェクトを保存する。

  6. IShellLinkIPersistFile インターフェースを解放する。

  7. COMライブラリを閉じる。(LOLLIPOPモジュールにより自動的に行なわれる)

COMライブラリの初期化とクローズ

シェルリンクではCOMライブラリが使われるので、これを初期化しておかなくてはなりません。また、使用後にライブラリのクローズも行なわなくてはなりません。

COMライブラリの初期化は CoInitialize 関数または CoInitializeEx 関数によって行なわれます。また、COMライブラリのクローズは CoUninitialize 関数を呼び出すことで行なわれます。

しかし、LOLLIPOP モジュールを使う場合には、モジュールをインクルードすることによって自動的に CoInitializeEx 関数が呼び出される仕組みになっています。また、プログラム終了時にはモジュールのクリーンアップ機能によって CoUninitialize 関数が自動的に呼び出される仕組みになっています。したがって、スクリプト中でこれらの関数を明示的に呼び出す必要はありません。

    #include "llmod.as"
    #include "rrmod/com/lollipop.as"

シェルリンクオブジェクト作成と IShellLink 取得

COM では、オブジェクトを作成するのに、CoCreateInstance 関数を呼び出します。この関数は、指定されたクラスIDのオブジェクトを作成して、指定されたインターフェースIDのインターフェースポインタを作成します。

実際には、この作業はLOLLIPOPモジュールが提供する createobj 命令によって行ないます。

createobj v1, s2, s3
v1 :インターフェースポインタを格納する変数
s2 :クラスID
s3 :インターフェースID

s2s3 には、それぞれ文字列形式のCLSIDおよびIIDを指定します。これにより、変数 v1 にインターフェースポインタが取得されます。

今回はシェルリンクオブジェクトを作成して、IShellLink インターフェースを取得しようとしているので、それぞれのクラス ID とインターフェース ID を文字列の形で指定します。

シェルリンクオブジェクトのクラス ID は

{00021401-0000-0000-C000-000000000046}

と定義されています。

また、 IShellLink インターフェースについてですが、これには IShellLinkAIShellLinkW の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 命令を使います。

icall v1, n2, v3, n4
v1 :インターフェースポインタ
n2 :メソッドのインデックス
v3 :引数を格納した配列変数
n4 :引数の数

SetPath メソッドのインデックスは 20 なので、それを指定して呼び出します。

    ; 関連付けるリンク先ファイルを指定
    sdim targetfile, 260
    targetfile = "C:\\Program Files\\hsp261\\hsp2.exe"

    ; IShellLink::SetPath メソッド呼び出し
    getptr pm, targetfile  ; ファイル名文字列のアドレス
    icall pShellLink, 20, pm, 1

ここでは、リンク先ファイル名の指定しかしていませんが、以下のメソッドを使って、いろいろな属性を設定することができます。これらは各自で試してみてください。

IShellLink::SetIconLocation
アイコンを設定
IShellLink::SetArguments
コマンドラインを設定
IShellLink::SetHotkey
ホットキーを設定
IShellLink::SetShowCmd
ウィンドウの初期表示状態を設定

IPersistFile の取得とオブジェクトの保存

次に、シェルリンクオブジェクトをファイルとして保存します。これには、オブジェクトが提供する IPersistFile インターフェースの機能を使用します。

あるオブジェクトのインターフェースから、そのオブジェクトの提供するほかのインターフェースを取得したい場合には、取得したいインターフェースの IID を指定して、QueryInterface を呼び出します。このメソッドはもともと IUnknown の持つメソッドですが、すべてのインターフェースは IUnknown から派生しているので、このメソッドを持ちます。

icall 命令を用いることもできますが、LOLLIPOP モジュールではこのメソッドを呼び出すための queryinterface 命令を提供しているので、それを使うことにします。

queryinterface v1, v2, s3
v1 :新しいインターフェースポインタを格納する変数
v2 :オブジェクトのインターフェースポインタ
s3 :文字列型のインターフェースID

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" をインクルードしておくことが必要です。

to_uni v1, v2, n3
v1 :Unicodeを格納する変数
v2 :Unicodeに変換する文字列変数
n3 :文字列の長さ

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 命令で行なうことができます。

release v1
v1 :解放するインターフェースポインタ

今回は2つのインターフェースポインタ(IShellLinkIPersistFile)を取得しているので、この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