シェルネームスペースとPIDL

シェルネームスペース

Windows は、シェルの管理領域であるシェルネームスペース と呼ばれるものを提供しています。

このシェルネームスペースは、ファイルとディレクトリで構成されるディスクシステムの(ある意味ハードウェア的な)概念に対して、ファイル、ディレクトリ、そのほかの仮想フォルダと呼ばれるWindowsオブジェクトを含むシステムの(ソフトウェア的な)概念を持ちます。Windows は、これらのすべてをオブジェクトとして、シェルネームスペースのデスクトップを基点としたフォルダツリー形式の階層構造で管理しています。

アイテムID と アイテムIDリスト と PIDL

シェルネームスペース内の個々のオブジェクトは、アイテムIDアイテムIDリストという識別子を持ちます。

アイテムID(item ID)は、オブジェクトの所属する親フォルダ内でのみ一意の識別子です。したがって、アイテムIDだけではシェルネームスペース上で1つのオブジェクトを識別することはできません。

そこで、オブジェクトにはもう1つの識別子であるアイテムIDリスト(item ID list)が与えられます。このアイテムIDリストは、複数のアイテムIDで構成されるリストのことです。これによって、シェルネームスペース上で一意の識別子となります。

Windows シェルは、シェルネームスペース領域内部のオブジェクトを、PIDL(アイテムIDリストへのポインタ)を通じて管理しています。アイテムIDリストは実際には ITEMIDLIST 構造体に格納されているので、PIDL は、この構造体のアドレスということになります。

特殊フォルダを取得する

シェルネームスペースにより、通常のファイル・ディレクトリ操作では行なうことのできないことを実現できます。その1つに、「デスクトップ」フォルダや「マイ コンピュータ」フォルダなどといった特殊フォルダを取得するということがあります。今回はそれを行なってみましょう。

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

  1. SHGetMalloc 関数を使って、シェルの IMalloc インターフェースへのポインタを取得する。
  2. SHGetSpecialFolderLocation 関数を呼び出して、目的となるフォルダの PIDL を取得する。
  3. SHGetPathFromIDList 関数を呼び出して、PIDL をパス名に変換する。
  4. IMalloc::Free メソッドでアイテム ID リスト用に確保されていたメモリを解放する。
  5. IMalloc インターフェースを解放する。

IMalloc インターフェースの取得

まず最初に、 SHGetMalloc 関数でシェルの IMalloc インターフェースを取得しなければなりません。なぜなら、あとで使用する SHGetSpecialFolderLocation 関数によって割り当てられるメモリを解放するのに、シェルが提供するメモリアロケータ(メモリ管理を行なうオブジェクト)の機能を使わなければならないためです。

SHGetMalloc 関数は、シェルのメモリアロケータオブジェクトを作成し、その IMalloc インターフェースを返します。

HRESULT  SHGetMalloc(
    LPMALLOC *ppMalloc
);

ppMalloc パラメータには、インターフェースポインタを格納するための変数のアドレスを指定します。これにより、この変数に IMalloc インターフェースポインタが格納されます。この関数は HRESULT 型の戻り値を返しており、戻り値が負の場合はエラーが発生したことを示します。

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

    ; IMalloc インターフェース取得
    getptr  pm, pMalloc
    dllproc "SHGetMalloc", pm, 1, D_SHELL

PIDLの取得

次に、SHGetSpecialFolderLocation 関数を呼び出して、フォルダのPIDLを取得します。

HRESULT SHGetSpecialFolderLocation(
    HWND  hwndOwner,
    int   nFolder,
    LPITEMIDLIST *ppidl
);

hwndOwner は、ダイアログなど表示させるときのオーナーウィンドウのハンドルを指定しますが、 0 (NULL) を指定して問題ありません。 nFolder には、どの特殊フォルダを取得するのかを指定します。例えば、「プログラム」フォルダを取得したい場合は 0x0002 (CSIDL_PROGRAMS) を指定します。 ppidl には、取得したPIDLを格納する変数のアドレスを指定します。

このとき、実際にアイテムIDリストが格納されるバッファはシェルによって確保されます。そして、そのバッファにアイテムIDリストが格納され、そのバッファのアドレスがPIDLとして ppidl で指定した変数に格納されます。

    ; PIDL取得
    pm.0 = 0
    pm.1 = 0x0002               ; CSIDL_PROGRAMS (「プログラム」フォルダ)
    getptr  pm.2, pidl          ; PIDL を格納する変数
    dllproc "SHGetSpecialFolderLocation", pm, 3, D_SHELL

パス名の取得

PIDL からパス名を取得するには、SHGetPathFromIDList 関数を使います。

BOOL SHGetPathFromIDListA(
    LPCITEMIDLIST  pidl,
    LPSTR  pszPath
);

pidl には、フォルダのPIDLを、 pszPath にはパス名を格納する文字列変数のアドレスを指定します。

    ; PIDL からパス名取得
    sdim  pathname, 260         ; パス名を格納する変数
    pm.0 = pidl                 ; PIDL
    getptr  pm.1, pathname
    dllproc "SHGetPathFromIDListA", pm, 2, D_SHELL

    mes "「プログラム」フォルダのパスは" + pathname

メモリ解放とインターフェース解放

アイテムIDリストを格納するためにシェルによって確保されたメモリブロックは、アプリケーション側で解放しなければなりません。これには、はじめに取得した IMalloc インターフェースの Free メソッドを使用します。

void  Free(
    void*  pv
);

pv は、解放するメモリブロックのアドレスです。このパラメータには PIDL をそのまま渡します。このメソッドのインデックスは 5 なので、 icall 実行時にこの値を指定します。

最後に、 release 命令で IMalloc インターフェースを解放します。

    ; IMalloc::Free メソッド
    pm = pidl                   ; PIDL
    icall pMalloc, 5, pm, 1

    ; インターフェース解放
    release  pMalloc

特殊フォルダのパス名 取得サンプルスクリプト

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

    ; IMalloc インターフェース取得
    getptr  pm, pMalloc
    dllproc "SHGetMalloc", pm, 1, D_SHELL
    if (stat < 0)|(pMalloc == 0){ ; 戻り値は HRESULT 型(負のときエラー)
        dialog "IMalloc インターフェースを取得できませんでした", 1
        end
    }

    ; PIDL取得
    pm.0 = 0
    pm.1 = 0x0002                 ; CSIDL_PROGRAMS (「プログラム」フォルダ)
    getptr  pm.2, pidl            ; PIDLを格納する変数
    dllproc "SHGetSpecialFolderLocation", pm, 4, D_SHELL
    if stat < 0 {                 ; 戻り値は HRESULT 型(負のときエラー)
        dialog "PIDL を取得できませんでした", 1
        goto *lb_free
    }

    ; PIDL からパス名取得
    sdim  pathname, 260           ; パス名を格納する変数
    pm.0 = pidl                   ; PIDL
    getptr  pm.1, pathname
    dllproc "SHGetPathFromIDListA", pm, 2, D_SHELL
    if stat == 0 {                ; 戻り値は BOOL 型(0 のときエラー)
        dialog "PIDL からパス名を取得できませんでした", 1
        goto *lb_free
    }

    dialog "「プログラム」フォルダのパスは[" + pathname + "]です"

*lb_free
    if pMalloc {
        ; IMalloc->Free(pidl);
        pm = pidl                     ; PIDL
        icall  pMalloc, 5, pm, 1

        ; インターフェース解放
        release  pMalloc
    }

    end