Delphi Programming / Object Pascal
サイトのトップページへ リンクのページへ ロゴマーク
[掲載 2005年05月11日] [更新 2021年10月12日] Delphi サンプルプログラム集
320_起動中のアプリケーション一覧をリスト
動作確認等 Windows 7 U64(SP1) + Delphi XE5(UP2) Pro VCL-32
後半は Windows 10 ビルド 19042 + Delphi XE5(UP2) Pro VCL-32
320_AppList.zip [8.9 MB] 2021年10月12日版 (EXE 同梱)







起動中のアプリケーションのリスト


起動中のアプリケーションのリストを取得するサンプルです.
主にタスクマネージャーの [アプリケーション] タブと同じ内容を表示します.起動中のアプリケーションの一覧を取得するコードは,Halbow 資料館の「第6章 ウィンドの階層について」に詳しい説明があります.本ページの Windows 7 までのサンプルは,その記事の内容の一部となっています.
記事の後半は,Windows 10 の UWP (Universal Windows Platform) アプリに対応したサンプルです.


[備考 1]
バックグラウンドで実行されているアプリがあります.Windows 10 には待機中のアプリがあります.これは,主に過去に利用したことがあるアプリです.これらのアプリのウィンドウは,デスクトップのウィンドウとしては表示されません.タスクバーにもアイコンとしての表示はありません.
どちらも実際には「起動中のアプリ」です.本ページでは,デスクトップ画面にウィンドウとして表示されているか,最小化してタスクバーにアイコンが表示されているアプリのウィンドウを「起動中」としています.

[備考 2]
起動中のアプリケーションの内,自分自身の EXE 名は Application.ExeName で取得できます.
パスだけや,パス名を除いた EXE 名も SysUtils ユニットに実装されている関数を使用して取得できます.また,TPath にも同様のメソッド類が実装されています.




01_ウィンドウハンドルからアイコンを取得


Windows 7 では,これまで使用していた GetClassLong 関数では,アイコンが取得できないアプリがあります.そこで,2013年07月12日の更新で,アイコンを取得するコードを,WM_GETICON メッセード使用する方法に変更しました.これを次のように関数としています.
Windws 10 の UWP アプリのアイコンは,どちらの方法でも取得できません.


//-----------------------------------------------------------------------------
//  ウィンドウハンドルからアイコンを取得する関数
//-----------------------------------------------------------------------------
function GetIconHandleFromWindow(hWindow: HWND): HICON;
begin
  Result := SendMessage(hWindow, WM_GETICON, ICON_SMALL2, 0);
  if Result = 0 then begin
    Result := SendMessage(hWindow, WM_GETICON, ICON_SMALL, 0);

    if Result = 0 then begin
      Result := GetClassLong(hWindow, GCL_HICONSM);
    end;
  end;
end;    




02_ウィンドウのタイトルとウィンドウのクラス名の取得


本ページのサンプルでは,ウィンドウのタイトルを取得と,ウィンドウのタイトルを取得するコードを,以下のように関数にして使用しています.この場合,FillChar の第 2 引数の値は文字数ではありません.バイト数であることに注意します.Length(LBuff) * SizeOf(Char) としても同じです.
Windows API の GetWindowText 関数と GetClassName 関数のの第 3 引数は,Unicode 版では,取得する文字数の最大値です.


//-----------------------------------------------------------------------------
//  ウィンドウのタイトルを取得する関数
//-----------------------------------------------------------------------------
function GetWindowTitle(hWindow: HWND): string;
var
  LBuff : array [0..MAX_PATH - 1] of Char;
begin
  Result := '';
  FillChar(LBuff, SizeOf(LBuff), #0);
  if (GetWindowText(hWindow, @LBuff, MAX_PATH) > 0) then begin
    Result := LBuff;
  end;
end;


//-----------------------------------------------------------------------------
//  ウィンドウのクラス名を取得する関数
//-----------------------------------------------------------------------------
function GetWindowClassName(hWindow: HWND): string;
var
  LBuff : array [0..MAX_PATH - 1] of Char;
begin
  Result := '';
  FillChar(LBuff, SizeOf(LBuff), #0);
  if (GetClassName(hWindow, @LBuff, MAX_PATH) > 0) then begin
    Result := LBuff;
  end;
end;




03_起動中のアプリケーションのリスト ( 1 ) ウィンドウのタイトル


起動中のアプリケーション名の一覧を取得するサンプルプログラムです.
EnumWindows のコールバック関数内で,ウィンドウのタイトルを取得して,TListView に表示します.


図1
設計時画面
図2
実行画面

リスト1
起動中のアプリケーションをリストする
unit AppList1Unit;

interface

uses
  Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes,
  Vcl.Graphics,Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.StdCtrls, Vcl.ExtCtrls,
  Vcl.ImgList, Vcl.ComCtrls;

type
  TForm1 = class(TForm)
    Button1: TButton;
    ListView1: TListView;
    ImageList1: TImageList;
    procedure Button1Click(Sender: TObject);
    procedure FormCreate(Sender: TObject);
    procedure ListView1Resize(Sender: TObject);
  private
    { Private 宣言 }
  public
    { Public 宣言 }
  end;

  function EnumWindowProc(hWindow: HWND; lPara: TListView): Boolean; stdcall;
  function GetIconHandleFromWindow(hWindow: HWND): HICON;
  function GetWindowTitle(hWindow: HWND): string;

var
  Form1: TForm1;

implementation

{$R *.DFM}

//=============================================================================
//  フォーム生成時の処理
//  一覧表示用の TListView の設定
//=============================================================================
procedure TForm1.FormCreate(Sender: TObject);
begin
  ImageList1.BkColor    := ListView1.Color;
  ListView1.ViewStyle   := vsReport;
  ListView1.SmallImages := ImageList1;

  ListView1.Columns.Add;
  ListView1.Columns[0].Caption :=' アプリケーション';
  ListView1.Columns[0].Width   := ListView1.ClientWidth;
end;
//=============================================================================
//  ウィンドウのリストを作成
//=============================================================================
procedure TForm1.Button1Click(Sender: TObject);
begin
  ListView1.Items.Clear;
  ListView1.Items.BeginUpdate;
  ImageList1.Clear;

  EnumWindows(Addr(EnumWindowProc), LPARAM(ListView1));
  ListView1.Items.EndUpdate;
end;

//=============================================================================
//  ListView1 の幅を変更したらカラムの幅も変更
//=============================================================================
procedure TForm1.ListView1Resize(Sender: TObject);
begin
  ListView1.Columns[0].Width := ListView1.ClientWidth;
end;

//-----------------------------------------------------------------------------
//  EnumWindowsのコールバック関数
//  このコールバック内で現在起動中のアプリの一覧を作成
//
//  所有されていないウィンドウのみ
//  非可視ウィンドウは除く
//  ウィンドウのタイトルがあるもののみ
//-----------------------------------------------------------------------------
function EnumWindowProc(hWindow: HWND; lPara: TListView): Boolean; stdcall;
var
  LWndText : string;
  LItem    : TListItem;
  LIcon    : TIcon;
begin
  Result:= True;

  if GetWindow(hWindow, GW_OWNER) <> 0 then Exit;
  if not IsWindowVisible(hWindow) then Exit;

  // ウィンドウのタイトルを取得
  LWndText := GetWindowTitle(hWindow);
  if LWndText = '' then Exit;
  if SameText(LWndText, 'Program Manager') then Exit;

  // このウィンドウのアイコンを表示
  LIcon := TIcon.Create;
  try
    LItem := lPara.Items.Add;
    LItem.Caption := LWndText;

    LIcon.Handle     := GetIconHandleFromWindow(hWindow);
    LItem.ImageIndex := Form1.ImageList1.AddIcon(LIcon);
  finally
    FreeAndNil(LIcon);
  end;
end;

//-----------------------------------------------------------------------------
//  ウィンドウハンドルからアイコンを取得する関数
//-----------------------------------------------------------------------------
function GetIconHandleFromWindow(hWindow: HWND): HICON;
begin
  Result := SendMessage(hWindow, WM_GETICON, ICON_SMALL2, 0);
  if Result = 0 then begin
    Result := SendMessage(hWindow, WM_GETICON, ICON_SMALL, 0);

    if Result = 0 then begin
      Result := GetClassLong(hWindow, GCL_HICONSM);
    end;
  end;
end;    

//-----------------------------------------------------------------------------
//  ウィンドウのタイトルを取得する関数
//-----------------------------------------------------------------------------
function GetWindowTitle(hWindow: HWND): string;
var
  LBuff : array [0..MAX_PATH - 1] of Char;
begin
  Result := '';
  FillChar(LBuff, SizeOf(LBuff), #0);
  if (GetWindowText(hWindow, @LBuff, MAX_PATH) > 0) then begin
    Result := LBuff;
  end;
end;

end.




04_起動中のアプリケーションのリスト  ( 2 ) プロセスのリスト


正確に言うと,アプリケーションのリストではなく,タスクマネージャーの [プロセス] タブの [イメージ名] に表示されるものを取得して表示するサンプルです.
1 つのアプリケーションが複数のプロセスを生成することもあります.したがって,同じ EXE が複数表示されることもあります.このサンプルは CreateToolhelp32Snapshot という関数でプロセスリストを取得します.EXE を管理者権限で実行すると,システム関係のプロセスも取得します.

プロセスのリストを取得する際,そのプロセスを生成した EXE 名に Excel.exe があると強制的に終了するようしています (大文字と小文字の区別はしない).つまり,エクセルのプロセスは常に表示されません.

このサンプルを掲載した約半年後に,プロセスのリストだけのサンプルを [330_起動中のプロセス一覧と強制終了] に掲載しましたが,そのまま残しています.コード内で使用している関数類は,[330_起動中のプロセス一覧と強制終了] のページにリンクがあります.


図3
設計時画面
図4
実行画面
  • EXCEL.EXE は表示されない

リスト2
起動中のプロセスを CreateToolhelp32Snapshot を使用して表示
unit AppList2Unit;
interface

uses
  Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes,
  Vcl.Graphics, Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.StdCtrls, Vcl.ComCtrls,
  Winapi.TlHelp32;

type
  TForm1 = class(TForm)
    ListView1: TListView;
    Button1: TButton;
    procedure Button1Click(Sender: TObject);
  private
    { Private 宣言 }
  public
    { Public 宣言 }
  end;

var
  Form1: TForm1;

implementation

uses
  System.StrUtils;

{$R *.DFM}

//=============================================================================
//  TListView にプログラムの EXE 名をリスト表示する
//  その中に'Excel.exe が あれば強制終了
//  Toolhelp32Snapshot を使用する方法
//
//  CreateToolhelp32Snapshot の使用には,uses に inapi.TlHelp32 が必要
//  ContainsText の使用には,uses に System.StrUtils が必要
//=============================================================================
procedure TForm1.Button1Click(Sender: TObject);
var
  ListHandle   : NativeUInt;
  LProcEntry   : TProcessEntry32;
  LProcessID   : DWORD;
  LExePath     : string;
  LhProcHandle : DWORD;
begin
  ListView1.Items.Clear;
  ListView1.Items.BeginUpdate;

  // プロセスのスナップショットのハンドルを取得
  ListHandle := CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
  if ListHandle > 0 then begin
    try
      // 最初のプロセスに関する情報を TProcessEntry32 レコード型に取得
      LProcEntry.dwSize := SizeOf(TProcessEntry32);
      Process32First(ListHandle, LProcEntry);
      repeat
        // パス名とプロセスID を取得
        LExePath   := LProcEntry.szExeFile;
        LProcessID := LProcEntry.th32ProcessID;

        // Terminate (プロセスの終了)アクセスモードでプロセスID を取得
        LhProcHandle := OpenProcess(PROCESS_TERMINATE, False, LProcessID);

        try
          // Excel.exe が起動していたら強制終了
          if ContainsText(LExePath, 'EXCEL.EXE') then begin
            TerminateProcess(LhProcHandle, 0);
          end else begin
            if LhProcHandle > 0 then begin
              ListView1.Items.Add.Caption := LExePath;
            end;
          end;
        finally
          CloseHandle(LhProcHandle);
        end;

      // 次のプロセスに関する情報を TProcessEntry32 レコード型に取得
      until Process32Next(ListHandle, LProcEntry) = False;
    finally
      CloseHandle(ListHandle);
    end;
  end;
  ListView1.Items.EndUpdate;
end;

end.




05_起動中のアプリケーションのリスト ( 3 ) アプリのフルパスを表示


最初のサンプルに,アプリのフルパスを表示する機能を追加したサンプルです.
EnumWindows の引数に TListView を渡し,コールバック関数内で TListView に表示していきます.
コード内で使用している関数類は,[330_起動中のプロセス一覧と強制終了] のページにリンクがあります.
このサンプルで使用している QueryFullProcessImageNameW は Windows Vista 以降で使用可能な関数です.したがって,Windows Vista 以降でないと動作しません.
Windows XP でフルパスを取得するには,GetModuleFileNameEx 関数を使用します.


  • 図5
  • 設計時画面

  • 図6
  • 実行画面
  • Excel 2007 と Excel 2010 が起動しているが,フルパスなら区別できる

リスト3
起動中のアプリケーションをフルパスで表示
unit AppList3Unit;

interface

uses
  Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes,
  Vcl.Graphics,Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.StdCtrls, Vcl.ExtCtrls,
   Winapi.ShellAPI, Vcl.ComCtrls, Vcl.ImgList;

type
  TForm1 = class(TForm)
    ImageList1: TImageList;
    ListView1: TListView;
    Button1: TButton;
    procedure Button1Click(Sender: TObject);
    procedure FormCreate(Sender: TObject);
  private
    { Private 宣言 }
  public
    { Public 宣言 }
  end;

  function QueryFullProcessImageNameW(Process: THandle; Flags: DWORD; Buffer: PChar;
    Size: PDWORD): DWORD; stdcall; external 'kernel32.dll';
  function EnumWindowProc(hWindow: HWND; lPara: TListView): Boolean; stdcall;
  function GetIconHandleFromWindow(hWindow: HWND): HICON;
  function GetWindowTitle(hWindow: HWND): string;

var
  Form1: TForm1;

implementation

{$R *.DFM}

//=============================================================================
//  フォーム生成時の処理
//  一覧表示用の TListView の設定
//=============================================================================
procedure TForm1.FormCreate(Sender: TObject);
begin
  ImageList1.BkColor    := ListView1.Color;
  ListView1.ViewStyle   := vsReport;
  ListView1.SmallImages := ImageList1;

  ListView1.Columns.Add;
  ListView1.Columns[0].Caption := ' アプリケーション';
  ListView1.Columns[0].Width   := 240;

  ListView1.Columns.Add;
  ListView1.Columns[1].Caption := ' アプリケーションのフルパス';
  ListView1.Columns[1].Width   := 310;
end;

//=============================================================================
//  ウィンドウのリストを作成
//=============================================================================
procedure TForm1.Button1Click(Sender: TObject);
begin
  ListView1.Items.Clear;
  ListView1.Items.BeginUpdate;
  ImageList1.Clear;

  ImageList1.BkColor    := ListView1.Color;
  ImageList1.BlendColor := ListView1.Color;

  EnumWindows(@EnumWindowProc, LPARAM(ListView1));
  ListView1.Items.EndUpdate;
end;

//-----------------------------------------------------------------------------
//  EnumWindowsのコールバック関数
//  TListView にプログラムの EXE 名をフルパスで表示する
//  QueryFullProcessImageNameW は Delphi XE に定義がないので定義が必要
//-----------------------------------------------------------------------------
function EnumWindowProc(hWindow: HWND; lPara: TListView): Boolean; stdcall;
const
  PROCESS_QUERY_LIMITED_INFORMATION = $1000;
var
  LWndText     : string;
  LProcessID   : DWORD;
  LhProcHandle : THandle;
  LBuffer      : array [0..MAX_PATH - 1] of Char;
  LSTR_SIZE    : DWORD;
  LExeFullPath : string;
  LIcon        : TIcon;
  LItem        : TListItem;
begin
  Result := True;

  if GetWindow(hWindow, GW_OWNER) <> 0 then Exit;
  if not IsWindowVisible(hWindow) then exit;

  // ウィンドウのタイトルがあるもののみ
  LWndText := GetWindowTitle(hWindow);
  if LWndText = '' then Exit;
  if SameText(LWndText, 'Program Manager') then Exit;

  // プロセスID を取得して,その値でプロセスのオープンハンドル取得
  GetWindowThreadProcessId(hWindow, Addr(LProcessID));
  LhProcHandle := OpenProcess(PROCESS_QUERY_LIMITED_INFORMATION,
                              False,
                              LProcessID);
  try
    // EXE のフルパスを取得
    FillChar(LBuffer, SizeOf(LBuffer), #0);
    LSTR_SIZE := Length(LBuffer);
    LExeFullPath:='';
    if QueryFullProcessImageNameW(LhProcHandle,
                                  0,
                                  @LBuffer[0],
                                  @LSTR_SIZE) > 0 then begin
      LExeFullPath := LBuffer;
    end;

    // ウィンドウのアイコンを取得して TListView にパス名と共に表示
    LIcon := TIcon.Create;
    try
      LIcon.Handle     := GetIconHandleFromWindow(hWindow);
      LItem            := lPara.Items.Add;
      LItem.Caption    := LWndText;
      LItem.ImageIndex := lPara.SmallImages.AddIcon(LIcon);
    finally
      FreeAndNil(LIcon);
    end;
    LItem.SubItems.Add(LExeFullPath);
  finally
    CloseHandle(LhProcHandle);
  end;
end;

//-----------------------------------------------------------------------------
//  ウィンドウハンドルからアイコンを取得する関数
//-----------------------------------------------------------------------------
function GetIconHandleFromWindow(hWindow: HWND): HICON;
begin
  Result := SendMessage(hWindow, WM_GETICON, ICON_SMALL2, 0);
  if Result = 0 then begin
    Result := SendMessage(hWindow, WM_GETICON, ICON_SMALL, 0);
    if Result = 0 then begin
      Result := GetClassLong(hWindow, GCL_HICONSM);
    end;
  end;
end;      

//-----------------------------------------------------------------------------
//  ウィンドウのタイトルを取得する関数
//-----------------------------------------------------------------------------
function GetWindowTitle(hWindow: HWND): string;
var
  LBuff : array [0..MAX_PATH - 1] of Char;
begin
  Result := '';
  FillChar(LBuff, SizeOf(LBuff), #0);
  if (GetWindowText(hWindow, @LBuff, MAX_PATH) > 0) then begin
    Result := LBuff;
  end;
end;

end.




06_起動中のアプリケーションのリスト ( 4 ) 選択アプリの強制終了


最初のサンプルと同じように,アプリケーションのリストを取得します.
EnumWindows の引数に TStringList を渡し,コールバック関数内で,その TStringList にアプリのウィンドウの情報を格納していきます.取得が終了したら,TListView に表示します.更に,リストで選択したアプリケーションを強制終了します.

対象のアプリケーションに WM_CLOSE メッセージを送っていますが,アプリケーションによっては,終了しないことがあるかも知れません.その時は他の方法を検討してください.


図7
設計時画面
図8
アプリのリストを表示
  • ワードの文書を選択して [アプリ終了] ボタンをクリック
図9
ワードがリストから消える

リスト4
起動中のアプリケーションのリストから,選択したアプリを強制終了
unit AppList4Unit;

interface

uses
  Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes,
  Vcl.Graphics,Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.StdCtrls, Vcl.ExtCtrls,
  Vcl.ImgList, Vcl.ComCtrls; 
type
  TForm1 = class(TForm)
    ImageList1: TImageList;
    ListView1: TListView;
    Button1: TButton;
    Button2: TButton;
    procedure FormCreate(Sender: TObject);
    procedure FormDestroy(Sender: TObject);
    procedure Button1Click(Sender: TObject);
    procedure Button2Click(Sender: TObject);
    procedure ListView1Resize(Sender: TObject);
  private
    { Private 宣言 }
    FStrList : TStringList;
  public
    { Public 宣言 }
  end;

  function EnumWindowProc(hWindow: hWnd; lPara: TStringList): Boolean; stdcall;
  function GetIconHandleFromWindow(hWindow: HWND): HICON;
  function GetWindowTitle(hWindow: HWND): string;

var
  Form1: TForm1;

implementation

{$R *.DFM}

//=============================================================================
//  フォーム生成時の処理
//  一覧表示用の TListView の設定
//=============================================================================
procedure TForm1.FormCreate(Sender: TObject);
begin
  ImageList1.BkColor := ListView1.Color;

  ListView1.ViewStyle   := vsReport;
  ListView1.SmallImages := ImageList1;

  ListView1.Columns.Add;
  ListView1.Columns[0].Caption := ' アプリケーション';
  ListView1.Columns[0].Width   :=  ListView1.ClientWidth;

  FStrList := TStringList.Create;
end;

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

//=============================================================================
//  ウィンドウのリストを作成
//=============================================================================
procedure TForm1.Button1Click(Sender: TObject);
var
  LIndex : Integer;
  LIcon  : TIcon;
  LItem  : TListItem;
begin
  ImageList1.Clear;
  ListView1.Items.Clear;
  FStrList.Clear;

  // ウィンドウの情報を TStringList に格納
  EnumWindows(Addr(EnumWindowProc), LPARAM(FStrList));

  // 取得した TStringList の情報を TListView に表示
  ListView1.Items.BeginUpdate;
  for LIndex := 0 to FStrList.Count - 1 do begin
    LItem := ListView1.Items.Add;
    LIcon :=TIcon.Create;
    try
      LIcon.Handle  := GetIconHandleFromWindow(THandle(FStrList.Objects[LIndex]));
      LItem.Caption := FStrList[LIndex];
      LItem.ImageIndex := ImageList1.AddIcon(LIcon);
    finally
      FreeAndNil(LIcon);
    end;
  end;
  ListView1.Items.EndUpdate;
end;

//=============================================================================
//  現在選択中のアプリを強制終了
//=============================================================================
procedure TForm1.Button2Click(Sender: TObject);
var
  LTargetWnd : HWND;
  LIndex     : Integer;
begin
  if ListView1.ItemFocused = nil then exit;

  LIndex  := ListView1.ItemFocused.Index;
  LTargetWnd := THandle(FStrList.Objects[LIndex]);

  if LTargetWnd > 0 then begin
    if LTargetWnd = Application.Handle then begin
      Close;
    end else begin
      // アプリを閉じる
      PostMessage(LTargetWnd, WM_CLOSE, 0, 0);
      Sleep(100);

      Button1Click(nil);
    end;
  end;
end;

//=============================================================================
//  ListView1 の幅を変更したらカラムの幅も変更
//=============================================================================
procedure TForm1.ListView1Resize(Sender: TObject);
begin
  ListView1.Columns[0].Width := ListView1.ClientWidth;
end;

//-----------------------------------------------------------------------------
//  EnumWindowsのコールバック関数
//  この関数内で現在起動中のアプリの一覧を作成
//  TStringList にウィンドウの情報を格納
//-----------------------------------------------------------------------------
function EnumWindowProc(hWindow: hWnd; lPara: TStringList): Boolean; stdcall;
var
  LWndTitle : string;
begin
  Result:= True;

  if GetWindow(hWindow, GW_OWNER) <> 0 then Exit;
  if not IsWindowVisible(hWindow) then exit;

  // ウィンドウのタイトルがあるもののみ
  LWndTitle := GetWindowTitle(hWindow);
  if LWndTitle = '' then Exit;
  if SameText(LWndTitle, 'Program Manager') then Exit;

  // lPara にウィンドウタイトルとウィンドウハンドル値を格納
  // lPara はこのコールバック関数の引数で TStringList 型
  lPara.AddObject(LWndTitle, TObject(hWindow));
end;

//-----------------------------------------------------------------------------
//  ウィンドウハンドルからアイコンを取得する関数
//-----------------------------------------------------------------------------
function GetIconHandleFromWindow(hWindow: HWND): HICON;
begin
  Result := SendMessage(hWindow, WM_GETICON, ICON_SMALL2, 0);
  if Result = 0 then begin
    Result := SendMessage(hWindow, WM_GETICON, ICON_SMALL, 0);
    if Result = 0 then begin
      Result := GetClassLong(hWindow, GCL_HICONSM);
    end;
  end;
end;

//-----------------------------------------------------------------------------
//  ウィンドウのタイトルを取得する関数
//-----------------------------------------------------------------------------
function GetWindowTitle(hWindow: HWND): string;
var
  LBuff : array [0..MAX_PATH - 1] of Char;
begin
  Result := '';
  FillChar(LBuff, SizeOf(LBuff), #0);
  if (GetWindowText(hWindow, @LBuff, MAX_PATH) > 0) then begin
    Result := LBuff;
  end;
end;

end.




07_起動中のアプリ列挙の Widows 10 UWP アプリへの対応


ここからは Windows 10 対応の記事とサンプルになっています.

下図は,本ページの最初のサンプルプログラムを Windows 10 で実行した結果です.待機中のアプリとバックグラウンドとで実行中のアプリもリストされています.タスクマネージャーでは,それらのアプリを表示しません.



図10
本ページの最初のサンプルを Windows 10 で実行
  • UWP アプリである
    電卓
    設定
    ペイント 3D
    がリストされている
  • これらは実際には起動していない.待機中のアプリ
  • バックグラウンドのアプリもリストされている
  • UWP アプリのアイコンが取得できていない.
図11
タスクマネージャーでの表示

Windows API の DwmGetWindowAttribute 関数を使用すると,待機中のアプリとバックグラウンドで実行中のアプリを検出できます.DwmGetWindowAttribute 関数の第 2 引数を DWMWA_CLOAKED にして実行して,戻り値である第 3 引数の値で判定します.



function EnumWindowProc(hWindow: HWND; lPara: LPARAM): Boolean; stdcall;
const
  DWMWA_CLOAKED = 14;
var
  // (略)
  LCloaked : BOOL;
begin

  // (略)

  // 待機中またはバックグラウンドのウィンドウは対象外とする
  if Form1.CheckBox1.Checked then begin
    DwmGetWindowAttribute(hWindow, DWMWA_CLOAKED, @LCloaked, SizeOf(LCloaked));
    if LCloaked then Exit;
  end;

  // (略)

end;


下図は,待機中のアプリとバックグラウンドで実行中のアプリを除外した結果です.[A;t] + [TAB] で表示されるウィンドウのリストとほぼ同じになります.



図12
待機中のアプリとバックグラウンドで実行中のアプリを除外した結果
  • [A;t] + [TAB] で表示されるウィンドウのリストとほぼ同じになる

[備考]
クローク (Cloaked は Cloak の過去形) は,ホテルやレストラン,劇場などで,コートや手荷物を一時的に預かる場所のことです.「昭和が明るかった頃」の時代には「手荷物預かり所」とも言われていました.
Cloak には,覆い隠すという意味もあります.




08_Windows 10 における UWP アプリのウィンドウ構造


Windows に付属しているメモ帳が起動しているかは以下のように,メモ帳のクラス名である notepad のウィンドウを調べることで確認できます.しかし,UWP アプリではこの方法は利用できません.


//=============================================================================
//  指定アプリが起動しているか調べる
//  指定クラス名のウィンドウハンドルを取得する方法
//
//  この方法は Window 10 の UWP アプリでは利用できない
//=============================================================================
procedure TForm1.Button1Click(Sender: TObject);
var
  LTargetWnd : HWND;
begin
  LTargetWnd := FindWindow('Notepad', nil);
  if LTargetWnd <> 0 then begin
    // メモ帳が起動している
  end;
end;


下図は「ペイント 3D」という UWP アプリを起動した時のウィンドウの構造です.ApplicationFrameWindow というクラス名のウィンドウとなっています.その階層下に Windows.UI.Core.CoreWindow というクラス名のウィンドウがあります.UWP アプリのウィンドウは全てこ構造になっています.



図13
UWP アプリのウィンドウ構造
  • ApplicationFrameWindow がトップレベルのウィンドウ
  • その階層下に Windows.UI.Core.CoreWindow のウィンドウがある

UWP アプリのウィンドウを最小化した時のウィンドウの構造です.クラス名が Windows.UI.Core.CoreWindow のウィンドウはトップレベルのウィンドウに移動します.
つまり,UWP アプリのウィドウのクラス名は全て同じということです.したがって,起動中の UWP アプリをウィンドウのクラス名で判定はできないことになります.

最小化時,それらのウィンドウは 1 つのジョブとして実行されていて,indows.UI.Core.CoreWindow のウィンドウはバッググラウンドで実行されています.



図14
最小化した UWP アプリのウィンドウ構造
  • Windows.UI.Core.CoreWindow は独立したトップレベルのウィンドウになっている
  • ApplicationFrameWindow の階層下には,そのクラス名のウィンドウはなくなる




09_指定アプリ名の UWP アプリが起動しているかを確認


前項で,UWP起動中の アプリではウィンドウのクラス名では判定できないことを説明しまた.
UWP アプリをインストールした時に登録される「アプリの名前」 (アプリ名) を使用すると,その UWP アプリが起動しているかを確認することができます.

Windows 10 では,インストールされているアプリは,UWP アプリも含めて以下の AppsFolder という仮想フォルダにリストがあります.次のコマンドを [ファイル名を指定して実行] のダイアログ,またはエクスプローラのアドレスバーで実行すると表示できます.PowerShell またはコマンドプロンプトから実行する時は先頭に start または explorer が必要です.


shell:AppsFolder


本サンプルは,このリストにある UWP アプリである「フォト 3D」が起動しているかを調べるサンプルです.「フォト 3D」は以下のコマンド (URL スキーム) で起動できます.UWP アプリは,Microsoft Edge 等,一部のアプリを除いて,EXE ファイルからは起動できません.URL スキームか AppUserModeId と呼ばれるアプリケーション ID を使用して起動します.
以下のような URL スキームの場合,ウェブブラウザからも起動できます.


ms-paint:


トップレベルのウィンドウから,クラス名が ApplicationFrameWindow のウィンドウを探します.
そのウィンドウの階層下に,クラス名が Windows.UI.Core.CoreWindow のウィンドウがあれば,それは起動中の UWP アプリです.Windows.UI.Core.CoreWindow のウィンドウのタイトルを調べます.そのタイトルが検索する文字列と一致すれば,それが探している目的のアプリです.

階層下に Windows.UI.Core.CoreWindow のウィンドウがなければ,その UWP アプリは最小化状態か待機中です.トップレベルのウィンドウから,このクラス名のウィンドウを調べます.そのウィンドウのタイトルが探している文字列と一致すれば,それが目的の UWP アプリのウィンドウです.
最小化状態は,Windows API の IsIconic 関数で判定できますが,Windows.UI.Core.CoreWindow のウィンドウは全てバックグラウンドでの実行中となり,最小化の状態としては検出できません.

実用的なプログラムにするには,UWP アプリでないアプリの起動も検出可能にした方がいいかも知れません.そのためには,ウィンドウの情報をレコード型等にして保持する必要があります.



UWP アプリを識別するのは AppUserModelId の値です.特定の AppUserModleId のアプリが起動中かを確認するには,上のリンクの 2 番目の記事のサンフルを参考にしてください.
UWP アプリではないデスクトップのアプリを識別するのは EXE ファイル名です,特定の EXE が起動しているかを確認する方法については,上のリンクの 3 番目のサンプルを参考にしてください.



図15
指定 UWP アプリが起動中かを確認
  • 「フォト」や「電卓」等のアプリ名で調べる

リスト5
指定アプリ名の UWP アプリが起動しているかを調べる
unit Unit1;

interface

uses
  Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes,
  Vcl.Graphics,Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.StdCtrls, Vcl.ExtCtrls,
   Winapi.ShellAPI;

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

  function EnumWindowProc(hWindow: HWND; lPara: LPARAM): Boolean; stdcall;
  function GetWindowTitle(hWindow: HWND): string;
  function GetWindowClassName(hWindow: HWND): string;

var
  Form1: TForm1;

implementation

uses
  Winapi.Dwmapi;

{$R *.DFM}

const
  UWP_FRAMEWND = 'ApplicationFrameWindow';
  UWP_COREWND  = 'Windows.UI.Core.CoreWindow';
var
  FAppName : string;

//=============================================================================
//  指定した UWP アプリが起動しているかを調べる
//=============================================================================
procedure TForm1.Button1Click(Sender: TObject);
var
  LText   : string;
  LAppWnd : HWND;
begin
  FAppName := 'ペイント 3D';
  LAppWnd  := 0;

  EnumWindows(@EnumWindowProc, LPARAM(@LAppWnd));

  if LAppWnd <> 0 then begin
    LText := FAppName + sLineBreak + '起動中。';
  end else begin
    LText := FAppName + sLineBreak + '検出できませんでした。' + sLineBreak
           + 'UWP アプリではない可能性もあります。';
  end;
  MessageBox(Handle, PChar(LText), '情報', MB_ICONINFORMATION);
end;

//=============================================================================
//  ペイント 3D (UWP アプリ) を起動
//=============================================================================
procedure TForm1.Button2Click(Sender: TObject);
begin
  ShellExecute(0, '', 'ms-paint:', nil, nil, SW_SHOWNORMAL);
end;

//=============================================================================
//  shell:AppsFolder を実行してインストールされているアプリの一覧を表示
//=============================================================================
procedure TForm1.Button3Click(Sender: TObject);
begin
  ShellExecute(0, '', 'shell:AppsFolder', nil, nil, SW_SHOWNORMAL);
end;

//-----------------------------------------------------------------------------
//  EnumWindows 関数のコールバック関数
//  クラス名が Windows.UI.Core.CoreWindow のウィンドウを調べる
//  ウィンドウのタイトル名と目的の文字列が同じウィンドウが見つかったら終了
//  Windows.UI.Core.CoreWindow のウィンドウタイトルはアプリの名前 (表示名)
//
//  FAppName が検索するアプリ名.グローバル変数にしておく
//-----------------------------------------------------------------------------
function EnumWindowCoreProc(hWindow: HWND; lPara: LPARAM): Boolean; stdcall;
begin
  Result := True;
  if GetWindowClassName(hWindow) <> UWP_COREWND then Exit;

  if SameText(FAppName, GetWindowTitle(hWindow)) then begin
    PNativeInt(lPara)^ := hWindow;
    Result := False;
  end;
end;

//-----------------------------------------------------------------------------
//  EnumWindows 関数のコールバック関数
//  クラス名が ApplicationFrameWindow のウィンドウを調べる
//-----------------------------------------------------------------------------
function EnumWindowProc(hWindow: HWND; lPara: LPARAM): Boolean; stdcall;
const
  DWMWA_CLOAKED = 14;
var
  LCoreTitle : string;
  LCloaked   : BOOL;
  LCoreWnd   : HWND;
begin
  Result := True;

  if GetWindow(hWindow, GW_OWNER) <> 0 then exit;
  if not IsWindowVisible(hWindow) then exit;

  // 待機中またはバックグラウンドのウィンドウは対象外
  DwmGetWindowAttribute(hWindow, DWMWA_CLOAKED, @LCloaked, SizeOf(LCloaked));
  if LCloaked then Exit;

  if GetWindowClassName(hWindow) = UWP_FRAMEWND then begin
    LCoreWnd := FindWindowEx(hWindow, 0, PChar(UWP_COREWND), nil);

    if LCoreWnd <> 0 then begin
      // 最小化ではない状態
      LCoreTitle := GetWindowTitle(LCoreWnd);

      if SameText(FAppName, LCoreTitle) then begin
        PNativeInt(lPara)^ := hWindow;
        Result := False;
      end;
    end else begin
      // 最小化している場合
      // クラス名が Windows.UI.Core.CoreWindow のウィンドウだけを調べる
      EnumWindows(@EnumWindowCoreProc, lPara);

      if PNativeInt(lPara)^ <> 0 then begin
        PNativeInt(lPara)^ := hWindow;
        Result := False;
      end;
    end;
  end;
end;

//-----------------------------------------------------------------------------
//  ウィンドウのタイトルを取得する関数
//-----------------------------------------------------------------------------
function GetWindowTitle(hWindow: HWND): string;
var
  LBuff : array [0..MAX_PATH - 1] of Char;
begin
  Result := '';
  FillChar(LBuff, SizeOf(LBuff), #0);
  if (GetWindowText(hWindow, @LBuff, MAX_PATH) > 0) then begin
    Result := LBuff;
  end;
end;

//-----------------------------------------------------------------------------
//  ウィンドウのクラス名を取得する関数
//-----------------------------------------------------------------------------
function GetWindowClassName(hWindow: HWND): string;
var
  LBuff : array [0..MAX_PATH - 1] of Char;
begin
  Result := '';
  FillChar(LBuff, SizeOf(LBuff), #0);
  if (GetClassName(hWindow, @LBuff, MAX_PATH) > 0) then begin
    Result := LBuff;
  end;
end;

end.




10_UWP を含むアプリのアイコンをアプリ名から取得


インストールされているアプリから,指定したアプリのアイコン画像を取得するサンプルです.これは,システムのイメージリストに登録されたアイコンの取得です.UWP アプリの場合も同じです.UWP アプリのパッケージ内の画像を取得するわけではありません.

Windows 10 では,インストールされているアプリのリストが,仮想フォルダ AppsFolder にあります.次のコマンドを「ファイル名を指定して実行」のダイアログか,エクスプローラのアドレス欄に貼り付けて実行すると表示できます.


shell:AppsFolder

PowerShell またはコマンドプロンプトから実行する場合は,先頭に start か explorer が必要です.


start shell:AppsFolder
explorer shell:AppsFolder

表示したら [表示] [詳細] で詳細表示にします.その後,[表示] [詳細表示の設定] のダイアログで [名前] と [AppUserModelId] だけをチェックします.
左側の [名前] 欄の表示名がアプリの名前 (アプリ名) です.本サンプルでは,ここに表示しているアイコ画像を取得して Image に描画します.右側の AppUserModelId は,アプリケーション識別子と呼ばれている値です.AppId と記述することもあります.
このフォルダ内のファイルは,コピー等の編集操作はできませんが,項目をダブルクリックすればアプリの起動は可能です.ショートカットの作成やピン留めも可能です.



図16
インストールされているアプリ

仮想フォルダ AppsFolder にはファイルシステムにおけるディレクトリがありません.一般的なファイル操作では [名前と [AppUserModleId] の値を取得できませんが,Windows のシェル名前空間の機能を使用すると取得できます.シェル名前空間では,フォルダ,ファイルに対して項目識別子 (項目 ID) と呼ばれる識別子を割り当てます.
本サンプルは,最初に仮想フォルダ AppsFolder 内の名前と AppUserModelId のリストを作成しておきます.アイコン画像の取得は,そのリストの AppUserModelId に相当する項目識別子を使用して取得します.

下の 2 番目の図は,UWP アプリ「フォト」のアイコン画像を取得した結果です.最初の TEdit.Text の値が検索対象のアプリ名です.その下の TEdit の値は AppUserModelId の値です.この値を,シェルとしてのフルパスを,以下のように実行するとそのアプリが起動します.


shell:AppsFolder\Microsoft.Windows.Photos_8wekyb3d8bbwe!App

PowerShell またはコマンドプロンプトから実行する場合は,先頭に start か explorer が必要です.


start shell:AppsFolder\Microsoft.Windows.Photos_8wekyb3d8bbwe!App
explorer shell:AppsFolder\Microsoft.Windows.Photos_8wekyb3d8bbwe!App


[備考]
インストールされていアプリ全てが仮想フォルダ AppsFolder のリストにあるわではありません.[スタート] メニューに登録していないアプリはリストにありません.


図17
UWP アプリ「設定」のアイコン画像
図18
UWP アプリ「フォト」のアイコン画像
図19
InterNet Explorer のアイコン画像
  • Internet Explorer は UWP アプリではない

Delphi XE5 のアイコン画像です.Delphi XE5 は UWP アプリではありません.
Delphi XE5 の AppUserModelId の値はシステムが自動的に設定した値となっています.アプリケーションが AppUserModelId の値を指定していない場合,AppUserModelId の値は,そのアプリケーションのインストールの際にシステムが自動的に設定します.



図20
Delphi XE5 のアイコン画像
  • Delphi XE5 は UWP アプリではない

Microsoft Edge のアイコン画像です.Microsoft Edge は UWP アプリです.筆者の Windows 10 の環境には,Microsoft Edge が 2 つ登録されています.もう1 つは以下のパッケージ名 (ファミリー名) で登録されいます.こちらにはアイコン画像がありません.


Microsoft.MicrosoftEdge_8wekyb3d8bbwe!MicrosoftEdge


図21
Microsoft Edge のアイコン画像
  • パッケージ名 (ファミリー名) でも登録されてい.るが,アイコン画像はない

リスト6
UWP を含むアプリのアイコンをアプリ名から取得
unit Unit1;

interface

uses
  Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes,
  Vcl.Graphics,Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.StdCtrls, Vcl.ExtCtrls,
    Winapi.CommCtrl;

type
  TForm1 = class(TForm)
    Button1: TButton;
    Edit1: TEdit;
    Edit2: TEdit;
    Image1: TImage;
    Button2: TButton;
//    procedure FormDestroy(Sender: TObject);
    procedure Button1Click(Sender: TObject);
    procedure FormCreate(Sender: TObject);
    procedure Button2Click(Sender: TObject);
    procedure FormDestroy(Sender: TObject);
  private
    { Private 宣言 }
  public
    { Public 宣言 }
  end;

  function GetSystemImageList(ASHILValue: Cardinal): HIMAGELIST;

var
  Form1: TForm1;

implementation

uses
  Winapi.ShellAPI,
  Winapi.ActiveX,
  Winapi.ShlObj,
  Generics.Collections;

{$R *.dfm}

type
  PAppIdItem = ^TAppIdItem;
  TAppIdItem = Record
    AppName : string;
    AppID   : string;
  end;

var
  FAppIDList : TList<TAppIdItem>;

//=============================================================================
//  フォーム生成時の処理
//  仮想フォルダ AppsFolder 内の名前と AppUserModelId の値のリストを作成
//
//  必要なユニット類は uses 部を参照
//=============================================================================
procedure TForm1.FormCreate(Sender: TObject);
var
  LPath         : WideString;
  LpidlRoot     : PItemIDList;
  LShItemRoot   : IShellItem;
  LShItemChild  : IShellItem;
  LEnumItems    : IEnumShellItems;
  LFetched      : LongInt;
  LAppName      : PWideChar;
  LAppIDPath    : PWideChar;
  LAppItem      : TAppIdItem;
begin
  FAppIDList := TList<TAppIdItem>.Create;
  FAppIDList.Clear;

  LpidlRoot    := nil;
  LShItemRoot  := nil;
  LShItemChild := nil;

  LPath := 'shell:AppsFolder';
  try
    // ファイルを項目識別子に変換
    LpidlRoot := ILCreateFromPath(PChar(LPath));
    if ILIsEmpty(LpidlRoot) then Exit;

    // フォルダの IShellItem オブジェクトを取得
    SHCreateItemFromIDList(LpidlRoot, IShellItem, LShItemRoot);

    // ファイル (IShellItem のリストを LEnumItems として取得
    LShItemRoot.BindToHandler(nil, BHID_EnumItems, IEnumShellItems, LEnumItems);

    // リストの各項目の表示名を調べる
    while (LEnumItems.Next(1, LShItemChild, @LFetched) = S_OK) do begin
      LShItemChild.GetDisplayName(SIGDN_DESKTOPABSOLUTEPARSING, LAppIDPath);
      if StrLen(LAppIDPath) = 0 then Continue;
      LShItemChild.GetDisplayName(SIGDN_NORMALDISPLAY, LAppName);
      if StrLen(LAppName) = 0 then Continue;

      // レコード型に AppUserModelId とその表示名をセットしてリストに追加
      LAppItem.AppName := LAppName;
      LAppItem.AppID   := LAppIDPath;
      FAppIDList.Add(LAppItem);
    end;
  finally
    LEnumItems := nil;
    LShItemRoot  := nil;
    LShItemChild := nil;
    CoTaskMemFree(LpidlRoot);
  end;
end;

//=============================================================================
//  フォーム破棄時の処理
//=============================================================================
procedure TForm1.FormDestroy(Sender: TObject);
begin
  FreeAndNil(FAppIDList);
end;

//=============================================================================
//  アイコンを取得して TImage に描画
//=============================================================================
procedure TForm1.Button1Click(Sender: TObject);
var
  LPath       : string;
  LAppTitle   : string;
  LAppIDPath  : string;
  LpidlFull   : PItemIDList;
  LIcon       : TIcon;
  LhImgList   : HIMAGELIST;
  LSHFileInfo : TSHFileInfo;
  LIndex      : Integer;
begin
  Image1.Picture.Assign(nil);
  Edit2.Text := '';

  // リストの各項目の表示名を調べる
  LAppTitle := '';
  for LIndex := 0 to FAppIDList.Count - 1 do begin
    LAppTitle := FAppIDList[LIndex].AppName;
    if SameText(LAppTitle, Trim(Edit1.Text)) then begin
      LAppIDPath := FAppIDList[LIndex].AppID;
      Break;
    end;
  end;
  if LAppTitle = '' then begin
    MessageBox(Handle, 'アイコンの取得に失敗しました。', '失敗', MB_ICONWARNING);
    Exit;
  end;


  Edit2.Text := LAppIDPath;

  // ファイルの項目識別子の値からアイコンを取得
  // SHGetFileInfo の引数にはフルパス相当の項目識別子(完全 PIDL) が必要
  LPath := 'shell:AppsFolder' + PathDelim + LAppIDPath;
  LpidlFull := ILCreateFromPath(PChar(LPath));
  SHGetFileInfo(Pointer(LpidlFull),
                0,
                LSHFileInfo,
                SizeOf(TSHFileInfo),
                SHGFI_PIDL or SHGFI_ICON or SHGFI_SMALLICON);

  LIcon := TIcon.Create;
  try
    // LIcon.Handle := LSHFileInfo.hIcon; であるが
    // 大きなアイコンにしてみる
    LhImgList := GetSystemImageList(SHIL_EXTRALARGE);
    LIcon.Handle := ImageList_GetIcon(LhImgList,
                                      LSHFileInfo.iIcon,
                                      ILD_TRANSPARENT);
    Image1.Picture.Assign(LIcon);
  finally
    FreeAndNil(LIcon);
    if LpidlFull <> nil then CoTaskMemFree(LpidlFull);
  end;

  Edit1.SetFocus;
end;

//=============================================================================
//  アプリを起動
//=============================================================================
procedure TForm1.Button2Click(Sender: TObject);
var
  LAppTitle   : string;
  LAppIDPath  : string;
  LIndex      : Integer;
begin
  Image1.Picture.Assign(nil);
  Edit2.Text := '';

  // リストの各項目の表示名を調べる
  LAppTitle := '';
  for LIndex := 0 to FAppIDList.Count - 1 do begin
    LAppTitle := FAppIDList[LIndex].AppName;
    if SameText(LAppTitle, Trim(Edit1.Text)) then begin
      LAppIDPath := FAppIDList[LIndex].AppID;
      Break;
    end;
  end;
  if LAppTitle = '' then begin
    MessageBox(Handle, 'アイコンの取得に失敗しました。', '失敗', MB_ICONWARNING);
    Exit;
  end;

  Edit2.Text := LAppIDPath;

  LAppIDPath := 'shell:AppsFolder\' + LAppIDPath;
  if ShellExecute(0, '', PChar(LAppIDPath), nil, nil, SW_SHOW) <= 32 then begin
    MessageBox(Handle, 'アプリの起動に失敗しました。', '失敗', MB_ICONWARNING);
  end;

  Edit1.SetFocus;
end;

//-----------------------------------------------------------------------------
//  SHGetImageList 関数を使用してシステムのイメージリストを取得
//  取得するアイコンのサイズを引数で指定する
//  定数名は ShellAPI.pas 内で定義されている
//-----------------------------------------------------------------------------
function GetSystemImageList(ASHILValue: Cardinal): HIMAGELIST;
type
  TSHGetImageList = function(iImageList: integer;
                             const riid: TGUID;
                             var ppv: Pointer): HRESULT; stdcall;
const
  IID_IImageList: TGUID= '{46EB5926-582E-4017-9FDF-E8998DAA0950}';
var
  LDllHandle      : THandle;
  LSHGetImageList : TSHGetImageList;
begin
  Result := 0;

  LDllHandle := LoadLibrary('Shell32.dll');
  if LDllHandle <> 0 then begin
    try
      LSHGetImageList := GetProcAddress(LDllHandle, 'SHGetImageList');
      if @LSHGetImageList <> nil then begin
        LSHGetImageList(ASHILValue, IID_IImageList, Pointer(Result));
      end;
    finally
      FreeLibrary(LDllHandle);
    end;
  end;
end;

end.


システムのイメージリストのアイコンサイズを SHIL_JUMBO) にすると,更に大きなサイズのアイコン画像が取得できます.これは 256 x 256 のサイズですが,このサイズがない場合はその下のサイズの取得になります.


LhImgList := GetSystemImageList(SHIL_JUMBO);


図22
更に大きなサイズのアイコン
  • 256 X 256

特定のアプリだけのアイコン画像を取得する場合は,AppsFolder フォルダ内の項目識別子のリストを作成する必要はありません.以下のように,AppUserModelId の値から,ILCreateFromPath 関数を使用して対象のアプリの項目識別子を取得して処理できます.



implementation    

uses
  Winapi.ShlObj,
  Winapi.ActiveX,
  Winapi.ShellAPI;

{$R *.dfm}

//=============================================================================
//  UWP アプリの「フォト」のアイコンを取得して TImage に描画
//=============================================================================
procedure TForm1.Button1Click(Sender: TObject);
var
  LAppIdFullPath : string;
  LAppIdPItemID  : PItemIDList;
  LIcon          : TIcon;
  LSHFileInfo    : TSHFileInfo;
begin
  Image1.Picture.Assign(nil);
  LAppIdPItemID  := nil;

  LAppIdFullPath := 'shell:AppsFolder\'
                  + 'Microsoft.Windows.Photos_8wekyb3d8bbwe!App';
  LAppIdPItemID := ILCreateFromPath(PWideChar(LAppIdFullPath));

  SHGetFileInfo(Pointer(LAppIdPItemID),
                0,
                LSHFileInfo,
                SizeOf(TSHFileInfo),
                SHGFI_PIDL or SHGFI_ICON or SHGFI_SMALLICON);

  LIcon := TIcon.Create;
  try
    LIcon.Handle := LSHFileInfo.hIcon;
    Image1.Picture.Assign(LIcon);
  finally
    FreeAndNil(LIcon);
  end;
  if LAppIdPItemID <> nil then CoTaskMemFree(LAppIdPItemID);
end;

//=============================================================================
//  UWP アプリの「フォト」を起動
//=============================================================================
procedure TForm1.Button2Click(Sender: TObject);
var
  LAppIdFullPath : string;
begin
  LAppIdFullPath := 'shell:AppsFolder\'
                  + 'Microsoft.Windows.Photos_8wekyb3d8bbwe!App';
  Shellexecute(0, '', PChar(LAppIdFullPath), nil, nil, SW_SHOW);
end;




11_起動中のアプリのタイトルを列挙 - Widows 10 対応


EnumWindows のコールバック関数内で,ウィンドウのタイトルを取得して,TListView に表示します.したがって,正確には「アプリの一覧」ではなく,「アプリのウィンドウの一覧」です.本サンプルで表示するウィンドウのリストは,[タスクビュー] (ショートカットキーは [Alt] + [TAB]) で表示されるウィンドウとほぼ同じになります.

起動中のアプリが UWP アプリの時は,SHGetPropertyStoreForWindow 関数で,ウィンドウハンドルから IPropertyStore インターフェイスのオブジェクトを取得します.その GetValu メソッドで AppUserModelId の値を取得するようにしています.AppUserModelId の値が分かれば,前項のサンプルで確認したように,項目識別子からアイコン画像が取得できます.AppUserModelId の値は,デスクトップアプリにおける EXE ファイル名に相当します.

UWP アプリでない時は,QueryFullProcessImageName 関数でプロセスハンドルを取得し,そのプロセスハンドルの値から QueryFullProcessImageNameW 関数を使用して EXE のフルパスを取得しています.このフルパスからアイコン画像を取得します.

アイコンはどちらも SHGetFileInfo 関数を使用して,システムのイメージリストから取得しています.



アプリのウィンドウを閉じるのに WM_CLOSE メッセージを使用しています.アプリケーションの状態によっては終了しないことがあるかも知れません.その時は他の方法を検討してください.


図23
起動中のアプリのタイトルを列挙
  • [起動中のアプリ] となっているが,正確には [表示中のウィンドウ]
  • [選択アプリ終了] は,正確には [選択ウィンドウを閉じる]

リスト7
起動中のアプリのタイトルを列挙 - Widows 10 対応
unit Unit1;

interface

uses
  Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes,
  Vcl.Graphics,Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.StdCtrls, Vcl.ExtCtrls,
   Winapi.ShellAPI, Winapi.ActiveX, Winapi.ShlObj, Winapi.CommCtrl, Vcl.ImgList,
   Vcl.ComCtrls;

type
  TForm1 = class(TForm)
    Button1: TButton;
    Button2: TButton;
    ListView1: TListView;
    ImageList1: TImageList;
    procedure Button1Click(Sender: TObject);
    procedure Button2Click(Sender: TObject);
    procedure FormCreate(Sender: TObject);
    procedure ListView1Resize(Sender: TObject);
  private
    { Private 宣言 }
  public
    { Public 宣言 }
  end;

  function QueryFullProcessImageNameW(Process: THandle; Flags: DWORD; Buffer: PChar;
    Size: PDWORD): DWORD; stdcall; external 'kernel32.dll';
  function EnumWindowProc(hWindow: HWND; lPara: TListView): Boolean; stdcall;
  function GetWindowTitle(hWindow: HWND): string;
  function GetWindowClassName(hWindow: HWND): string;

var
  Form1: TForm1;

implementation

uses
  Winapi.Dwmapi,
  Winapi.propSys,
  Winapi.PropKey;

{$R *.DFM}

//=============================================================================
//  フォーム生成時の処理
//  一覧表示用の TListView の設定
//=============================================================================
procedure TForm1.FormCreate(Sender: TObject);
begin
  ImageList1.BkColor    := ListView1.Color;
  ListView1.ViewStyle   := vsReport;
  ListView1.SmallImages := ImageList1;

  ListView1.Columns.Add;
  ListView1.Columns[0].Caption :=' アプリケーション';
  ListView1.Columns[0].Width   := ListView1.ClientWidth;
end;

//=============================================================================
//  起動中のウィンドウのリストを作成
//=============================================================================
procedure TForm1.Button1Click(Sender: TObject);
begin
  ListView1.Items.Clear;
  ListView1.Items.BeginUpdate;
  ImageList1.Clear;

  EnumWindows(@EnumWindowProc, LPARAM(ListView1));
  ListView1.Items.EndUpdate;
  ListView1Resize(nil);
end;

//=============================================================================
//  選択中のアプリのウィンドウを閉じる
//=============================================================================
procedure TForm1.Button2Click(Sender: TObject);
var
  LTargetWnd : HWND;
  LIndex     : Integer;
begin
  if ListView1.ItemFocused = nil then exit;

  LIndex  := ListView1.ItemFocused.Index;
  LTargetWnd := HWND(ListView1.Items[LIndex].Data);

  if LTargetWnd <> 0 then begin
    if LTargetWnd = Application.Handle then begin
      Close;
    end else begin
      // アプリのウィンドウを閉じる
      PostMessage(LTargetWnd, WM_CLOSE, 0, 0);
      Sleep(100);

      Button1Click(nil);
    end;
  end;
end;

//=============================================================================
//  ListView1 の幅を変更したらカラムの幅も変更
//=============================================================================
procedure TForm1.ListView1Resize(Sender: TObject);
begin
  ListView1.Columns[0].Width := ListView1.ClientWidth;
end;

//-----------------------------------------------------------------------------
//  EnumWindows のコールバック関数
//  このコールバック内で現在起動中のアプリの一覧を作成
//  必要なユニット類は uses 部の記述を参照
//-----------------------------------------------------------------------------
function EnumWindowProc(hWindow: HWND; lPara: TListView): Boolean; stdcall;
const
  UWP_FRAMEWND  = 'ApplicationFrameWindow';
  UWP_COREWND   = 'Windows.UI.Core.CoreWindow';
  PROCESS_QUERY_LIMITED_INFORMATION = $1000;
  DWMWA_CLOAKED = 14;
  CLASS_BRIDGE  = 'Windows.UI.Composition.DesktopWindowContentBridge';
var
  LIsUWPApp   : Boolean;
  LAppWindow  : HWND;
  LAppTitle   : string;
  LCloaked    : BOOL;
  LPropStore  : IPropertyStore;
  LPropVar    : TPropVariant;
  LProcessID  : DWORD;
  LhProcess   : THandle;
  LBuffer     : array [0..MAX_PATH - 1] of Char;
  LSTR_SIZE   : DWORD;
  LAppPath    : string;
  LITemIDPath : PItemIDList;
  LSHFileInfo : TSHFileInfo;
  LIcon       : TIcon;
  LItem       : TListItem;
begin
  Result := True;
  LAppWindow := hWindow;

  if GetWindow(LAppWindow, GW_OWNER) <> 0 then exit;
  if not IsWindowVisible(LAppWindow) then exit;

  // 待機中またはバックグラウンドのウィンドウは対象外
  // クラス名 Windows.UI.Core.CoreWindow のウインドウはバックグラウンド扱い
  DwmGetWindowAttribute(LAppWindow, DWMWA_CLOAKED, @LCloaked, SizeOf(LCloaked));
  if LCloaked then Exit;

  LAppTitle := GetWindowTitle(LAppWindow);
  if LAppTitle = '' then Exit;
  if SameText(LAppTitle, 'Program Manager') then Exit;
  if FindWindowEx(LAppWindow, 0, PChar(CLASS_BRIDGE), nil) <> 0 then Exit;


  // クラス名 ApplicationFrameWindow のウインドウは UWP アプリ
  LIsUWPApp := GetWindowClassName(LAppWindow) = UWP_FRAMEWND;

  if LIsUWPApp then begin
    // AppUserModelId の値を取得
    if SHGetPropertyStoreForWindow(LAppWindow,
                                IID_IPropertyStore,
                                Pointer(LPropStore)) <> S_OK then Exit;
    if LPropStore.GetValue(PKEY_AppUserModel_ID, LPropVar) <> S_OK then Exit;
    LAppPath := LPropVar.bstrVal;
    LAppPath := 'shell:AppsFolder\' + LAppPath;
  end else begin
    // プロセスIDを取得
    // その値からプロセスのオープンハンドル取得
    GetWindowThreadProcessId(LAppWindow, Addr(LProcessID));
    LhProcess := OpenProcess(PROCESS_QUERY_LIMITED_INFORMATION, False, LProcessID);
    try
      // EXE のフルパスを取得
      FillChar(LBuffer, SizeOf(LBuffer), #0);
      LSTR_SIZE := Length(LBuffer);
      LAppPath:='';
      if QueryFullProcessImageNameW(LhProcess,
                                    0,
                                    @LBuffer[0],
                                    @LSTR_SIZE) = 0 then Exit;
      LAppPath := LBuffer;
    finally
      CloseHandle(LhProcess);
    end;
  end;

  // ウィンドウのアイコンを取得して TListView に表示
  // TListView の Item の Data プロパティにウィンドウハンドルの値を代入
  LIcon := TIcon.Create;
  try
    if LIsUWPApp then begin
      LITemIDPath := ILCreateFromPath(PWideChar(LAppPath));
      SHGetFileInfo(Pointer(LITemIDPath),
                    0,
                    LSHFileInfo,
                    SizeOf(TSHFileInfo),
                    SHGFI_PIDL or SHGFI_ICON or SHGFI_SMALLICON);
      LIcon.Handle := LSHFILeInfo.hIcon;
      CoTaskMemFree(LITemIDPath);
    end else begin
      // ファイル名 (またはファイルの関連付け) から取得
      SHGetFileInfo(PChar(LAppPath),
                          0,
                          LSHFileInfo,
                          SizeOf(LSHFileInfo),
                          SHGFI_ICON or SHGFI_SMALLICON or SHGFI_SHELLICONSIZE);
      LIcon.Handle :=  LSHFileInfo.hIcon;
    end;

    LItem            := lPara.Items.Add;
    LItem.Caption    := GetWindowTitle(LAppWindow);
    LItem.Data       := PNativeUInt(LAppWindow);
    LItem.ImageIndex := lPara.SmallImages.AddIcon(LIcon);
  finally
    FreeAndNil(LIcon);
  end;
end;

//-----------------------------------------------------------------------------
//  ウィンドウのタイトルを取得する関数
//-----------------------------------------------------------------------------
function GetWindowTitle(hWindow: HWND): string;
var
  LBuff : array [0..MAX_PATH - 1] of Char;
begin
  Result := '';
  FillChar(LBuff, SizeOf(LBuff), #0);
  if (GetWindowText(hWindow, @LBuff, MAX_PATH) > 0) then begin
    Result := LBuff;
  end;
end;

//-----------------------------------------------------------------------------
//  ウィンドウのクラス名を取得する関数
//-----------------------------------------------------------------------------
function GetWindowClassName(hWindow: HWND): string;
var
  LBuff : array [0..MAX_PATH - 1] of Char;
begin
  Result := '';
  FillChar(LBuff, SizeOf(LBuff), #0);
  if (GetClassName(hWindow, @LBuff, MAX_PATH) > 0) then begin
    Result := LBuff;
  end;
end;

end.


UWP アプリではないアプリのアイコン画像も,次のように項目識別子から取得することができます.


    LITemIDPath := ILCreateFromPath(PWideChar(LAppPath));
    SHGetFileInfo(Pointer(LITemIDPath),
                  0,
                  LSHFileInfo,
                  SizeOf(TSHFileInfo),
                  SHGFI_PIDL or SHGFI_ICON or SHGFI_SMALLICON);
    LIcon.Handle := LSHFILeInfo.hIcon;
    CoTaskMemFree(LITemIDPath);


[備考]
Windows 10 のタスクマネージャに表示される表示は,AppUserModelId のリストの表示名,つまりアプリの名前 (アプリ名) です.本プログラムのようにインストールしていないプログラムの場合は EXE 名の表示となります.
タスクマネージャの表示と同じにするには処理が必要です.
タスクマネージャは [Ctrl] + [Shift] + [ESC] のキー操作で起動できます.




12_起動中のアプリのタイトルとフルパスを列挙 - Widows 10 対応


numWindows のコールバック関数内で,ウィンドウのタイトルと起動している EXE のフルパスを取得して,TListView に表示します.正確には「アプリの一覧」ではなく,「アプリのウィンドウの一覧」です.本サンプルで表示するウィンドウは,[Alt] + [TAB] で表示されるウィンドウとほぼ同じになります.

既に前項のサンプルで,アイコン画像を取得するために EXE のフルパスを取得しています.それを追加表示しているだけです.ただし,UWP アプリの場合は AppUserModelId の値だけを表示するようにしています.



図24
実行結果例

リスト8
起動中のアプリのタイトルとフルパスを列挙 - Widows 10 対応
unit Unit1;

interface

uses
  Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes,
  Vcl.Graphics,Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.StdCtrls, Vcl.ExtCtrls,
   Winapi.ShellAPI,  Winapi.ActiveX, Winapi.ShlObj, Winapi.CommCtrl, Vcl.ImgList,
   Vcl.ComCtrls;

type
  TForm1 = class(TForm)
    ImageList1: TImageList;
    ListView1: TListView;
    Button1: TButton;
    procedure FormCreate(Sender: TObject);
    procedure Button1Click(Sender: TObject);
    procedure FormResize(Sender: TObject);
  private
    { Private 宣言 }
  public
    { Public 宣言 }
  end;

  function QueryFullProcessImageNameW(Process: THandle; Flags: DWORD; Buffer: PChar;
    Size: PDWORD): DWORD; stdcall; external 'kernel32.dll';
  function EnumWindowProc(hWindow: HWND; lPara: TListView): Boolean; stdcall;
  function GetWindowTitle(hWindow: HWND): string;
  function GetWindowClassName(hWindow: HWND): string;

var
  Form1: TForm1;

implementation

uses
  Winapi.Dwmapi,
  Winapi.propSys,
  Winapi.PropKey,
    System.StrUtils;

{$R *.DFM}

//=============================================================================
//  フォーム生成時の処理
//  一覧表示用の TListView の設定
//=============================================================================
procedure TForm1.FormCreate(Sender: TObject);
begin
  ImageList1.BkColor    := ListView1.Color;
  ListView1.ViewStyle   := vsReport;
  ListView1.SmallImages := ImageList1;

  ListView1.Columns.Add;
  ListView1.Columns[0].Caption := ' アプリケーション';
  ListView1.Columns[0].Width   := 240;

  ListView1.Columns.Add;
  ListView1.Columns[1].Caption := ' アプリケーションのフルパス';
end;

//=============================================================================
// 起動中のウィンドウのリストを作成
//=============================================================================
procedure TForm1.Button1Click(Sender: TObject);
begin
  ListView1.Items.Clear;
  ListView1.Items.BeginUpdate;
  ImageList1.Clear;

  ImageList1.BkColor    := ListView1.Color;
  ImageList1.BlendColor := ListView1.Color;

  EnumWindows(@EnumWindowProc, LPARAM(ListView1));

  ListView1.Items.EndUpdate;
  FormResize(nil);
end;

//=============================================================================
//  フォームの幅を変更したらカラムの幅も変更
//=============================================================================
procedure TForm1.FormResize(Sender: TObject);
begin
  ListView1.Columns[1].Width := ListView1.ClientWidth - ListView1.Columns[0].Width;
end;

//-----------------------------------------------------------------------------
//  EnumWindows のコールバック関数
//  このコールバック内で現在起動中のアプリの一覧を作成
//  必要なユニット類は uses 部の記述を参照
//-----------------------------------------------------------------------------
function EnumWindowProc(hWindow: HWND; lPara: TListView): Boolean; stdcall;
const
  UWP_FRAMEWND  = 'ApplicationFrameWindow';
  UWP_COREWND   = 'Windows.UI.Core.CoreWindow';
  PROCESS_QUERY_LIMITED_INFORMATION = $1000;
  DWMWA_CLOAKED = 14;
  CLASS_BRIDGE  = 'Windows.UI.Composition.DesktopWindowContentBridge';
var
  LIsUWPApp   : Boolean;
  LAppWindow  : HWND;
  LAppTitle   : string;
  LCloaked    : BOOL;
  LPropStore  : IPropertyStore;
  LPropVar    : TPropVariant;
  LProcessID  : DWORD;
  LhProcess   : THandle;
  LBuffer     : array [0..MAX_PATH - 1] of Char;
  LSTR_SIZE   : DWORD;
  LAppPath    : string;
  LITemIDPath : PItemIDList;
  LSHFileInfo : TSHFileInfo;
  LIcon       : TIcon;
  LItem       : TListItem;
begin
  Result := True;
  LAppWindow := hWindow;

  if GetWindow(LAppWindow, GW_OWNER) <> 0 then exit;
  if not IsWindowVisible(LAppWindow) then exit;

  // 待機中またはバックグラウンドのウィンドウは対象外
  // クラス名 Windows.UI.Core.CoreWindow のウインドウはバックグラウンド扱い
  DwmGetWindowAttribute(LAppWindow, DWMWA_CLOAKED, @LCloaked, SizeOf(LCloaked));
  if LCloaked then Exit;

  LAppTitle := GetWindowTitle(LAppWindow);
  if LAppTitle = '' then Exit;
  if SameText(LAppTitle, 'Program Manager') then Exit;
  if FindWindowEx(LAppWindow, 0, PChar(CLASS_BRIDGE), nil) <> 0 then Exit;


  // クラス名 ApplicationFrameWindow のウインドウは UWP アプリ
  LIsUWPApp := GetWindowClassName(LAppWindow) = UWP_FRAMEWND;

  if LIsUWPApp then begin
    // AppUserModelId の値を取得
    if SHGetPropertyStoreForWindow(LAppWindow,
                                IID_IPropertyStore,
                                Pointer(LPropStore)) <> S_OK then Exit;
    if LPropStore.GetValue(PKEY_AppUserModel_ID, LPropVar) <> S_OK then Exit;
    LAppPath := LPropVar.bstrVal;
    LAppPath := 'shell:AppsFolder\' + LAppPath;
  end else begin
    // プロセスIDを取得
    // その値からプロセスのオープンハンドル取得
    GetWindowThreadProcessId(LAppWindow, Addr(LProcessID));
    LhProcess := OpenProcess(PROCESS_QUERY_LIMITED_INFORMATION, False, LProcessID);
    try
      // EXE のフルパスを取得
      FillChar(LBuffer, SizeOf(LBuffer), #0);
      LSTR_SIZE := Length(LBuffer);
      LAppPath:='';
      if QueryFullProcessImageNameW(LhProcess,
                                    0,
                                    @LBuffer[0],
                                    @LSTR_SIZE) = 0 then Exit;
      LAppPath := LBuffer;
    finally
      CloseHandle(LhProcess);
    end;
  end;

  // ウィンドウのアイコンを取得して TListView に表示
  LIcon := TIcon.Create;
  try
    if LIsUWPApp then begin
      LITemIDPath := ILCreateFromPath(PWideChar(LAppPath));
      SHGetFileInfo(Pointer(LITemIDPath),
                    0,
                    LSHFileInfo,
                    SizeOf(TSHFileInfo),
                    SHGFI_PIDL or SHGFI_ICON or SHGFI_SMALLICON);
      LIcon.Handle := LSHFILeInfo.hIcon;
      CoTaskMemFree(LITemIDPath);
    end else begin
      // ファイル名 (またはファイルの関連付け) から取得
      SHGetFileInfo(PChar(LAppPath),
                          0,
                          LSHFileInfo,
                          SizeOf(LSHFileInfo),
                          SHGFI_ICON or SHGFI_SMALLICON or SHGFI_SHELLICONSIZE);
      LIcon.Handle :=  LSHFileInfo.hIcon;
    end;

    LItem            := lPara.Items.Add;
    LItem.Caption    := GetWindowTitle(LAppWindow);
    LItem.ImageIndex := lPara.SmallImages.AddIcon(LIcon);
  finally
    FreeAndNil(LIcon);
  end;
  LAppPath := ReplaceText(LAppPath, 'shell:AppsFolder\', '');
  LItem.SubItems.Add(LAppPath);
end;

//-----------------------------------------------------------------------------
//  ウィンドウのタイトルを取得する関数
//-----------------------------------------------------------------------------
function GetWindowTitle(hWindow: HWND): string;
var
  LBuff : array [0..MAX_PATH - 1] of Char;
begin
  Result := '';
  FillChar(LBuff, SizeOf(LBuff), #0);
  if (GetWindowText(hWindow, @LBuff, MAX_PATH) > 0) then begin
    Result := LBuff;
  end;
end;

//-----------------------------------------------------------------------------
//  ウィンドウのクラス名を取得する関数
//-----------------------------------------------------------------------------
function GetWindowClassName(hWindow: HWND): string;
var
  LBuff : array [0..MAX_PATH - 1] of Char;
begin
  Result := '';
  FillChar(LBuff, SizeOf(LBuff), #0);
  if (GetClassName(hWindow, @LBuff, MAX_PATH) > 0) then begin
    Result := LBuff;
  end;
end;

end.




13_UWP アプリのインストール先とアイコン画像


ここまでのサンプルでは,アプリのアイコン画像をシステムイメージから取得しています.UWP アプリに限らず,アプリケーションのアイコン画像を,本ページのサンプルのような目的で使用するのであれば,サンプルコード内のアイコン画像取得の方法で十分と思われます.
以下は,純粋に「UWP アプリのアイコンはどこにあるのか ?」という疑問に対する答になるかも知れない記事です.

UWP アプリ (Microsoft Store アプリ - マイクロソフト ストア アプリ) は,拡張子が Appx のファイルで提供されます.これが UWP アプリのパッケージです.Appx パッケージとも言います.
Appx パッケージをインストールすると,その情報が以下のレジストリの階層下に書き込まれます.レジストリのキー名がパッケージ名です.

キー名をフルパッケージ名,あるいはパッケージフル名と言い,バージョン部分を除いた名前をパッケージ名,あるいはパッケージファミリー名と言うこともあります.


HKEY_CLASSES_ROOT\Local Settings\Software\Microsoft\Windows\CurrentVersion\AppModel\Repository\Packages

現在のユーザがインストールしている UWP アプリの一覧はレジストリの以下のキーにあります.各パッケージ名のキーの階層下には何も情報はありません.パッケージ名だけとなっています.


HKEY_CURRENT_USER\Software\Classes\ActivatableClasses\Package



図25
レジストリのキー名

パッケージは主に以下のどちらかのフォルダにインストールされます.C*\Windows\SystemApps にインストールされた UWP アプリはアンインストールできません.このフォルダには、「Microsoft Edge」,「Cortana」(コルタナ),「Windows セキュリティ」(Defender) 等がインストールされています.

一般的な UWP アプリは WindowsApps フォルダにインストールされます.デスクトップアプリはインストール先を設定できますが,UWP アプリはインストール先を変更することはできません.WindowsApps フォルダは隠し属性のフォルダです.WindowsApps フォルダは,アクセス権を変更しないと開くことができません.


C:\Program Files\WindowsApps
C*\Windows\SystemApps


レジストリのパッケージ名のキーに,名前が PackageRootFolder の項目がありjます.これがパッケージのインストール先のフルパスです.筆者の環境で,この記事を書いている時点では,「フォト 3D」アプリが以下の値になっていました.
この文字列を,エクスプローラのアドレス欄に貼り付けて実行した結果が下図です.WindowsApps フォルダを直接開くことはできませんが,パッケージ名を指定すればそのパッケージを開けます.ただし,変更を伴う操作はできません.必要であれば,フォルダあるいはファイルを別の場所にコピーします.


C:\Program Files\WindowsApps\Microsoft.Windows.Photos_2021.21070.22007.0_x64__8wekyb3d8bbwe


図26
「フォト」のパッケージ

パッケージ内に Assets というフォルダがあります.そのフォルダを表示した結果が下図です.「フォト」のアイコン画像があります.画像の形式は全て PNG です.筆者の環境では 182 個あります.これが「UWP アプリのアイコンはどこにあるのか ?」という疑問に対する答ということになります.UWP アプリのアイコン画像は,インストール先のパッケージのフォルダ内にあります.それらの画像は,上記の手順で確認できます.

アイコンの画像は必ずしも Assets フォルダ直下にあるとは限りません.UWP アプリによっては,種類ごとに別のフォルダ内に置かれていることもあります.どの場面でどのアイコン画像を使用するのかという情報は,AppxManifest.xml に記述されています.



図27
「フォト」のアイコン画像

以下の画像ファイルを読み込んで TImage に描画してみました.
レジストリのキー名はバージョン番号を含むフルパッケージ名です.これを検索すると,バージョンアップによってフルパッケージ名が変更されるとアクセスできなくなります.そこで,以下のサンプルでは,バージョン部分を除いたファミリー名の文字列があるキー名を調べて,そのキー名をパッケージ名としています.


Assets\PhotosAppList.targetsize-256_altform-lightunplated.png


図28
「フォト」のアイコン画像を描画

リスト9
UWP アプリのパッケージからアイコン画像を取得
unit Unit1;

interface

uses
  Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes,
  Vcl.Graphics, Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.ExtCtrls, Vcl.StdCtrls;

type
  TForm1 = class(TForm)
    Button1: TButton;
    Image1: TImage;
    procedure Button1Click(Sender: TObject);
  private
    { Private 宣言 }
  public
    { Public 宣言 }
  end;

var
  Form1: TForm1;

implementation

uses
  System.Win.Registry,
  System.StrUtils,
  System.RegularExpressions,
  Vcl.Imaging.pngimage;

{$R *.DFM}

//=============================================================================
//  UWP アプリのパッケージのフォルダから直接アイコン画像を読み込む
//
//  (1) レジストリからパッケージのキー名のリストを取得
//  (2) フォトのパッケージのファミリー名の値を含む文字列があるキー名を探す
//  (3) 項目 PackageRootFolder の値を取得.これがインストール先のフォルダ
//  (4) インストール先のアイコン画像を読み込んで TImage に描画
//============================================================================
procedure TForm1.Button1Click(Sender: TObject);
var
  LAppId      : string;
  LRegistry   : TRegistry;
  LKeyPkgRoot : string;
  LKeyPkg     : string;
  LKeyList    : TStringList;
  LVersion    : string;
  LPkgPath    : string;
  LImgPath    : string;
  LIndex      : Integer;
begin
  Image1.Picture.Assign(nil);

  // アプリケーション識別子 AppUserModelId の値
  LAppId := 'Microsoft.Windows.Photos_8wekyb3d8bbwe!App';

  LRegistry := TRegistry.Create;
  try
    LRegistry.Access  := KEY_READ;
    LRegistry.RootKey := HKEY_CLASSES_ROOT;
    LKeyPkgRoot := 'Local Settings\Software\Microsoft\Windows\CurrentVersion'
                 + '\AppModel\Repository\Packages';
    LKeyList := TStringList.Create;
    try
      if not LRegistry.OpenKey(LKeyPkgRoot, False) then Exit;

      LRegistry.GetKeyNames(LKeyList);
      LRegistry.CloseKey;

      for LIndex := 0 to LKeyList.Count - 1 do begin
        // バージョン文字列を削除して検索
        LPkgPath := TRegEx.Replace(LKeyList[LIndex], '_(.+)_', '_');
        if not ContainsText(LAppId, LPkgPath) then Continue;

        LPkgPath := '';
        LKeyPkg := LKeyPkgRoot + PathDelim + LKeyList[LIndex];
        if LRegistry.OpenKey(LKeyPkg, False) then begin
          LPkgPath := LRegistry.ReadString('PackageRootFolder');
          LRegistry.CloseKey;
          if LPkgPath <> '' then Break;
        end;
      end;
    finally
      FreeAndNil(LKeyList);
    end;
  finally
    FreeAndNil(LRegistry);
  end;
  if LPkgPath = '' then Exit;


  LImgPath := 'Assets\PhotosAppList.targetsize-256_altform-lightunplated.png';
  Image1.Picture.LoadFromFile(LPkgPath + PathDelim + LImgPath);
end;

end.


以下の,UWP アプリのパッケージの,インストール先のフルパスを取得して一覧表示するサンプルを添付しておきました.



図29
実行結果

リスト10
UWP アプリのパッケージの,インストール先のフルパスを一覧表示
unit Unit1;

interface

uses
  Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes,
  Vcl.Graphics, Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.ExtCtrls, Vcl.StdCtrls,
  Vcl.ComCtrls;

type
  TForm1 = class(TForm)
    Button1: TButton;
    Button2: TButton;
    ListView1: TListView;
    procedure Button1Click(Sender: TObject);
    procedure Button2Click(Sender: TObject);
    procedure FormCreate(Sender: TObject);
    procedure FormResize(Sender: TObject);
    procedure ListView1DblClick(Sender: TObject);
  private
    { Private 宣言 }
  public
    { Public 宣言 }
  end;

var
  Form1: TForm1;

implementation

uses
  System.Win.Registry,
  Winapi.ShellAPI;

{$R *.DFM}

//=============================================================================
//  フォーム生成時の処理
//=============================================================================
procedure TForm1.FormCreate(Sender: TObject);
begin
  ListView1.Columns.Add;
  ListView1.ShowColumnHeaders := False;
end;

//=============================================================================
//  UWP アプリのパッケージ名のリストをレジストリから取得
//  項目 PackageRootFolder の値があるキー名だけをを取得してその値をリスト
//=============================================================================
procedure TForm1.Button1Click(Sender: TObject);
var
  LRegistry   : TRegistry;
  LKeyPkgRoot : string;
  LKeyPkg     : string;
  LKeyList    : TStringList;
  LPkgPath    : string;
  LIndex      : Integer;
begin
  ListView1.Items.Clear;
  ListView1.Items.BeginUpdate;

  LRegistry := TRegistry.Create;
  try
    LRegistry.Access  := KEY_READ;
    LRegistry.RootKey := HKEY_CLASSES_ROOT;
    LKeyPkgRoot := 'Local Settings\Software\Microsoft\Windows\CurrentVersion'
                 + '\AppModel\Repository\Packages';
    LKeyList := TStringList.Create;
    try
      if not LRegistry.OpenKey(LKeyPkgRoot, False) then Exit;

      LRegistry.GetKeyNames(LKeyList);
      LRegistry.CloseKey;

      for LIndex := 0 to LKeyList.Count - 1 do begin
        LKeyPkg := LKeyPkgRoot + PathDelim + LKeyList[LIndex];
        if LRegistry.OpenKey(LKeyPkg, False) then begin
          LPkgPath := LRegistry.ReadString('PackageRootFolder');
          if LPkgPath <> '' then begin
            ListView1.Items.Add.Caption := LPkgPath;
          end;
        end;
        LRegistry.CloseKey;
      end;
    finally
      FreeAndNil(LKeyList);
    end;
  finally
    FreeAndNil(LRegistry);
  end;

  ListView1.SortType := stText;
  ListView1.Items.EndUpdate;
  FormResize(nil);
end;

//=============================================================================
//  パッケージをエクスプローラで表示
//=============================================================================
procedure TForm1.Button2Click(Sender: TObject);
var
  LListItem   : TListItem;
  LFolderPath : string;
begin
  LListItem := ListView1.Selected;
  if LListItem = nil then Exit;

  LFolderPath := LListItem.Caption;
  ShellExecute(0, '', PChar(LFolderPath), nil, nil, SW_SHOW);
end;

//=============================================================================
//  ListView1 の OnDblClick イベント
//  パッケージをエクスプローラで表示
//=============================================================================
procedure TForm1.ListView1DblClick(Sender: TObject);
begin
  Button2Click(nil);
end;

//=============================================================================
//  このフォームの OnFormResize イベント
//  フォームのサイズを変更したら TListView の幅を調整
//=============================================================================
procedure TForm1.FormResize(Sender: TObject);
begin
  ListView1.Columns[0].Width := ListView1.ClientWidth;
end;

end.


[備考]
UWP アプリは,Windows のランタイムライブラリである WinRT を使用する COM の拡張オブジェクトとしての位置づけとなっています.Delphi 10.2 では,WinRT の機能を利用するための Winapi.WinRT, System.win.WinRT, Winapi.Globalization 等のユニット類が実装されています.それらのユニットは,主に source\rtl\win\winrt 内にあります.