Delphi Programming / Object Pascal
サイトのトップページへ リンクのページへ ロゴマーク
[掲載 2005年02月11日] [更新 2016年12月18日] Delphi サンプルプログラム集
   
460_特殊フォルダのフルパスを取得
動作確認等 Windows 7 U64(SP1) + Delphi XE Pro
Windows Vista 以降専用,Windows 7 以降,Delphi XE5 以降専用サンプルあり
460_SpecialFolderPath.zip 6,552 KB] 2016年12月18日版(EXE同梱)



  • 2013年07月06日 (過去の履歴は省略)
  • 関数別のサンプルにして,記事の構成を全面的に書き直し,解説的な内容も追加
  • 2014年01月25日
  • GetEnvironmentVariable 関数と TPath 関係の関数のサンプルを追加




特殊フォルダのフルパスを取得するための関数


シェル関係の関数類を使用して,特殊フォルダのパス名を取得するサンプルです.
特殊フォルダのパスを取得する関数には下表のようなものがあります.1 つは従来からある関数類で,CSIDL という値を使用して特殊フォルダのパス名または項目識別子を取得します.もう 1 つは Vista 以降で使用可能になった KNOWNFOLDERID (または FOLDERID) と呼ばれる値を使用して取得します.

SHFolder.pas (SHfolder.dll) にも SHGetFolderPath 関数がありますが,これは Windows 9X, 2000 初期の時代のものです (Internet Explorer 5.0 以前の環境用).互換用に残されています.現在は ShlObj.pas (Shell32.dll) 内にある同名の関数を使用することになります.

全ての特殊フォルダのパス名あるいは項目識別子が取得できるわけではありません.使用しているコンピュータに実装されていないフォルダや操作できないフォルダの取得は失敗します.また,ファイルシステムのパス名しか取得できない関数では,GUID で表わされる仮想フォルダは取得できません.

添付の CSIDL と FOLDERID のリストは,Delphi XE で定義されているものですが,2013年07月05日現在,MSDN オンラインの記事にあるリストと同じで,全ての定数値が含まれています.


関数名 説明
SHGetFolderPath CSIDL の値から特殊フォルダのパスを取得
SHGetSpecialFolderPath 関数の機能拡張版
仮想フォルダの取得は失敗する
Vista 以降では,内部で SHGetKnownFolderPath を実行する
つまり,SHGetKnownFolderPath のラッパー関数
SHGetSpecialFolderPath CSIDL の値からフォルダのパスを取得
仮想フォルダの取得は失敗する
サポート外.代わりに ShGetFolderPath を使用する
SHGetFolderLocation CSIDL の値からフォルダの項目識別子を取得
SHGetSpecialFolderLocation 関数の機能拡張版
Vista 以降では,内部で SHGetKnownFolderIDList を実行する
つまり,SHGetKnownFolderIDList のラッパー関数
SHGetSpecialFolderLocation CSIDL の値からフォルダの項目識別子を取得
サポート外.代わりに SHGetFolderLocation を使用する
SHGetKnownFolderPath Windows Vista 以降用.Delphi 2010 以降で使用可能
KNOWNFOLDERID の値からフォルダのパスを取得
SHGetKnownFolderIDList Windows Vista 以降用.Delphi 2010 以降で使用可能
KNOWNFOLDERID の値からフォルダの項目識別子を取得
SHGetKnownFolderItem Windows 7 以降用.Delphi 2010 以降で使用可能
KNOWNFOLDERID の値から IShellItem オブジェクトを取得
IShellItem は Windows XP(SP1) から使用可能


[備考]
ネット上に,SHGetFolderPath よりも,サポート外の SHGetSpecialFolderPath の方が新しい,という記事があります.
記事の掲載日や環境の記載がない記事もあり,情報の判断が困難な場合がありますが,おそらく,歴史的には次のように言えるようです.
  • Windows 9X の時代に SHGetFolderPath が SHFolder.dll で提供された
  • その後,Windows 2000 の Shell32.dll に SHGetSpecialFolderPath が実装された
    (Shell32.dll version 4.71 で追加)
  • その後 SHGetSpecialFolderPath を拡張した SHGetFolderPath が実装された
    (Shell32.dll version 5.0 で追加)
  • 互換性のために SHFolder.dll を現在も提供している
古いアプリケーションの中には,SHFolder.dll がないためにエラーとなるものがあるようです.
Internet Explorer 5.0 以前の環境では,SHFolder.dll を使用することになります.




01_基本的なコード


上の表の一部を除いた関数の簡単なテストです.[Program Files] または [Program Files (x86)] のフォルダのフルパスを取得します.
特殊フォルダのパスを取得する関数の使い方としては,これだけで終わりですが,これだけではサンプルプログラム集として寂しいので,これ以降は,各々の関数で,指定した特殊フォルダのパス名がどんな値になるか,あるいは,どんなファイルがあるのかを確認できるように,TOpenDialog で,あるいはエクスプローラで表示するようにしています.

エクスプローラでは表示できても,TOpenDialog では表示できないフォルダもあります.例えばコントロールパネルが該当します.これは,TOpenDialog の目的から考えても当然と言えます.そのようなことも確認,テストするためのサンプルです.



図1
32 ビットの EXE を作成して実行した結果
  • CSIDL_PROGRAM_FILES のパスを取得した場合
  • どのボタンでも結果は同じ
図2
64 ビットの EXE を作成して実行した結果
  • CSIDL_PROGRAM_FILES のパスを取得した場合
  • どのボタンでも結果は同じ
  • 32 ビット版の Windows ではこの結果となる

リスト1
特殊フォルダのフルパスを取得する関数類の簡単なテスト
unit Unit1;

interface

uses
  Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs,
  StdCtrls, ExtCtrls;

type
  TForm1 = class(TForm)
    Button1: TButton;
    Button2: TButton;
    Button3: TButton;
    Button4: TButton;
    procedure FormCreate(Sender: TObject);
    procedure Button1Click(Sender: TObject);
    procedure Button2Click(Sender: TObject);
    procedure Button3Click(Sender: TObject);
    procedure Button4Click(Sender: TObject);
  private
    { Private 宣言 }
  public
    { Public 宣言 }
  end;

var
  Form1: TForm1;

implementation

{$R *.DFM}

uses ShlObj, KnownFolders, ActiveX;

//=============================================================================
//  フォーム生成時の処理
//=============================================================================
procedure TForm1.FormCreate(Sender: TObject);
begin
  if not CheckWin32Version(6, 0) then begin
    Button3.Enabled := False;
    Button4.Enabled := False;
  end;
end;

//=============================================================================
//  CSIDL値からフォルダのパス名を取得
//
//  SHGetFolderPathの使用には,usesにShlObjが必要
//  CSIDL_XXXの使用には,usesにShlObjが必要
//=============================================================================
procedure TForm1.Button1Click(Sender: TObject);
var
  Buff : array [0..MAX_PATH-1] of Char;
  iRet : HRESULT;
  ADir : String;
begin
  FillChar(Buff, SizeOf(Buff), #0);

  iRet := SHGetFolderPath(0, CSIDL_PROGRAM_FILES, 0, SHGFP_TYPE_CURRENT, @Buff);
  if iRet = S_OK then begin
    ADir := Buff;
    MessageBox(0, PChar(ADir), '結果', MB_OK);
  end;
end;

//=============================================================================
//  CSIDL値からフォルダの項目識別子を取得
//
//  SHGetFolderLocationの使用には,usesにShlObjが必要
//  CSIDL_XXXの使用には,usesにShlObjが必要
//  CoTaskMemFreeの使用には,usesにActiveXが必要
//=============================================================================
procedure TForm1.Button2Click(Sender: TObject);
var
  ItemID : PItemIDList;
  Buff   : array [0..MAX_PATH-1] of Char;
  ADir   : String;
begin
  //最初にnilにしておくと,解放の際にnilがどうかで判定が可能
  ItemID := nil;

  if SHGetFolderLocation(0, CSIDL_PROGRAM_FILES, 0, 0, ItemID) = S_OK then begin
    //項目識別子からパス名を取得
    FillChar(Buff, SizeOf(Buff), #0);
    SHGetPathFromIDList(ItemID, @Buff);
    ADir := Buff;
  end;

  //項目識別子のメモリアロケートの解放
  if ItemID <> nil then CoTaskMemFree(ItemID);

  MessageBox(0, PChar(ADir), '結果', MB_OK);
end;

//=============================================================================
//  FOLDERIDからフォルダのパス名を取得
//  Windows Vista以降用
//
//  SHGetKnownFolderPathの使用には,usesにShlObjが必要
//  FOLDERID_XXXの使用には,usesにKnownFoldersが必要
//=============================================================================
procedure TForm1.Button3Click(Sender: TObject);
var
  pFolderPath : PChar;
  iRet        : HRESULT;
  FolderPath  : String;
begin
  iRet := SHGetKnownFolderPath(FOLDERID_ProgramFiles, 0, 0, pFolderPath);

  if iRet = S_OK then begin
    FolderPath := pFolderPath;
    MessageBox(0, PChar(FolderPath), '結果', MB_OK);
  end;
end;

//=============================================================================
//  FOLDERIDからフォルダの項目識別子を取得
//  Windows Vista以降用
//
//  SHGetKnownFolderIDListの使用には,usesにShlObjが必要
//  FOLDERID_XXXの使用には,usesにKnownFoldersが必要
//  CoTaskMemFreeの使用には,usesにActiveXが必要
//=============================================================================
procedure TForm1.Button4Click(Sender: TObject);
var
  ItemID : PItemIDList;
  Buff   : array [0..MAX_PATH-1] of Char;
  ADir   : String;
begin
  //最初にnilにしておくと,解放の際にnilがどうかで判定が可能
  ItemID := nil;

  if SHGetKnownFolderIDList(FOLDERID_ProgramFiles, 0, 0, ItemID) = S_OK then begin
    //項目識別子からパス名を取得
    FillChar(Buff, SizeOf(Buff), #0);
    SHGetPathFromIDList(ItemID, @Buff);
    ADir := Buff;
  end;

  //項目識別子のメモリアロケートの解放
  if ItemID <> nil then CoTaskMemFree(ItemID);

  MessageBox(0, PChar(ADir), '結果', MB_OK);
end;

end.




02_LPTSTR と PWSTR 型の文字列の扱い


マイクロソフト SDK や MSDN の関数類の定義をみると,文字列型には,プリフィックスが LP と P で始まるものがあります.
上のサンプルでは,SHGetFolderPath 関数の引数が LPTSTR, SHGetKnownFolderPath 関数が PWSTR となっています.プリフィックスの LP は Long Pointer の意味で,主に Windows 9X の 16 ビットの時代に 32 ビット値を処理するための仕組みです.比較的新しい関数類の文字列の多くは,PWSTR のように,プリフィックスがポインタを意味する P となっているようです.

プリフィックスが LP の文字列型を扱う場合,例えば次のように,Char 型の配列を用意して文字列の領域を確保してから,その先頭アドレスを関数の引数に渡します.後は Delphi がうまく処理してくれます.

  Buff : array [0..MAX_PATH-1] of Char;

引数に PChar 型を渡す場合は,例えば次のようにしてメモリ領域を確保します.[670_他のアプリへ文字列送信と取得] にも参考コードがあります.


procedure TForm1.Button1Click(Sender: TObject);
var
  pFolderPath : PChar;
  FolderPath  : String;
begin
  pFolderPath := StrAlloc(MAX_PATH + 1);
  try
    if SHGetFolderPath(0, CSIDL_PROGRAM_FILES, 0, 0, pFolderPath) = S_OK then begin
      FolderPath := pFolderPath;
      MessageBox(0, PChar(FolderPath), '結果', MB_OK);
    end;
  finally
    StrDispose(pFolderPath);
  end;
end;


引数に String 型を渡す場合は,例えば次のように,文字列の長さ分のメモリ領域を確保します.これも [670_他のアプリへ文字列送信と取得] に参考コードがあります.

Windows API の中には,指定の引数の値を 0 や -1 にすると,必要なバッファサイズを取得できる関数がありますが,長さ制限のあるパス名の取得関数類にはないようです.


procedure TForm1.Button1Click(Sender: TObject);
var
  FolderPath : String;
begin
  SetLength(FolderPath, MAX_PATH);
  if SHGetFolderPath(0, CSIDL_PROGRAM_FILES, 0, 0, PChar(FolderPath)) = S_OK then begin
    MessageBox(0, PChar(FolderPath), '結果', MB_OK);
  end;
end;


上のコードの処理をしないと,実行後に,下図のような,「アドレス... でアドレス... に対する読み取り違反がおきました」という,メモリへのアクセス関係エラーが発生する場合があります.


図3
メモリアクセス関係エラー

プリフィックスが P の文字列型の場合は,特にメモリ領域を確保しなくても,筆者が現在確認したところでは,Delphi がうまく処理してくれるようです.上の Button3Click イベントのコードのように取得できるようです.動作を確実にするためには,プリフィックスが LP の文字列型と同じようにしておいた方が無難かも知れません.DLL 内でどのように処理が行われているかは,その関数によるかも知れません.

文字列型の扱いは,ネット上の C++ のサンプルコードを見ても,疑問に思うコードがあり,その使用方法に迷う方もいるようです.その点,Windows API の関数類を使用しないのであれば,Delphi の文字列型の扱い方は単純明快です.





03_SHGFP_TYPE 定数と KNOWN_FOLDER_FLAG


SHGetFolderPath と SHGetFolderLocation 関数の第 4 引数には下表のどちらかの値を指定するようになっています.特殊フォルダを別の場所に設定した場合等の判定に利用できます.
SHGetKnownFolderPath と SHGetKnownFolderIDList では,同じような指定に,KNOWN_FOLDER_FLAG 列挙型の値を使用します.


定数名 説明
SHGFP_TYPE_CURRENT 0 実際のパスを取得する場合に指定
SHGFP_TYPE_DEFAULT 1 デフォルトのパスを取得する場合に指定




04_項目識別子のメモリアロケート ( 割り当て ) 処理


項目識別子を使用する関数では,関数の利用後に,アプリケーション側で,関数が割り当てた項目識別子のメモリアロケートを解放する必要がある場合があります.
メモリアロケーションの解放には CoTaskMemFree(ItemID) を使用します.
実際に必要かどうかは,各々の関数の説明で確認することになります.

  if ItemID <> nil then CoTaskMemFree(ItemID);

シェル関数類の説明には,項目識別子のメモリアロケーションの解放に ILFree を使用するように書いてあるものがあります.
ところが,ILFree の説明を読むと,Windows 2000 以降ではむしろ CoTaskMemFree を使用すべきとあります.そこで,本ページのサンプルでは,CoTaskMemFree を使用しています.

項目識別子のメモリアロケーションの解放を確実にするには,次のように,使用する前に nil を代入しておくと便利です.これは,コンポーネントやクラス類を実行時に生成する場合にも利用できます.

  ItemID := nil;





05_SHGetFolderPath 関数


SHGetFolderPath 関数を使用した特殊フォルダのパス名を取得するサンプルです.
取得可能な特殊フォルダはファイルシステムだけです.仮想フォルダは取得できません.


図4
設計時画面
  • 以下を配置
    CSIDL を一覧表示する TListBox
    フルパスを表示する TEdit
図5
実行時画面
  • ListBox1 に CSIDL 値をリスト表示
  • 項目ダブルクリックまたは [ENTER] で該当フォルダ内を表示
  • TEditに フルパス表示
図6
TOpenDialog でデスクトップを表示

リスト2
SHGetFolderPath 関数で特殊フォルダのフルパスを取得
unit Unit1;

interface

uses
  Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs,
  StdCtrls, ExtCtrls;

type
  TForm1 = class(TForm)
    ListBox1: TListBox;
    Edit1: TEdit;
    OpenDialog1: TOpenDialog;
    RadioGroup1: TRadioGroup;
    procedure FormCreate(Sender: TObject);
    procedure ListBox1Click(Sender: TObject);
    procedure ListBox1DblClick(Sender: TObject);
    procedure ListBox1KeyDown(Sender: TObject; var Key: Word;
      Shift: TShiftState);
    procedure RadioGroup1Click(Sender: TObject);
  private
    { Private 宣言 }
  public
    { Public 宣言 }
  end;

var
  Form1: TForm1;

implementation

{$R *.DFM}

uses ShlObj, ShellAPI;

//=============================================================================
//  Form生成時の処理
//  ディスクからCSIDL値を格納したファイルを読み込み
//  ListBox1の各ItemにAddObjectでListBox1に表示する文字列と各CSIDL値をセット
//=============================================================================
procedure TForm1.FormCreate(Sender: TObject);
var
  SL     : TStringList;
  SLTemp : TStringList;
  i      : Integer;
begin
  SL     := TStringList.Create;
  SLTemp := TStringList.Create;
  try
    SL.LoadFromFile('../CSIDL_List.csv');
    SL.Sort;
    ListBox1.Items.BeginUpdate;
    for i := 0 to SL.Count - 1 do begin
      SLTemp.CommaText := SL[i];
      ListBox1.Items.AddObject(SLTemp[0], TObject(StrToInt(SLTemp[1])));
    end;
    ListBox1.Items.EndUpdate;
  finally
    FreeAndNil(SL);
    FreeAndNil(SLTemp);
  end;
end;

//=============================================================================
//  ListBoxの項目をダブルクリックしたらそのCSIDLに相当するのフォルダ名を取得
//  そのフォルダを[ファイルを開く]ダイアログまたはエクスプローラで開く
//=============================================================================
procedure TForm1.ListBox1DblClick(Sender: TObject);
var
  AIndex  : Integer;
  CSIDL   : Integer;
  Buff    : array [0..MAX_PATH] of Char;
  ADir    : String;
  ExeFile : String;
  hInst   : Cardinal;
begin
  Edit1.Text := '';

  //ListBox1のIndexを取得してそのTObjectで定義した値を取出す
  AIndex := ListBox1.ItemIndex;
  CSIDL  := Integer(ListBox1.Items.Objects[AIndex]);

  FillChar(Buff, SizeOf(Buff), #0);
  if SHGetFolderPath(0, CSIDL, 0, 0, @Buff) = S_OK then begin
    ADir       := Buff;
    Edit1.Text := ADir;

    //以下,取得した値の確認用
    if (Sender <> nil) and (ADir <> '') then begin
      if RadioGroup1.ItemIndex = 0 then begin
        OpenDialog1.InitialDir := ADir;
        OpenDialog1.Execute;
        Update;
      end else begin
        //実際には起動アプリが空でもエクスプローラは起動する
        //あるいは,起動ファイルにパス名を指定すれば起動する
        ExeFile := 'explorer.exe';
        hInst   := ShellExecute(Handle,
                                'open',
                                PChar(ExeFile),
                                PChar(ADir),
                                nil,
                                SW_SHOW);
        if hInst <= 32 then begin
          MessageBox(Handle, '起動に失敗しました.', '情報', MB_ICONINFORMATION);
        end;
      end;
    end;
  end;
end;

//=============================================================================
//  ListBox1の項目を選択したら,パス名を取得してEdit1に表示
//=============================================================================
procedure TForm1.ListBox1Click(Sender: TObject);
begin
  ListBox1DblClick(nil);
end;

//=============================================================================
//  ListBox1で[Enter]キーを押したら,ダブルクリックと同じ動作をさせる
//=============================================================================
procedure TForm1.ListBox1KeyDown(Sender: TObject; var Key: Word;
  Shift: TShiftState);
begin
  if Key = VK_RETURN then begin
    ListBox1DblClick(Sender);
  end;
end;

//=============================================================================
//  RaditoGroup1のOnClickイベント処理
//  ListBox1にフォーカス移動して[Enter]キー操作可能にする
//=============================================================================
procedure TForm1.RadioGroup1Click(Sender: TObject);
begin
  ListBox1.SetFocus;
end;

end.




06_SHGetFolderLocation 関数


SHGetFolderLocation 関数を使用して,特殊フォルダの項目識別子を取得するサンプルです.
項目識別子では,TOpenDialog や ShellExecute 関数でフォルダを表示できません.そこで,[818_シェル名前空間と項目識別子] のサンプルと同じように,デスクトップの IShellFolder を求め,そのメソッド GetDisplayNameOf でフォルダのパス名とエクスプローラで表示される表示名を取得します.

この時取得できるパス名は,仮想フォルダの場合,GUID の文字列となります.これを使用します.
本ページの基本的なコードで使用している SHGetPathFromIDList 関数でも,項目識別子からパス名を取得できますが,仮想フォルダは取得できません.



図7
実行例
  • 以下を配置
    CSIDL を一覧表示する ListBox1 (TListBox)
    フルパスを表示する Edit1 (TEdit)
    表示名を表示する Edit2 (TEdit)
図8
実行
  • 仮想フォルダは GUID の文字列となる
図9
CSIDL_CONTROLS をエクスプローラで表示

リスト3
SHGetFolderLocation 関数で特殊フォルダのフルパスを取得
unit Unit1;

interface

uses
  Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs,
  StdCtrls, ExtCtrls;

type
  TForm1 = class(TForm)
    ListBox1: TListBox;
    Edit1: TEdit;
    OpenDialog1: TOpenDialog;
    RadioGroup1: TRadioGroup;
    Edit2: TEdit;
    procedure FormCreate(Sender: TObject);
    procedure ListBox1Click(Sender: TObject);
    procedure ListBox1DblClick(Sender: TObject);
    procedure ListBox1KeyDown(Sender: TObject; var Key: Word;
      Shift: TShiftState);
    procedure RadioGroup1Click(Sender: TObject);
  private
    { Private 宣言 }
  public
    { Public 宣言 }
  end;

var
  Form1: TForm1;

implementation

{$R *.DFM}

uses ShlObj, ActiveX, ShellAPI;

//=============================================================================
//  Form生成時の処理
//  ディスクからCSIDL値を格納したファイルを読み込み
//  ListBox1の各ItemにAddObjectでListBox1に表示する文字列と各CSIDL値をセット
//=============================================================================
procedure TForm1.FormCreate(Sender: TObject);
var
  LSL     : TStringList;
  LSLTemp : TStringList;
  i       : Integer;
begin
  LSL     := TStringList.Create;
  LSLTemp := TStringList.Create;
  try
    LSL.LoadFromFile('../CSIDL_List.csv');
    LSL.Sort;
    ListBox1.Items.BeginUpdate;
    for i := 0 to LSL.Count - 1 do begin
      LSLTemp.CommaText := LSL[i];
      ListBox1.Items.AddObject(LSLTemp[0], TObject(StrToInt(LSLTemp[1])));
    end;
    ListBox1.Items.EndUpdate;
  finally
    FreeAndNil(LSL);
    FreeAndNil(LSLTemp);
  end;
end;

//-----------------------------------------------------------------------------
//  相対項目識別子からフォルダまたはファイル名を求める
//  仮想フォルダはGUID文字列として求まる
//
//  第1引数  対象の項目識別子を取得する際に使用したシェルフォルダオブジェクト
//  第2引数  対象の項目識別子.ShFolder階層下の相対値
//  第3引数  フルパスを取得する場合は,SHGDN_FORPARSING
//           ファイル名だけ取得する場合は,SHGDN_NORMALまたはSHGDN_INFOLDER
//-----------------------------------------------------------------------------
function GetDispNameFromPItemID(ShFolder: IShellFolder; ItemID:
  PItemIDList; Flag: DWORD=SHGDN_NORMAL): String;
var
  LStrRet : TStrRet;
begin
  Result := '';
  if ShFolder = nil then exit;
  if ItemID = nil then exit;

  FillChar(LStrRet, SizeOf(LStrRet), #0);
  if NOERROR = ShFolder.GetDisplayNameOf(ItemID, Flag, LStrRet) then begin
    Result := LStrRet.pOleStr;
  end;
end;

//=============================================================================
//  ListBoxの項目をダブルクリックしたらそのCSIDLに相当する項目識別子を取得
//  その項目識別子に相当するフォルダを
//  [ファイルを開く]ダイアログまたはエクスプローラで開く
//=============================================================================
procedure TForm1.ListBox1DblClick(Sender: TObject);
var
  LIndex          : Integer;
  LCSIDL          : Integer;
  LItemID         : PItemIDList;
  LpDesktopFolder : IShellFolder;
  LDisplayName    : String;
  LFolderPath     : String;
  LExeFile        : String;
  LhInst          : Cardinal;
begin
  Edit1.Text := '';
  Edit2.Text := '';
  LItemID     := nil;

  //ListBox1のIndexを取得してそのTObjectで定義した値を取出す
  LIndex := ListBox1.ItemIndex;
  LCSIDL := Integer(ListBox1.Items.Objects[LIndex]);

  //CSIDLに相当する項目識別子を取得
  if SHGetFolderLocation(0, LCSIDL, 0, 0, LItemID) = S_OK then begin

    //[デスクトップ]のシェルフォルダオブジェクトを取得
    SHGetDesktopFolder(LpDesktopFolder);

    //取得した項目識別子から表示名とバス名を取得
    LDisplayName := GetDispNameFromPItemID(LpDesktopFolder, LItemID, SHGDN_INFOLDER);
    LFolderPath  := GetDispNameFromPItemID(LpDesktopFolder, LItemID, SHGDN_FORPARSING);

    Edit1.Text := LFolderPath;
    Edit2.Text := LDisplayName;

    //以下,取得した値の確認用
    if (Sender <> nil) and (LFolderPath <> '') then begin
      if RadioGroup1.ItemIndex = 0 then begin
        OpenDialog1.InitialDir := LFolderPath;
        OpenDialog1.Execute;
        Update;
      end else begin
        //実際には起動アプリが空でもエクスプローラは起動する
        //あるいは,起動ファイルにパス名を指定すれば起動する
        LExeFile := 'explorer.exe';
        LhInst   := ShellExecute(Handle,
                                'open',
                                PChar(LExeFile),
                                PChar(LFolderPath),
                                nil,
                                SW_SHOW);
        if LhInst <= 32 then begin
          MessageBox(Handle, '起動に失敗しました.', '情報', MB_ICONINFORMATION);
        end;
      end;
    end;
  end;

  //項目識別子のメモリアロケートの解放
  if LItemID <> nil then CoTaskMemFree(LItemID);
end;

//=============================================================================
//  ListBox1の項目を選択したら,パス名を取得してEdit1に表示
//=============================================================================
procedure TForm1.ListBox1Click(Sender: TObject);
begin
  ListBox1DblClick(nil);
end;

//=============================================================================
//  ListBox1で[Enter]キーを押したら,ダブルクリックと同じ動作をさせる
//=============================================================================
procedure TForm1.ListBox1KeyDown(Sender: TObject; var Key: Word;
  Shift: TShiftState);
begin
  if Key = VK_RETURN then begin
    ListBox1DblClick(Sender);
  end;
end;

//=============================================================================
//  RaditoGroup1のOnClickイベント処理
//  ListBox1にフォーカス移動して[Enter]キー操作可能にする
//=============================================================================
procedure TForm1.RadioGroup1Click(Sender: TObject);
begin
  ListBox1.SetFocus;
end;

end.




07_SHGetFolderLocation 関数と ShellExecuteEx


SHGetFolderLocation 関数を使用して,特殊フォルダの項目識別子を取得するサンプルです.
ShellExecuteEX 関数は,項目識別子を指定してフォルダをエクスプローラで表示できます.



リスト4
SHGetFolderLocation 関数で特殊フォルダの項目識別子を取得
取得したフォルダを,ShellExecuteEx 関数を使用してエクスプローラで表示する場合
unit Unit1;

interface

uses
  Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs,
  StdCtrls, ExtCtrls;

type
  TForm1 = class(TForm)
    ListBox1: TListBox;
    Edit1: TEdit;
    procedure FormCreate(Sender: TObject);
    procedure ListBox1Click(Sender: TObject);
    procedure ListBox1DblClick(Sender: TObject);
    procedure ListBox1KeyDown(Sender: TObject; var Key: Word;
      Shift: TShiftState);
  private
    { Private 宣言 }
  public
    { Public 宣言 }
  end;

var
  Form1: TForm1;

implementation

{$R *.DFM}

uses ShlObj, ShellAPI, ActiveX;

//=============================================================================
//  Form生成時の処理
//  ディスクからCSIDL値を格納したファイルを読み込み
//  ListBox1の各ItemにAddObjectでListBox1に表示する文字列と各CSIDL値をセット
//=============================================================================
procedure TForm1.FormCreate(Sender: TObject);
var
  SL     : TStringList;
  SLTemp : TStringList;
  i      : Integer;
begin
  SL     := TStringList.Create;
  SLTemp := TStringList.Create;
  try
    SL.LoadFromFile('../CSIDL_List.csv');
    SL.Sort;
    ListBox1.Items.BeginUpdate;
    for i := 0 to SL.Count - 1 do begin
      SLTemp.CommaText := SL[i];
      ListBox1.Items.AddObject(SLTemp[0], TObject(StrToInt(SLTemp[1])));
    end;
    ListBox1.Items.EndUpdate;
  finally
    FreeAndNil(SL);
    FreeAndNil(SLTemp);
  end;
end;

//-----------------------------------------------------------------------------
//  ShellExecuteExで指定ファイルを実行
//
//  SEE_MASK_NOCLOSEPROCESS
//    ShellExcuteEx実行後,プロセスを維持Processにプロセスのハンドルを返す
//  SEE_MASK_FLAG_NO_UI
//    エラー発生時にメッセージBoxを表示しない
//  SEE_MASK_IDLIST
//    項目識別子を使用する
//-----------------------------------------------------------------------------
function RunExeFilePIDL(hWnd: HWND; AFile: String; pidlFile: PItemIDList;
   Verb: String): Cardinal;
var
  AInfo : TShellExecuteInfo;
  ret   : DWORD;
begin
  FillChar(AInfo, SizeOf(AInfo), 0);
  AInfo.cbSize := SizeOf(AInfo);
  AInfo.Wnd    := hWnd;
  AInfo.fMask  := SEE_MASK_NOCLOSEPROCESS or SEE_MASK_FLAG_NO_UI or SEE_MASK_IDLIST;
  AInfo.lpVerb := PChar(Verb);

  AInfo.lpFile       := PChar(AFile);
  AInfo.lpIDList     := pidlFile;
  AInfo.nShow        := SW_SHOW;

  ShellExecuteEx(@AInfo);

  //ShellExecuteExは成功するとhInstAppに33以上の値を返す
  if AInfo.hInstApp >= 33 then
  begin
    repeat
      ret := WaitForInputIdle(AInfo.hProcess, 50);
      Application.ProcessMessages;
    until ret <> WAIT_TIMEOUT;
  end;
  CloseHandle(AInfo.hProcess);

  Result := AInfo.hInstApp;
end;

//=============================================================================
//  ListBoxの項目をダブルクリックしたらそのCSIDLに相当する項目識別子を取得
//  その項目識別子のフォルダをエクスプローラで表示
//=============================================================================
procedure TForm1.ListBox1DblClick(Sender: TObject);
var
  AIndex    : Integer;
  CSIDL     : Integer;
  ItemID    : PItemIDList;
  Buff      : array [0..MAX_PATH] of Char;
  ExeFile   : String;
  iRet      : Cardinal;
begin
  Edit1.Text := '';
  ItemID     := nil;

  //ListBox1のIndexを取得してそのTObjectで定義した値を取出す
  AIndex := ListBox1.ItemIndex;
  CSIDL  := Integer(ListBox1.Items.Objects[AIndex]);

  //CSIDLに相当する項目識別子を取得
  if SHGetFolderLocation(0, CSIDL, 0, 0, ItemID) = S_OK then begin

    //項目識別子からパス名を取得
    //仮想フォルダは取得できない
    FillChar(Buff, SizeOf(Buff), #0);
    SHGetPathFromIDList(ItemID, Buff);
    Edit1.Text := Buff;

    //以下,取得した値の確認用
    //実際には起動アプリが空でもエクスプローラは起動する
    //あるいは,起動ファイルにパス名を指定すれば起動する
    ExeFile := 'explorer.exe';
    if Sender <> nil then begin
      if ItemID <> nil then begin
        iRet := RunExeFilePIDL(0, ExeFile, ItemID, '');
        if iRet <= 32 then begin
          MessageBox(Handle, '起動に失敗しました.', '情報', MB_ICONINFORMATION);
        end;
      end;
    end;
  end;

  //項目識別子のメモリアロケートの解放
  if ItemID <> nil then CoTaskMemFree(ItemID);
end;

//=============================================================================
//  ListBox1の項目を選択したら,パス名を取得してEdit1に表示
//=============================================================================
procedure TForm1.ListBox1Click(Sender: TObject);
begin
  ListBox1DblClick(nil);
end;

//=============================================================================
//  ListBox1で[Enter]キーを押したら,ダブルクリックと同じ動作をさせる
//=============================================================================
procedure TForm1.ListBox1KeyDown(Sender: TObject; var Key: Word;
  Shift: TShiftState);
begin
  if Key = VK_RETURN then begin
    ListBox1DblClick(Sender);
  end;
end;

end.




08_SHGetKnownFolderPath 関数


Windows Vista 以降専用の関数です.
SHGetKnownFolderPath 関数を使用した特殊フォルダのパス名を取得するサンプルです.


図10
実行例
  • 以下を配置
    FOLDERID を一覧表示する ListBox1 (TListBox)
    フルパスを表示する Edit1 (TEdit)
図11
実行
図12
FOLDERID_Documents を表示
  • TOpenDialog で表示した画像

リスト5
SHGetKnownFolderPath 関数で特殊フォルダのフルパスを取得
unit Unit1;

interface

uses
  Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs,
  StdCtrls, ExtCtrls;

type
  TForm1 = class(TForm)
    ListBox1: TListBox;
    Edit1: TEdit;
    OpenDialog1: TOpenDialog;
    RadioGroup1: TRadioGroup;
    procedure FormCreate(Sender: TObject);
    procedure FormDestroy(Sender: TObject);
    procedure ListBox1Click(Sender: TObject);
    procedure ListBox1DblClick(Sender: TObject);
    procedure ListBox1KeyDown(Sender: TObject; var Key: Word;
      Shift: TShiftState);
    procedure RadioGroup1Click(Sender: TObject);
  private
    { Private 宣言 }
    SL : TStringList;
  public
    { Public 宣言 }
  end;

var
  Form1: TForm1;

implementation

{$R *.DFM}

uses ShlObj, ShellAPI;

//=============================================================================
//  Form生成時の処理
//  ディスクからファイルを読み込み
//  ListBox1の各Itemに表示するFOLDERIDの文字列をセット
//  実際に使用するFOLDERIDのTGUIDは文字列としてSL(TStringList)に残す
//=============================================================================
procedure TForm1.FormCreate(Sender: TObject);
var
  SLTemp : TStringList;
  i      : Integer;
begin
  SL     := TStringList.Create;
  SLTemp := TStringList.Create;
  try
    SL.LoadFromFile('../FOLDERID_List.csv');
    SL.Sort;
    for i := 0 to SL.Count - 1 do begin
      SLTemp.CommaText := SL[i];
      ListBox1.Items.Add(Trim(SLTemp[0]));
      SL[i] := Trim(SLTemp[1]);
    end;
  finally
    FreeAndNil(SLTemp);
  end;
end;

//=============================================================================
//  終了時の処理
//=============================================================================
procedure TForm1.FormDestroy(Sender: TObject);
begin
  FreeAndNil(SL);
end;

//=============================================================================
//  ListBoxの項目をダブルクリックしたらそのFOLDERIDに相当するフォルダ名を取得
//  そのフォルダで[ファイル選択]ダイアログまたはエクスプローラで表示
//=============================================================================
procedure TForm1.ListBox1DblClick(Sender: TObject);
var
  AIndex      : Integer;
  FolderID    : TGUID;
  pFolderPath : PChar;
  FolderPath  : String;
  ExeFile     : String;
  hInst       : Cardinal;
begin
  Edit1.Text := '';

  //FOLDERIDのTGUIDの値を取得
  AIndex   := ListBox1.ItemIndex;
  FolderID := StringToGUID(SL[AIndex]);

  //フォルダのパス名を取得
  if SHGetKnownFolderPath(FolderID, 0, 0, pFolderPath) = S_OK then begin

    FolderPath := pFolderPath;
    Edit1.Text := FolderPath;

    //以下,取得した値の確認用
    if (Sender <> nil) and (FolderPath <> '') then begin
      if RadioGroup1.ItemIndex = 0 then begin
        OpenDialog1.InitialDir := FolderPath;
        OpenDialog1.Execute;
        Update;
      end else begin
        //実際には起動アプリが空でもエクスプローラは起動する
        //あるいは,起動ファイルにパス名を指定すれば起動する
        ExeFile := 'explorer.exe';
        hInst   := ShellExecute(Handle,
                                'open',
                                PChar(ExeFile),
                                PChar(FolderPath),
                                nil,
                                SW_SHOW);
        if hInst <= 32 then begin
          MessageBox(Handle, '起動に失敗しました.', '情報', MB_ICONINFORMATION);
        end;
      end;
    end;
  end;
end;

//=============================================================================
//  ListBox1の項目を選択したら,パス名を取得してEdit1に表示
//=============================================================================
procedure TForm1.ListBox1Click(Sender: TObject);
begin
  ListBox1DblClick(nil);
end;

//=============================================================================
//  ListBox1で[Enter]キーを押したら,ダブルクリックと同じ動作をさせる
//=============================================================================
procedure TForm1.ListBox1KeyDown(Sender: TObject; var Key: Word;
  Shift: TShiftState);
begin
  if Key = VK_RETURN then begin
    ListBox1DblClick(Sender);
  end;
end;

//=============================================================================
//  RaditoGroup1のOnClickイベント処理
//  ListBox1にフォーカス移動して[Enter]キー操作可能にする
//=============================================================================
procedure TForm1.RadioGroup1Click(Sender: TObject);
begin
  ListBox1.SetFocus;
end;

end.




09_SHGetKnownFolderIDList 関数


Windows Vista 以降専用の関数です.
SHGetKnownFolderIDList 関数を使用して,特殊フォルダの項目識別子を取得するサンプルです.
項目識別子では,TOpenDialog や ShellExecute 関数でフォルダを表示できません.そこで,[818_シェル名前空間と項目識別子] のサンプルと同じように,デスクトップの IShellFolder を求め,そのメソッド GetDisplayNameOf でフォルダのパス名とエクスプローラで表示される表示名を取得します.

この時取得できるパス名は,仮想フォルダの場合,GUID の文字列となります.これを使用します.
本ページの基本的なコードで使用している SHGetPathFromIDList 関数でも,項目識別子からパス名を取得できますが,仮想フォルダは取得できません.



図13
実行例
  • 以下を配置
    FOLDERID を一覧表示する ListBox1 (TListBox)
    フルパスを表示する Edit1 (TEdit)
    表示名を表示する Edit2 (TEdit)
図14
実行
  • 仮想フォルダは GUID の文字列となる
図15
FOLDERID_Libraries を表示
  • TOpenDialog で表示した画像
    この [ライブラリ] は Windows 7 で追加されたフォルダ

リスト6
SHGetKnownFolderIDList 関数で特殊フォルダのフルパスを取得
unit Unit1;

interface

uses
  Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs,
  StdCtrls, ExtCtrls;

type
  TForm1 = class(TForm)
    ListBox1: TListBox;
    Edit1: TEdit;
    OpenDialog1: TOpenDialog;
    RadioGroup1: TRadioGroup;
    Edit2: TEdit;
    procedure FormCreate(Sender: TObject);
    procedure FormDestroy(Sender: TObject);
    procedure ListBox1Click(Sender: TObject);
    procedure ListBox1DblClick(Sender: TObject);
    procedure ListBox1KeyDown(Sender: TObject; var Key: Word;
      Shift: TShiftState);
    procedure RadioGroup1Click(Sender: TObject);
  private
    { Private 宣言 }
    SL : TStringList;
  public
    { Public 宣言 }
  end;

var
  Form1: TForm1;

implementation

{$R *.DFM}

uses ShlObj, ActiveX, ShellAPI;

//=============================================================================
//  Form生成時の処理
//  ディスクからファイルを読み込み
//  ListBox1の各Itemに表示するFOLDERIDの文字列をセット
//  実際に使用するFOLDERIDのTGUIDは文字列としてSL(TStringList)に残す
//=============================================================================
procedure TForm1.FormCreate(Sender: TObject);
var
  LSLTemp : TStringList;
  i       : Integer;
begin
  SL      := TStringList.Create;
  LSLTemp := TStringList.Create;
  try
    SL.LoadFromFile('../FOLDERID_List.csv');
    SL.Sort;
    for i := 0 to SL.Count - 1 do begin
      LSLTemp.CommaText := SL[i];
      ListBox1.Items.Add(Trim(LSLTemp[0]));
      SL[i] := Trim(LSLTemp[1]);
    end;
  finally
    FreeAndNil(LSLTemp);
  end;
end;

//=============================================================================
//  終了時の処理
//=============================================================================
procedure TForm1.FormDestroy(Sender: TObject);
begin
  FreeAndNil(SL);
end;

//-----------------------------------------------------------------------------
//  相対項目識別子からフォルダまたはファイル名を求める
//  仮想フォルダはGUID文字列として求まる
//
//  第1引数  対象の項目識別子を取得する際に使用したシェルフォルダオブジェクト
//  第2引数  対象の項目識別子.ShFolder階層下の相対値
//  第3引数  フルパスを取得する場合は,SHGDN_FORPARSING
//           ファイル名だけ取得する場合は,SHGDN_NORMALまたはSHGDN_INFOLDER
//-----------------------------------------------------------------------------
function GetDispNameFromPItemID(ShFolder: IShellFolder; ItemID:
  PItemIDList; Flag: DWORD=SHGDN_NORMAL): String;
var
  LStrRet : TStrRet;
begin
  Result := '';
  if ShFolder = nil then exit;
  if ItemID = nil then exit;

  FillChar(LStrRet, SizeOf(LStrRet), #0);
  if NOERROR = ShFolder.GetDisplayNameOf(ItemID, Flag, LStrRet) then begin
    Result := LStrRet.pOleStr;
  end;
end;

//=============================================================================
//  ListBoxの項目をダブルクリックしたらそのFOLDERIDに相当する項目識別子を取得
//  その項目識別子に相当するフォルダで
//  [ファイル選択]ダイアログまたはエクスプローラを開く
//=============================================================================
procedure TForm1.ListBox1DblClick(Sender: TObject);
var
  LIndex          : Integer;
  LFolderID       : TGUID;
  LItemID         : PItemIDList;
  LpDesktopFolder : IShellFolder;
  LDisplayName    : String;
  LFolderPath     : String;
  LExeFile        : String;
  LhInst          : Cardinal;
begin
  Edit1.Text := '';
  Edit2.Text := '';
  LItemID     := nil;

  //FOLDERIDのTGUIDの値を取得
  LIndex    := ListBox1.ItemIndex;
  LFolderID := StringToGUID(SL[LIndex]);

  //FOLDERIDのTGUIDに相当する項目識別子を取得
  if SHGetKnownFolderIDList(LFolderID, 0, 0, LItemID) = S_OK then begin

    //[デスクトップ]のシェルフォルダオブジェクトを取得
    SHGetDesktopFolder(LpDesktopFolder);

    //取得した項目識別子から表示名とバス名を取得
    LDisplayName := GetDispNameFromPItemID(LpDesktopFolder, LItemID, SHGDN_INFOLDER);
    LFolderPath  := GetDispNameFromPItemID(LpDesktopFolder, LItemID, SHGDN_FORPARSING);

    Edit1.Text := LFolderPath;
    Edit2.Text := LDisplayName;

    //以下,取得した値の確認用
    if (Sender <> nil) and (LFolderPath <> '') then begin
      if RadioGroup1.ItemIndex = 0 then begin
        OpenDialog1.InitialDir := LFolderPath;
        OpenDialog1.Execute;
        Update;
      end else begin
        //実際には起動アプリが空でもエクスプローラは起動する
        //あるいは,起動ファイルにパス名を指定すれば起動する
        LExeFile := 'explorer.exe';
        LhInst   := ShellExecute(Handle,
                                'open',
                                PChar(LExeFile),
                                PChar(LFolderPath),
                                nil,
                                SW_SHOW);

        if LhInst <= 32 then begin
          MessageBox(Handle, '起動に失敗しました.', '情報', MB_ICONINFORMATION);
        end;
      end;
    end;
  end;

  //項目識別子のメモリアロケートの解放
  if LItemID <> nil then CoTaskMemFree(LItemID);
end;

//=============================================================================
//  ListBox1の項目を選択したら,パス名を取得してEdit1に表示
//=============================================================================
procedure TForm1.ListBox1Click(Sender: TObject);
begin
  ListBox1DblClick(nil);
end;

//=============================================================================
//  ListBox1で[Enter]キーを押したら,ダブルクリックと同じ動作をさせる
//=============================================================================
procedure TForm1.ListBox1KeyDown(Sender: TObject; var Key: Word;
  Shift: TShiftState);
begin
  if Key = VK_RETURN then begin
    ListBox1DblClick(Sender);
  end;
end;

//=============================================================================
//  RaditoGroup1のOnClickイベント処理
//  ListBox1にフォーカス移動して[Enter]キー操作可能にする
//=============================================================================
procedure TForm1.RadioGroup1Click(Sender: TObject);
begin
  ListBox1.SetFocus;
end;

end.




10_SHGetKnownFolderIDList 関数と ShellExecuteEx


SHGetKnownFolderIDList 関数を使用して,特殊フォルダの項目識別子を取得するサンプルです. ShellExecuteEX 関数は,項目識別子を指定してフォルダをエクスプローラで表示できます.
この関数は,Windows Vista 以降で使用可能な関数です.


リスト7
SHGetKnownFolderIDList 関数で特殊フォルダの項目識別子を取得
取得したフォルダを,ShellExecuteEx 関数を使用してエクスプローラで表示する場合
unit Unit1;

interface

uses
  Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs,
  StdCtrls, ExtCtrls;

type
  TForm1 = class(TForm)
    ListBox1: TListBox;
    Edit1: TEdit;
    procedure FormCreate(Sender: TObject);
    procedure FormDestroy(Sender: TObject);
    procedure ListBox1Click(Sender: TObject);
    procedure ListBox1DblClick(Sender: TObject);
    procedure ListBox1KeyDown(Sender: TObject; var Key: Word;
      Shift: TShiftState);
  private
    { Private 宣言 }
    SL : TStringList;
  public
    { Public 宣言 }
  end;

var
  Form1: TForm1;

implementation

{$R *.DFM}

uses ShlObj, ShellAPI, ActiveX;

//=============================================================================
//  Form生成時の処理
//  ディスクからファイルを読み込み
//  ListBox1の各Itemに表示するFOLDERIDの文字列をセット
//  実際に使用するFOLDERIDのTGUIDは文字列としてSL(TStringList)に残す
//=============================================================================
procedure TForm1.FormCreate(Sender: TObject);
var
  SLTemp : TStringList;
  i      : Integer;
begin
  SL     := TStringList.Create;
  SLTemp := TStringList.Create;
  try
    SL.LoadFromFile('../FOLDERID_List.csv');
    SL.Sort;
    for i := 0 to SL.Count - 1 do begin
      SLTemp.CommaText := SL[i];
      ListBox1.Items.Add(Trim(SLTemp[0]));
      SL[i] := Trim(SLTemp[1]);
    end;
  finally
    FreeAndNil(SLTemp);
  end;
end;

//=============================================================================
//  終了時の処理
//=============================================================================
procedure TForm1.FormDestroy(Sender: TObject);
begin
  FreeAndNil(SL);
end;

//-----------------------------------------------------------------------------
//  ShellExecuteExで指定ファイルを実行
//
//  SEE_MASK_NOCLOSEPROCESS
//    ShellExcuteEx実行後,プロセスを維持Processにプロセスのハンドルを返す
//  SEE_MASK_FLAG_NO_UI
//    エラー発生時にメッセージBoxを表示しない
//  SEE_MASK_IDLIST
//    項目識別子を使用する
//-----------------------------------------------------------------------------
function RunExeFilePIDL(hWnd: HWND; AFile: String; pidlFile: PItemIDList;
   Verb: String): Cardinal;
var
  AInfo : TShellExecuteInfo;
  ret   : DWORD;
begin
  FillChar(AInfo, SizeOf(AInfo), 0);
  AInfo.cbSize := SizeOf(AInfo);
  AInfo.Wnd    := hWnd;
  AInfo.fMask  := SEE_MASK_NOCLOSEPROCESS or SEE_MASK_FLAG_NO_UI or SEE_MASK_IDLIST;
  AInfo.lpVerb := PChar(Verb);

  AInfo.lpFile       := PChar(AFile);
  AInfo.lpIDList     := pidlFile;
  AInfo.nShow        := SW_SHOW;

  ShellExecuteEx(@AInfo);

  //ShellExecuteExは成功するとhInstAppに33以上の値を返す
  if AInfo.hInstApp >= 33 then
  begin
    repeat
      ret := WaitForInputIdle(AInfo.hProcess, 50);
      Application.ProcessMessages;
    until ret <> WAIT_TIMEOUT;
  end;
  CloseHandle(AInfo.hProcess);

  Result := AInfo.hInstApp;
end;

//=============================================================================
//  ListBoxの項目をダブルクリックしたらそのFOLDERIDに相当する項目識別子を取得
//  その項目識別子に相当するフォルダをエクスプローラで開く
//=============================================================================
procedure TForm1.ListBox1DblClick(Sender: TObject);
var
  AIndex    : Integer;
  FolderID  : TGUID;
  ItemID    : PItemIDList;
  Buff      : array [0..MAX_PATH] of Char;
  ExeFile   : String;
  iRet      : Cardinal;
begin
  Edit1.Text := '';
  ItemID     := nil;

  //FOLDERIDのTGUIDの値を取得
  AIndex   := ListBox1.ItemIndex;
  FolderID := StringToGUID(SL[AIndex]);

  //FOLDERIDのTGUIDに相当する項目識別子を取得
  if SHGetKnownFolderIDList(FolderID, 0, 0, ItemID) = S_OK then begin

    //項目識別子からパス名を取得
    //仮想フォルダは取得できない
    FillChar(Buff, SizeOf(Buff), #0);
    SHGetPathFromIDList(ItemID, Buff);
    Edit1.Text := Buff;

    //以下,取得した値の確認用
    //実際には起動アプリが空でもエクスプローラは起動する
    //あるいは,起動ファイルにパス名を指定すれば起動する
    ExeFile := 'explorer.exe';
    if Sender <> nil then begin
      if ItemID <> nil then begin
        iRet := RunExeFilePIDL(0, ExeFile, ItemID, '');
        if iRet <= 32 then begin
          MessageBox(Handle, '起動に失敗しました.', '情報', MB_ICONINFORMATION);
        end;
      end;
    end;
  end;

  //項目識別子のメモリアロケートの解放
  if ItemID <> nil then CoTaskMemFree(ItemID);
end;

//=============================================================================
//  ListBox1の項目を選択したら,パス名を取得してEdit1に表示
//=============================================================================
procedure TForm1.ListBox1Click(Sender: TObject);
begin
  ListBox1DblClick(nil);
end;

//=============================================================================
//  ListBox1で[Enter]キーを押したら,ダブルクリックと同じ動作をさせる
//=============================================================================
procedure TForm1.ListBox1KeyDown(Sender: TObject; var Key: Word;
  Shift: TShiftState);
begin
  if Key = VK_RETURN then begin
    ListBox1DblClick(Sender);
  end;
end;

end.




11_SHGetKnownFolderItem 関数


SHGetKnownFolderItem 関数を使用して,特殊フォルダのパス名を取得するサンプルです.
この関数は,IShellItem オブジェクトを取得します.
サンプルでは,このIShellItem の GetDisplayName メソッドで,フルパス名とエクスプローラで表示する名前を取得しています.項目識別子からパス名を取得するよりもコードが簡単になるのですが,この関数は Windows 7 以降でないと使用できません.



図16
実行例
  • 以下を配置
    FOLDERID を一覧表示する ListBox1 (TListBox)
    フルパスを表示する Edit1 (TEdit)
    表示名を表示する Edit2 (TEdit)
図17
実行
  • 仮想フォルダは GUID の文字列となる
図18
FOLDERID_ComputerFolder を表示
  • TOpenDialog で表示した画像

リスト8
SHGetKnownFolderItem 関数で特殊フォルダのフルパスを取得
unit Unit1;

interface

uses
  Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs,
  StdCtrls, ExtCtrls;

type
  TForm1 = class(TForm)
    ListBox1: TListBox;
    Edit1: TEdit;
    RadioGroup1: TRadioGroup;
    OpenDialog1: TOpenDialog;
    Edit2: TEdit;
    procedure FormCreate(Sender: TObject);
    procedure ListBox1DblClick(Sender: TObject);
    procedure FormDestroy(Sender: TObject);
    procedure ListBox1Click(Sender: TObject);
    procedure ListBox1KeyDown(Sender: TObject; var Key: Word;
      Shift: TShiftState);
    procedure RadioGroup1Click(Sender: TObject);
  private
    { Private 宣言 }
    SL : TStringList;
  public
    { Public 宣言 }
  end;

var
  Form1: TForm1;

implementation

{$R *.DFM}

uses ShlObj, ShellAPI, ActiveX;

//=============================================================================
//  Form生成時の処理
//  ディスクからファイルを読み込み
//  ListBox1の各Itemに表示するFOLDERIDの文字列をセット
//  実際に使用するFOLDERIDのTGUIDは文字列としてSL(TStringList)に残す
//=============================================================================
procedure TForm1.FormCreate(Sender: TObject);
var
  LSLTemp : TStringList;
  i       : Integer;
begin
  SL     := TStringList.Create;
  LSLTemp := TStringList.Create;
  try
    SL.LoadFromFile('../FOLDERID_List.csv');
    SL.Sort;
    for i := 0 to SL.Count - 1 do begin
      LSLTemp.CommaText := SL[i];
      ListBox1.Items.Add(Trim(LSLTemp[0]));
      SL[i] := Trim(LSLTemp[1]);
    end;
  finally
    FreeAndNil(LSLTemp);
  end;
end;

//=============================================================================
//  終了時の処理
//=============================================================================
procedure TForm1.FormDestroy(Sender: TObject);
begin
  FreeAndNil(SL);
end;

//=============================================================================
//  ダブルクリックしたらFOLDERIDに相当するIShellItemオブジェクトを取得
//  IShellItemのGetDisplayNameメソッドでフォルダのパス名と表示名を取得
//  そのフォルダを[ファイルを開く]ダイアログまはたエクスプローラで表示
//=============================================================================
procedure TForm1.ListBox1DblClick(Sender: TObject);
var
  LIndex        : Integer;
  LFolderID     : TGUID;
  LpItem        : IShellItem;
  LiRet         : HRESULT;
  LpFilePath    : PChar;
  LpDisplayName : PChar;
  LExeFile      : String;
  LhInst        : Cardinal;
begin
  Edit1.Text    := '';
  Edit2.Text    := '';
  LpFilePath    := nil;
  LpDisplayName := nil;

  //ListBox1のIndexを取得してそのTObjectで定義した値を取出す
  LIndex    := ListBox1.ItemIndex;
  LFolderID := StringToGUID(SL[LIndex]);

  //FOLDERIDに相当するIShellItemオブジェクトを取得
  LiRet := SHGetKnownFolderItem(LFolderID, 0, 0, IID_IShellItem, Pointer(LpItem));

  if LiRet = S_OK then begin
    //GetDisplayNameメソッドでパス名と表示名を取得
    //SIGDN_FILESYSPATHにすると仮想フォルダ等のGUID文字列は取得できない
    LpItem.GetDisplayName(SIGDN_DESKTOPABSOLUTEPARSING, LpFilePath);
    LpItem.GetDisplayName(SIGDN_NORMALDISPLAY,          LpDisplayName);

    Edit1.Text := LpFilePath;
    Edit2.Text := LpDisplayName;

    //以下,取得した値の確認用
    if (Sender <> nil) and (LpFilePath <> nil) then begin
      if RadioGroup1.ItemIndex = 0 then begin
        OpenDialog1.InitialDir := LpFilePath;
        OpenDialog1.Execute;
        Update;
      end else begin
        //実際には起動アプリが空でもエクスプローラは起動する
        //あるいは,起動ファイルにパス名を指定すれば起動する
        LExeFile := 'explorer.exe';
        LhInst   := ShellExecute(Handle,
                                'open',
                                PChar(LExeFile),
                                LpFilePath,
                                nil,
                                SW_SHOW);
        if LhInst <= 32 then begin
          MessageBox(Handle, '起動に失敗しました.', '情報', MB_ICONINFORMATION);
        end;
      end;
    end;
  end;

  if LpFilePath    <> nil then CoTaskMemFree(LpFilePath);
  if LpDisplayName <> nil then CoTaskMemFree(LpDisplayName);
end;

//=============================================================================
//  ListBox1の項目を選択したら,パス名を取得してEdit1に表示
//=============================================================================
procedure TForm1.ListBox1Click(Sender: TObject);
begin
  ListBox1DblClick(nil);
end;

//=============================================================================
//  ListBox1で[Enter]キーを押したら,ダブルクリックと同じ動作をさせる
//=============================================================================
procedure TForm1.ListBox1KeyDown(Sender: TObject; var Key: Word;
  Shift: TShiftState);
begin
  if Key = VK_RETURN then begin
    ListBox1DblClick(Sender);
  end;
end;

//=============================================================================
//  RaditoGroup1のOnClickイベント処理
//  ListBox1にフォーカス移動して[Enter]キー操作可能にする
//=============================================================================
procedure TForm1.RadioGroup1Click(Sender: TObject);
begin
  ListBox1.SetFocus;
end;

end.


Windows Vista であれば,IKnownFolderManager インターフェイスから IShellItem オブジェクトを取得できます.
まず,IKnownFolderManager インターフェイスのインスタンスを生成します.その GetFolder メソッドで IKnownFolde オブジェクトを取得します.この IKnownFolde の GetShellItem で IShellItem のオブジェクトが取得できます.後は上のサンプルと同じです.


リスト9
IKnownFolderManager インターフェイスから IShellItem オブジェクトを取得する方法
procedure TForm1.ListBox1DblClick(Sender: TObject);
var
  LIndex        : Integer;
  LFolderID     : TGUID;
  LpKonw        : IKnownFolderManager;
  LpKonwFolder  : IKnownFolder;
  LpItem        : IShellItem;
  LpFilePath    : PChar;
  LpDisplayName : PChar;
begin
  Edit1.Text    := '';
  Edit2.Text    := '';
  LpFilePath    := nil;
  LpDisplayName := nil;

  LIndex    := ListBox1.ItemIndex;
  LFolderID := StringToGUID(SL[LIndex]);

  if Failed(CoCreateInstance(CLSID_KnownFolderManager,
                             nil,
                             CLSCTX_INPROC_SERVER,
                             IKnownFolderManager,
                             LpKonw)) then exit;


  if LpKonw.GetFolder(LFolderID, LpKonwFolder) = S_OK then begin
    if LpKonwFolder.GetShellItem(0, IID_IShellItem, LpItem) = S_OK then begin
      LpItem.GetDisplayName(SIGDN_DESKTOPABSOLUTEEDITING, LpFilePath);
      LpItem.GetDisplayName(SIGDN_NORMALDISPLAY, LpDisplayName);
      Edit1.Text := LpFilePath;
      Edit2.Text := LpDisplayName;
    end;
  end;

  if LpFilePath    <> nil then CoTaskMemFree(LpFilePath);
  if LpDisplayName <> nil then CoTaskMemFree(LpDisplayName);
end;




12_特定フォルダ専用の関数と GetHomePath 関数


Windows API の中には,下表のような,特定のフォルダのパス名を取得する関数があります.
Delphi には GetHomePath という,CSIDL_APPDATA のパス名を取得する専用の関数があります.


関数名 説明
GetSystemDirectory システムディレクトリのパスを取得
互換用.ShGetFolderPath 参照
GetSystemWindowsDirectory マルチユーザーシステムの Windows ディレクトリのパスを取得
Delphi XE3 までには実装されていない
GetSystemWow64Directory WOW64 のシステムディレクトリのパスを取得
32 ビット版 Windows には存在しないディレクトリ
Delphi XE3 までには実装されていない
GetWindowsDirectory Windows ディレクトリのパスを取得
互換用.ShGetFolderPath 参照
GetCurrentDirectory 現在のプロセスのカレントディレクトリを取得
GetHomePath Delphi XE 以降で使用可能
CSIDL_APPDATA の値を取得


図19
実行例

リスト10
特定フォルダ専用の関数と GetHomePath 関数
unit Unit1;

interface

uses
  Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs,
  StdCtrls;

type
  TForm1 = class(TForm)
    Edit1: TEdit;
    Button1: TButton;
    Button2: TButton;
    Button3: TButton;
    Button4: TButton;
    Button5: TButton;
    procedure Button1Click(Sender: TObject);
    procedure Button2Click(Sender: TObject);
    procedure Button3Click(Sender: TObject);
    procedure Button4Click(Sender: TObject);
    procedure Button5Click(Sender: TObject);
  private
    { Private 宣言 }
  public
    { Public 宣言 }
  end;

var
  Form1: TForm1;

implementation

{$R *.DFM}

//=============================================================================
//  GetSystemDirectory関数
//=============================================================================
procedure TForm1.Button1Click(Sender: TObject);
var
  Buff  : array[0..MAX_PATH-1] of Char;
  ASize : Cardinal;
begin
  Edit1.Text := '';

  FillChar(Buff, SizeOf(Buff), #0);
  ASize := Windows.GetSystemDirectory(@Buff, SizeOf(Buff));
  if ASize > 0 then begin
    Edit1.Text := Buff;
  end;
end;

//=============================================================================
//  GetSystemDirectory関数
//  String型で取得する場合
//  Windows APIの関数の中には必要なバッファサイズを取得できるものもある
//=============================================================================
procedure TForm1.Button2Click(Sender: TObject);
var
  PathName : String;
  ASize    : Cardinal;
begin
  Edit1.Text := '';

  //必要なバッファサイズを取得
  ASize := GetSystemDirectory(PChar(PathName), 0);
  if ASize > 0 then begin
    //受取るバッファサイズを設定
    SetLength(PathName, ASize);

    Windows.GetSystemDirectory(PChar(PathName), ASize);
    Edit1.Text := PathName;
  end;
end;

//=============================================================================
//  GetWindowsDirectory関数
//=============================================================================
procedure TForm1.Button3Click(Sender: TObject);
var
  Buff  : array[0..MAX_PATH-1] of Char;
  ASize : Cardinal;
begin
  Edit1.Text := '';

  FillChar(Buff, SizeOf(Buff), #0);
  ASize := Windows.GetWindowsDirectory(@Buff, SizeOf(Buff));
  if ASize > 0 then begin
    Edit1.Text := Buff;
  end;
end;

//=============================================================================
//  GetCurrentDirectory関数
//=============================================================================
procedure TForm1.Button4Click(Sender: TObject);
var
  Buff  : array[0..MAX_PATH-1] of Char;
  ASize : Cardinal;
begin
  Edit1.Text := '';

  FillChar(Buff, SizeOf(Buff), #0);
  ASize := Windows.GetCurrentDirectory(SizeOf(Buff), @Buff);
  if ASize > 0 then begin
    Edit1.Text := Buff;
  end;
end;

//=============================================================================
//  GetHomePath関数
//=============================================================================
procedure TForm1.Button5Click(Sender: TObject);
begin
  Edit1.Text := SysUtils.GetHomePath;
end;

end.




13_GetEnvironmentVariable 関数


システムの環境変数には特殊フォルダの変数があります.
環境変数で定義されている値は,GetEnvironmentVariable 関数を使用すると取得できます.その確認用のサンプルです.

本サンプルは,GetEnvironmentStrings 関数を使用して,現在のプロセスで使用可能な環境変数の全て取得してリストしています.環境変数には特殊フォルダ以外の変数もあります.




図20
実行例
  • Windows 7 であるが [OS] は Windows NT となる
  • 環境変数には特殊フォルダ以外も含まれているので注意

リスト11
GetEnvironmentVariable 関数を使用して,環境変数から特殊フォルダのフルパスを取得
unit Unit1;

interface

uses
  Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs,
  StdCtrls;

type
  TForm1 = class(TForm)
    Edit1: TEdit;
    ListBox1: TListBox;
    procedure FormShow(Sender: TObject);
    procedure ListBox1Click(Sender: TObject);
  private
    { Private 宣言 }
  public
    { Public 宣言 }
  end;

var
  Form1: TForm1;

implementation

uses
  StrUtils;

{$R *.DFM}

//=============================================================================
//  フォーム表示開始時の処理
//  環境変数のリストを作成
//=============================================================================
procedure TForm1.FormShow(Sender: TObject);
var
  LEnvList   : TStringList;
  LpEnvVars  : PChar;
  LpEnvEntry : PChar;
  LEnvString : string;
  LPos       : Integer;
  i          : Integer;
begin
  ListBox1.Items.Clear;

  LEnvList := TStringList.Create;
  try
    //環境変数の値のリストを取得
    LpEnvVars := GetEnvironmentStrings;

    if LpEnvVars <> nil then begin
      //取得したPChar型の文字列は#0で区切られている.終端は#0#0
      //これを順番に取り出す
      //先頭のポインタを退避
      LpEnvEntry := LpEnvVars;
      try
        while LpEnvEntry^ <> #0 do begin
          LEnvList.Add(LpEnvEntry);
          Inc(LpEnvEntry, StrLen(LpEnvEntry) + 1);
        end;
      finally
        //メモリブロックを破棄
        Windows.FreeEnvironmentStrings(LpEnvVars);
      end;
    end;

    //TListBoxに結果を追加していく
    for i := 0 to LEnvList.Count - 1 do begin
      LEnvString := Trim(LEnvList[i]);
      LPos := Pos('=', LEnvString);
      LEnvString := StrUtils.AnsiLeftStr(LEnvString, LPos - 1);
      //空だけにしないとIDEからの実行時に自分自身が含まれる
      if LEnvString <> '' then begin
        ListBox1.Items.Add(LEnvString);
      end;
    end;
  finally
    FreeAndNil(LEnvList);
  end;
end;

//=============================================================================
//  ListBox1のOnClickイベント処理
//=============================================================================
procedure TForm1.ListBox1Click(Sender: TObject);
var
  AIndex     : Integer;
  EnviroPath : String;
begin
  Edit1.Text := '';
  AIndex     := ListBox1.ItemIndex;
  if AIndex < 0 then exit;

  EnviroPath := SysUtils.GetEnvironmentVariable(Trim(ListBox1.Items[AIndex]));
  Edit1.Text := EnviroPath;
end;

end.




14_TPath の特殊フォルダのパス名取得関数 - Delphi XE5 以降


Delphi XE5 の TPath は特殊フォルダのパス名取得関数類が追加されています.
これらの関数は,Windows, OS X, iOS, Android で使用可能なマルチプラットフォーム対応の関数類となっています.
ここまでのサンプルではファイル選択ダイアログやエクスプローラでフォルダを表示していましたが,ここでは Delphi 付属のシェルコンポーネントを使用してみました.特殊フォルダをルートフォルダとしてツリー表示します.



図21
設計時画面
  • Delphi に付属のシェルコンポを使用
図22
実行例
  • GetSharedDocumentsPath の選択結果

リスト12
TPath の特殊フォルダのパス名取得関数 - Delphi XE5 以降
unit Unit1;

interface

uses
  Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs,
  StdCtrls, ExtCtrls, ComCtrls, ShellCtrls;

type
  TForm1 = class(TForm)
    Edit1: TEdit;
    Panel1: TPanel;
    Splitter1: TSplitter;
    ListBox1: TListBox;
    ShellTreeView1: TShellTreeView;
    procedure FormShow(Sender: TObject);
    procedure ListBox1Click(Sender: TObject);
  private
    { Private 宣言 }
  public
    { Public 宣言 }
  end;

var
  Form1: TForm1;

implementation

uses IOUtils;

{$R *.DFM}

//=============================================================================
//  フォーム表示開始時の処理
//=============================================================================
procedure TForm1.FormShow(Sender: TObject);
begin
  ListBox1.Items.Clear;
  ListBox1.Items.Add('TPath.GetHomePath');
  ListBox1.Items.Add('TPath.GetTempPath');
  ListBox1.Items.Add('TPath.GetLibraryPath');
  ListBox1.Items.Add('TPath.GetDocumentsPath');
  ListBox1.Items.Add('TPath.GetSharedDocumentsPath');
  ListBox1.Items.Add('TPath.GetCachePath');
  ListBox1.Items.Add('TPath.GetPicturesPath');
  ListBox1.Items.Add('TPath.GetSharedPicturesPath');
  ListBox1.Items.Add('TPath.GetPublicPath');
  ListBox1.Items.Add('TPath.GetCameraPath');
  ListBox1.Items.Add('TPath.GetSharedCameraPath');
  ListBox1.Items.Add('TPath.GetMusicPath');
  ListBox1.Items.Add('TPath.GetSharedMusicPath');
  ListBox1.Items.Add('TPath.GetMoviesPath');
  ListBox1.Items.Add('TPath.GetSharedMoviesPath');
  ListBox1.Items.Add('TPath.GetAlarmsPath');
  ListBox1.Items.Add('TPath.GetSharedAlarmsPath');
  ListBox1.Items.Add('TPath.GetRingtonesPath ');
  ListBox1.Items.Add('TPath.GetSharedRingTonesPath');
  ListBox1.Items.Add('TPath.GetDownloadsPath');
  ListBox1.Items.Add('TPath.GetSharedDownloadsPath');
end;

//=============================================================================
//  ListBox1のOnClickイベント処理
//=============================================================================
procedure TForm1.ListBox1Click(Sender: TObject);
var
  AIndex     : Integer;
  FolderPath : String;
begin
  Edit1.Text := '';
  AIndex     := ListBox1.ItemIndex;
  if AIndex < 0 then exit;

  if AIndex =  0 then FolderPath := TPath.GetHomePath;
  if AIndex =  1 then FolderPath := TPath.GetTempPath;
  if AIndex =  2 then FolderPath := TPath.GetLibraryPath;
  if AIndex =  3 then FolderPath := TPath.GetDocumentsPath;
  if AIndex =  4 then FolderPath := TPath.GetSharedDocumentsPath;
  if AIndex =  5 then FolderPath := TPath.GetCachePath;
  if AIndex =  6 then FolderPath := TPath.GetPicturesPath;
  if AIndex =  7 then FolderPath := TPath.GetSharedPicturesPath;
  if AIndex =  8 then FolderPath := TPath.GetPublicPath;
  if AIndex =  9 then FolderPath := TPath.GetCameraPath;
  if AIndex = 10 then FolderPath := TPath.GetSharedCameraPath;
  if AIndex = 11 then FolderPath := TPath.GetMusicPath;
  if AIndex = 12 then FolderPath := TPath.GetSharedMusicPath;
  if AIndex = 13 then FolderPath := TPath.GetMoviesPath;
  if AIndex = 14 then FolderPath := TPath.GetSharedMoviesPath;
  if AIndex = 15 then FolderPath := TPath.GetAlarmsPath;
  if AIndex = 16 then FolderPath := TPath.GetSharedAlarmsPath;
  if AIndex = 17 then FolderPath := TPath.GetRingtonesPath;
  if AIndex = 18 then FolderPath := TPath.GetSharedRingTonesPath;
  if AIndex = 19 then FolderPath := TPath.GetDownloadsPath;
  if AIndex = 20 then FolderPath := TPath.GetSharedDownloadsPath;

  Edit1.Text := FolderPath;
  ShellTreeView1.Root := FolderPath;
end;

end.