Delphi Programming / Object Pascal
サイトのトップページへ リンクのページへ ロゴマーク
[掲載 2005年12月07日] [更新 2018年03月03日] Delphi サンプルプログラム集
330_起動中のプロセス一覧と強制終了
動作確認等 Windows 7 U64(SP1) + Delphi XE Pro
Windows Vista 以降でないと動作しないサンプルあり
330_AppProcessList.zip [3,987 KB] 2018年03月03日版 (EXE 同梱)



  • 2013年07月06日
  • 機能別のサンプルにした
  • Windows 7 U64(SP1) + Delphi XE Pro で動作確認
  • その環境に合わせて記事も変更。解説的な内容も追加
  • 2018年03月03日
  • Privilege.pas をプロジェクトから削除して、このユニットをライブラリのパスが通った場所に置いて使用する方式に変更




起動中のプロセス一覧の取得と強制終了


アプリケーションが起動したプロセスの一覧を取得するサンプルです。
正確には、プロセスを生成したアプリケーションの実行ファイル名を取得します。複数のプロセスを生成するアプリケーションもあります。それらの全てを取得して TListView に表示します。次の 2 つの方法をテストしています。
  • EnumProcesses 関数を使用する方法
  • Toolhelp32Snapshot ( TProcessEntry32 ) を使用する方法
起動したアプリの実行ファイルのパス名の取得には、次の関数でテストしています。
  • GetModuleFileNameEx 関数
  • QueryFullProcessImageName 関数
プロセスの列挙用の 2 つの関数、パス名取得用の関数も、どちらも Windows XP 以降で使用可能ですが、QueryFullProcessImageName 関数は Vista 以降でないと使用できません。また、32 ビットのアプリケーションで GetModuleFileNameEx 関数を使用した場合、64 ビットのアプリケーションが生成したプロセスを起動した実行パス名は検出できません。

これらの関数を使用して、パス名を取得するには、プロセスの情報取得のために、プロセス ID (PID) の値を指定して OpenProcess 関数を実行します。この時、SeDebugPrivilege 特権を有効にする必要があります。有効にして、管理者権限で実行しないと、特にシステム関係のプログラムで OpenProcess 関数の実行に失敗します。そこで、本ページのサンプルでは、DEKO さんのサイトにあるコードをユニットにして利用しています。このユニットをライブラリのパスが通った場所に置きます。
サンプルでは、このことを確認するために、パス名が取得できなかった場合、空文字を TListView に表示するようにしています。

このデバッグ特権のことは、英語版の MSDN オンラインの OpenProcess の説明記事には書いてありますが、日本語版の OpenProcess 関数の説明にはありません。記事の新旧については不明です。





01_EnumProcesses 関数によるプロセスの列挙


起動中のプロセスの一覧は EnumProcesses 関数を使用すると取得できます。
EnumProcesses 関数では、あらかじめ、プロセス ID の値を取得する領域を確保しておきます。プロセス ID (PID) の値は DWORD (4 バイト) 型です。[この型のサイズ × プロセスの数] の分必要です。
プロセスの数をあらかじめ知ることができないので、以下のサンプルでは、2048 / 4 = 512 に設定しています。

取得したプロセス ID の値を使用して、OpenProcess の第 1 引数に PROCESS_QUERY_INFORMATION or PROCESS_VM_READ を指定してプロセスのオープンハンドル値を取得し、それを元に、そのプロセスを起動した実行ファイルのパス名を取得します。





02_パス名を GetModuleFileNameEx 関数で取得


EnumProcesses 関数を使用するサンプルです。
パス名の取得に GetModuleFileNameEx 関数を使用しています。
下図は、IDE でコンパイルして実行した結果ですが、一部のパスが取得できていません。これは 64 ビットのプロセス関係です。


図1
設計時画面
  • TListView のカラムを 2 つ作成しておく
図2
IDE 上でコンパイルして実行
  • 64 ビットのプロセス名は取得できない
  • 例えば 64 ビット版のメモ帳のプロセス名は取得できない
  • 32 ビット版のメモ帳のプロセス名は取得できる

同じコードを、Delphi XE3 で [ターゲットプラットフォーム] を [64 ビット Windows] にして 64 ビットの EXE を作成して、管理者権限で実行した結果が下図です。
64 ビットのプロセスも取得できていています。


図3
64 ビットの EXE を実行した結果
  • 管理者権限で実行した場合

同じ 64 ビットの EXE を管理者に昇格しないで実行した結果です。やはり 64 ビットのプロセス関係が取得できません。


図4
64 ビットの EXE を実行した結果
  • 管理者に昇格しないで実行した場合

リスト1
パス名を GetModuleFileNameEx 関数で取得
unit Unit1;
interface

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


type
  TForm1 = class(TForm)
    Panel1: TPanel;
    Label1: TLabel;
    Button1: TButton;
    ListView1: TListView;
    procedure Button1Click(Sender: TObject);
  private
    { Private 宣言 }
    function ChangeShortToLongPath(ShortName: String): String;
  public
    { Public 宣言 }
  end;

var
  Form1: TForm1;

implementation

uses Privilege;

{$R *.DFM}

//=============================================================================
//  現在起動中のプロセスの一覧を作成
//  EnumProcesses関数を使用して、パス名の取得にGetModuleFileNameExを使用
//
//  EnumProcessesの使用には、usesにPsAPIが必要
//  GetModuleFileNameExkの使用には、usesにPsAPIが必要
//=============================================================================
procedure TForm1.Button1Click(Sender: TObject);
const
  MAX_PROCCOUNT = 2048;

var
  ProcIDs     : array[0..MAX_PROCCOUNT -1] of DWORD;
  cbNeeded    : DWORD;
  ProcessCnt  : DWORD;
  i           : Integer;
  hProcHandle : Cardinal;
  ExePath     : String;
  Buff        : array[0..MAX_PATH-1] of Char;
  ListItem    : TListItem;
begin
  //デバッグの特権を有効にする
  Privilege.SetPrivilege(SE_DEBUG_NAME, True);

  ListView1.Items.Clear;
  ListView1.Items.BeginUpdate;

  //プロセスのIDのリストを取得
  if not EnumProcesses(@ProcIDs, SizeOf(ProcIDs), cbNeeded) then begin
    ListView1.Items.EndUpdate;
    exit;
  end;

  //実際のプロセスの数を計算
  ProcessCnt := cbNeeded div SizeOf(DWORD);

  for i := 0 to ProcessCnt -1 do begin
    ExePath := '';
    FillChar(Buff, SizeOf(Buff), #0);

    //プロセスID値からプロセスのオープンハンドルを取得
    hProcHandle := OpenProcess(PROCESS_QUERY_INFORMATION or PROCESS_VM_READ,
                                 False,
                                 ProcIDs[i]);
    try
      //オープンハンドルからパス名を取得
      if hProcHandle > 0 then begin
        if GetModuleFileNameEx(hProcHandle,
                               0,
                               @Buff,
                               Length(Buff)) > 0 then begin
          ExePath := Buff;
          //'\??\'という文字列が含まれて場合がある(原因は不明)。これを削除し
          //短いパス名を長いパス名に変換
          ExePath := StringReplace(ExePath, '\??\', '', [rfReplaceAll]);
          ExePath := ChangeShortToLongPath(ExePath);
        end;
      end;

    finally
      CloseHandle(hProcHandle);
    end;

    //ListView1にパス名とプロセスのハンドル値を表示
    ListItem :=  ListView1.Items.Add;
    ListItem.Caption := ExePath;
    ListItem.SubItems.Add(IntToStr(ProcIDs[i]));
  end;

  ListView1.Items.EndUpdate;
  Label1.Caption := 'プロセス数  ' + IntToStr(ProcessCnt);
end;

//=============================================================================
//  短いパス名を長いパス名に変換
//  GetModuleFileNameExで取得したファイル名には、短いファイルの場合がある
//=============================================================================
function TForm1.ChangeShortToLongPath(ShortName: String): String;
var
  Buff : array[0..MAX_PATH-1] of Char;
begin
  if (GetLongPathName(PChar(ShortName), Buff, Length(Buff))) > 0 then begin
    Result := Buff;
  end else begin
    Result := ShortName;
  end;
end;

end.




03_パス名を QueryFullProcessImageName 関数で取得


同じく EnumProcesses 関数を使用するサンプルです。
パス名の取得に QueryFullProcessImageName 関数を使用すると 32 ビット、64 ビット両方のパス名が取得できます。ただし、この関数は Windows Vista 以降でないと使用できません。
この関数を使用する時は、OpenProcess の第 1 引数に PROCESS_QUERY_LIMITED_INFORMATION を指定するといいようです。

下図は、32 ビットのEXE を実行した結果ですが、64 ビットのプロセス関係も取得できていることが分かります。EXE は管理者権限で実行します。


図5
実行結果
  • 32 ビットの EXE からでも 64 ビットのプロセスのパス名が取得できる
  • EXE は管理者権限で実行

リスト2
パス名を QueryFullProcessImageName 関数で取得
unit Unit1;
interface

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


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

function QueryFullProcessImageNameW(Process: THandle; Flags: DWORD; Buffer: PChar;
  Size: PDWORD): Boolean; stdcall; external 'kernel32.dll';

var
  Form1: TForm1;

implementation

uses Privilege;

{$R *.DFM}

//=============================================================================
//  現在起動中のプロセスの一覧を作成
//  EnumProcesses関数を使用して、パス名の取得にQueryFullProcessImageNameWを使用
//
//  QueryFullProcessImageNameWはDelphiに定義がないので定義が必要
//  EnumProcessesの使用には、usesにPsAPIが必要
//  GetModuleFileNameExkの使用には、usesにPsAPIが必要
//=============================================================================
procedure TForm1.Button1Click(Sender: TObject);
const
  MAX_PROCCOUNT = 2048;
  PROCESS_QUERY_LIMITED_INFORMATION = $1000;
  PROCESS_NAME_NATIVE = 1;

var
  ProcIDs     : array[0..MAX_PROCCOUNT -1] of DWORD;
  cbNeeded    : DWORD;
  ProcessCnt  : DWORD;
  i           : Integer;
  hProcHandle : THandle;
  ExePath     : String;
  Buff        : array[0..MAX_PATH-1] of Char;
  STR_SIZE    : DWORD;
  ListItem    : TListItem;
begin
  //デバッグの特権を有効にする
  Privilege.SetPrivilege(SE_DEBUG_NAME, True);

  ListView1.Items.Clear;
  ListView1.Items.BeginUpdate;

  //プロセスのIDを取得
  if not EnumProcesses(@ProcIDs, SizeOf(ProcIDs), cbNeeded) then begin
    ListView1.Items.EndUpdate;
    exit;
  end;

  //実際のプロセスの数を計算
  ProcessCnt := cbNeeded div SizeOf(DWORD);

  for i := 0 to ProcessCnt -1 do begin
    ExePath := '';
    FillChar(Buff, SizeOf(Buff), #0);

    //プロセスID値からプロセスのオープンハンドルを取得
    hProcHandle := OpenProcess(PROCESS_QUERY_LIMITED_INFORMATION,
                               False,
                               ProcIDs[i]);

    try
      //オープンハンドルからパス名を取得
      //QueryFullProcessImageNameWの第2引数を1にするとデバイスのパス文字列
      if hProcHandle > 0 then begin
        STR_SIZE := Length(Buff);
        if QueryFullProcessImageNameW(hProcHandle, 0, @Buff, @STR_SIZE) then begin
          ExePath := String(Buff);
        end;
      end;

    finally
      CloseHandle(hProcHandle);
    end;

    //ListView1にパス名とプロセスID値を表示
    ListItem :=  ListView1.Items.Add;
    ListItem.Caption := ExePath;
    ListItem.SubItems.Add(IntToStr(ProcIDs[i]));
  end;

  ListView1.Items.EndUpdate;
  Label1.Caption := 'プロセス数  ' + IntToStr(ProcessCnt);
end;

end.




04_パス名の取得を Vista 以降とそれより前で使い分ける


上のコードで使用している QueryFullProcessImageName 関数 は Windows Vista 以降でないと使用できません。Windows XP でプロセスの一覧を取得するには、次のように Windows のバージョンによって分岐処理が必要となるでしょう。

Windows XP でフルパスを取得するには、GetModuleFileNameEx 関数を使用することになりますが、既に確認したように、GetModuleFilNameEx 関数を使用した場合、32 ビットの EXE からは 64 ビットのプロセスのパス名が検出できないことに注意してください。

リスト3
パス名の取得を Vista 以降とそれより前で使い分ける

//=============================================================================
//  QueryFullProcessImageNameWはDelphi XEに定義がないので定義が必要
//  現在起動中のプロセスの一覧を作成
//  EnumProcesses関数を使用
//  Windows Vistaの時は、パス名の取得にQueryFullProcessImageNameWを使用
//  Windows Vistaより前では、パス名の取得にGetModuleFileNameExを使用
//
//  EnumProcessesの使用には、usesにPsAPIが必要
//  GetModuleFileNameExkの使用には、usesにPsAPIが必要
//=============================================================================
procedure TForm1.Button1Click(Sender: TObject);
const
  MAX_PROCCOUNT = 2048;
  PROCESS_QUERY_LIMITED_INFORMATION = $1000;
  PROCESS_NAME_NATIVE = 1;

var
  PathFunc    : function (Process: THandle; Flags: DWORD; Buffer: PChar;
                  Size: PDWORD): Boolean stdcall;

  ProcIDs     : array[0..MAX_PROCCOUNT -1] of DWORD;
  cbNeeded    : DWORD;
  ProcessCnt  : DWORD;
  i           : Integer;
  hProcHandle : THandle;
  ExePath     : String;
  Buff        : array[0..MAX_PATH-1] of Char;
  STR_SIZE    : DWORD;
  ListItem    : TListItem;
begin
  //デバッグの特権を有効にする
  Privilege.SetPrivilege(SE_DEBUG_NAME, True);

  ListView1.Items.Clear;
  ListView1.Items.BeginUpdate;

  //プロセスのIDを取得
  if not EnumProcesses(@ProcIDs, SizeOf(ProcIDs), cbNeeded) then begin
    ListView1.Items.EndUpdate;
    exit;
  end;

  //実際のプロセスの数を計算
  ProcessCnt := cbNeeded div SizeOf(DWORD);

  for i := 0 to ProcessCnt -1 do begin
    ExePath := '';
    FillChar(Buff, SizeOf(Buff), #0);

    //Vista以降
    if CheckWin32Version(6, 0) then begin
      @PathFunc := GetProcAddress(GetModuleHandle('Kernel32.dll'),
                                  'QueryFullProcessImageNameW');
      //プロセスID値からプロセスのオープンハンドルを取得
      hProcHandle := OpenProcess(PROCESS_QUERY_LIMITED_INFORMATION,
                                 False,
                                 ProcIDs[i]);

      try
        //オープンハンドルからパス名を取得
        //QueryFullProcessImageNameWの第2引数を1にするとデバイスのパス文字列
        if hProcHandle > 0 then begin
          STR_SIZE := Length(Buff);
          if PathFunc(hProcHandle, 0, @Buff, @STR_SIZE) then begin
            ExePath := String(Buff);
          end;
        end;
      finally
        CloseHandle(hProcHandle);
      end;
    //Windows XP
    end else begin
      //プロセスID値からプロセスのオープンハンドルを取得
      hProcHandle := OpenProcess(PROCESS_QUERY_INFORMATION or PROCESS_VM_READ,
                                 False,
                                 ProcIDs[i]);
      try
        //オープンハンドルからパス名を取得
        if hProcHandle > 0 then begin
          if GetModuleFileNameEx(hProcHandle,
                                 0,
                                 @Buff,
                                 Length(Buff)) > 0 then begin
            ExePath := Buff;
            //'\??\'という文字列が含まれて場合がある(原因は不明)。これを削除し
            ExePath := StringReplace(ExePath, '\??\', '', [rfReplaceAll]);
          end;
        end;
      finally
        CloseHandle(hProcHandle);
      end;
    end;
    //短いパス名を長いパス名に変換
    if ExePath <> '' then ExePath := ChangeShortToLongPath(ExePath);

    //ListView1にパス名とプロセスのID値を表示
    ListItem :=  ListView1.Items.Add;
    ListItem.Caption := ExePath;
    ListItem.SubItems.Add(IntToStr(ProcIDs[i]));
  end;

  ListView1.Items.EndUpdate;
  Label1.Caption := 'プロセス数  ' + IntToStr(ProcessCnt);
end;




05_Toolhelp32Snapshot を使用したプロセスの列挙


ツールヘルプライブラリというのがあります。このライブラリが提供している関数を使用すると、実行中のアプリケーションの情報を取得することができます。
この中に、CreateToolhelp32Snapshot という関数があります。この関数を使用したサンプルです。

この関数を使用した場合、まず最初のプロセスの情報を取得し、次のプロセスの情報を順番に取得していきます。取得できるプロセスがなくなった時点で終了となります。TProcessEntry32 レコード型の変数にプロセスの情報が格納されます。
このレコード型の szExeFile メンバーの値がパス名ですが、結果を見ると、ファイル名しか取得できていません。ディレクトリ名がありません。

このサンプルは、非ユニコード版 (Ansi 版) の バージョンの Delphi でコンパイルすれば、Windows 98, 2000 でも動作します。Windows 98 では、プロセスを起動したアプリの実行ファイル名がフルパスで取得できます。

CreateToolhelp32Snapshot は Windows NT では動作しません。


図6
設計時画面
  • TListView のカラムを 2 つ作成しておく
図7
実行結果
  • 管理者権限で実行
  • ディレクトリ名は取得できない

リスト4
Toolhelp32Snapshot を使用したプロセスの列挙
unit Unit1;
interface

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


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

var
  Form1: TForm1;

implementation

uses Privilege;

{$R *.DFM}

//=============================================================================
//  現在起動中のプロセスの一覧を作成
//  Toolhelp32Snapshotを使用する方法
//
//  CreateToolhelp32Snapshotの使用には、usesにTlhelp32が必要
//=============================================================================
procedure TForm1.Button1Click(Sender: TObject);
const
  MAX_PROCCOUNT = 2048;
  PROCESS_QUERY_LIMITED_INFORMATION = $1000;
  PROCESS_NAME_NATIVE = 1;

var
  ListHandle  : Cardinal;
  ProcEntry   : TProcessEntry32;
  ProcessID   : DWORD;
  ProcessCnt  : DWORD;
  ExePath     : String;
  ListItem    : TListItem;
begin
  //デバッグの特権を有効にする
  Privilege.SetPrivilege(SE_DEBUG_NAME, True);

  ListView1.Items.Clear;
  ListView1.Items.BeginUpdate;

  ProcessCnt := 0;

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

        //ListView1にパス名とプロセスID値を表示
        ListItem :=  ListView1.Items.Add;
        ListItem.Caption := ExePath;
        ListItem.SubItems.Add(IntToStr(ProcessID));

        ProcessCnt := ProcessCnt + 1;

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

  ListView1.Items.EndUpdate;
  Label1.Caption := 'プロセス数  ' + IntToStr(ProcessCnt);
end;

end.




06_Toolhelp32Snapshot を使用したプロセスの列挙でフルパスを取得


MSDN オンラインの TProcessEntry32 構造体の説明によると、フルパス取得には、EnumProcesses 関数のサンプルでも使用した QueryFullProcessImageName 関数を使用するように書いてあります。そこで、この関数を使用したのが次のコードです。
プロセスを起動したアプリケーションのフルパスが取得できています。

CreateToolhelp32Snapshot は Windows NT では動作しません。Windows Vista 以降で使用するのであれば、このコードということになりそうです。


図8
実行結果
  • 管理者権限で実行

リスト5
Toolhelp32Snapshot を使用したプロセスの列挙でフルパスを取得
unit Unit1;
interface

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


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

function QueryFullProcessImageNameW(Process: THandle; Flags: DWORD; Buffer: PChar;
  Size: PDWORD): Boolean; stdcall; external 'kernel32.dll';

var
  Form1: TForm1;

implementation

uses Privilege;

{$R *.DFM}

//=============================================================================
//  現在起動中のプロセスの一覧を作成
//  Toolhelp32Snapshotを使用してフルパスを取得
//
//  QueryFullProcessImageNameWはDelphiに定義がないので定義が必要
//  CreateToolhelp32Snapshotの使用には、usesにTlhelp32が必要
//=============================================================================
procedure TForm1.Button1Click(Sender: TObject);
const
  PROCESS_QUERY_LIMITED_INFORMATION = $1000;
  PROCESS_NAME_NATIVE = 1;

var
  ListHandle  : Cardinal;
  ProcEntry   : TProcessEntry32;
  ProcessID   : DWORD;
  hProcHandle : THandle;
  ProcessCnt  : DWORD;
  ExePath     : String;
  Buff        : array[0..MAX_PATH-1] of Char;
  STR_SIZE    : DWORD;
  ListItem    : TListItem;
begin
  //デバッグの特権を有効にする
  Privilege.SetPrivilege(SE_DEBUG_NAME, True);

  ListView1.Items.Clear;
  ListView1.Items.BeginUpdate;

  ProcessCnt := 0;

  //プロセスのスナップショットのハンドルを取得
  ListHandle := CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
  if ListHandle > 0 then begin
    try
      //最初のプロセスに関する情報をTProcessEntry32レコード型に取得
      ProcEntry.dwSize := SizeOf(TProcessEntry32);
      Process32First(ListHandle, ProcEntry);
      repeat
        ExePath := '';
        FillChar(Buff, SizeOf(Buff), #0);

        //プロセスIDを取得
        ProcessID := ProcEntry.th32ProcessID;
        //プロセスID値からプロセスのオープンハンドルを取得
        hProcHandle := OpenProcess(PROCESS_QUERY_LIMITED_INFORMATION,
                                   False,
                                   ProcessID);
        try
          //オープンハンドルからパス名を取得
          if hProcHandle > 0 then begin
            //オープンハンドルからパス名を取得
            //QueryFullProcessImageNameWの第2引数を1にするとデバイスのパス文字列
            STR_SIZE := Length(Buff);
            if QueryFullProcessImageNameW(hProcHandle,
                                          0,
                                          @Buff,
                                          @STR_SIZE) then begin
              ExePath := String(Buff);
            end;
          end;

        finally
          CloseHandle(hProcHandle);
        end;

        //ListView1にパス名とプロセスID値を表示
        ListItem :=  ListView1.Items.Add;
        ListItem.Caption := ExePath;
        ListItem.SubItems.Add(IntToStr(ProcessID));

        ProcessCnt := ProcessCnt + 1;

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

  ListView1.Items.EndUpdate;
  Label1.Caption := 'プロセス数  ' + IntToStr(ProcessCnt);
end;

end.




07_指定したプロセスの強制終了


起動中のプロセスの一覧から、選択したプロセスを強制終了させるサンプルです。
本ページのサンプルでは、プロセス ID の値が TListView の SubItem の 1 番目に格納されています。この文字列を数値に変換してプロセス ID として使用します。

このプロセス ID を引数にして、OpenProcess 関数でプロセスのハンドルを取得します。この時、プロセスを強制終了するためのフラグ PROCESS_TERMINATE を指定します。
このプロセスを TerminateProcess 関数で終了させます。


図9
設計時画面
  • TListView のカラムを 2 つ作成しておく
図10
エクセルを終了するところ
  • Excel 2007 と Excel 2010 が起動しているが、Excel 2007 を強制終了しようとしているところ

リスト6
指定したプロセスを強制終了するコード
//=============================================================================
//  ListView1で選択中のプロセスを強制終了
//=============================================================================
procedure TForm1.Button2Click(Sender: TObject);
var
  ProcessID   : Cardinal;
  hProcHandle : Cardinal;
begin
  if ListView1.ItemFocused = nil then exit;

  //プロセスIDを取得
  ProcessID := StrToInt(ListView1.Selected.SubItems[0]);

  //Terminate(プロセスの終了)アクセスモードでOpenProcess
  hProcHandle := OpenProcess(PROCESS_TERMINATE, False, ProcessID);
  try
    TerminateProcess(hProcHandle, 0);
  finally
    CloseHandle(hProcHandle);
  end;

  Button1Click(Sender);
end;




08_指定 EXE が起動中 ( ファイルが開いている ) かを確認する方法


起動中のプロセスの一覧を取得すれば、どのアプリケーションが起動しているか確認できます。
もし、対象のアプリケーションの実行ファイルのフルパス名が分かっていれば、次のコードで、そのアプリケーショが起動しているかを確認することができます。

CreateFile 関数でファイルを読み書き両用モードでの作成を試みます。 CreateFile 関数は実際のファイルは作成しません。ファイルのハンドルを生成するだけです。ファイルが存在しないと関数の実行は失敗します。
CreateFile 関数を使用すれば、指定したファイルハンドルが有効かを確認できます。このサンプルでは、対象のファイルが他のアプリで開いているかは確認できまぜん。例えば、テキストファイルをエディタ等で開いているかを確認することはできません。
引数の意味については MSDN オンラインの記事で確認してください。
FileOpen 関数でも同じです。FileOpen 関数は、内部で CreateFile 関数を実行しています。

次のコードは、添付のサンプルの 02_Toolhelp32Snapshot\Project1.exe が起動しているかを調べるようになっています。



[備考]
対象の EXE が自分自身の場合、Mutex を使用する方法もあります。



図11
実行画面
  • 指定したアプリが起動している場合

リスト7
指定したアプリケーションが起動しているかを調べるコード例
unit Unit1;

interface

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


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

var
  Form1: TForm1;

implementation

{$R *.DFM}

//=============================================================================
//  指定した EXE が起動中かを確認
//=============================================================================
procedure TForm1.Button1Click(Sender: TObject);
var
  ExeFullPath : String;
  hFileHandle : HFILE;
  ErrorID     : Cardinal;
begin
  //対象のプログラムのEXEのフルパス
  ExeFullPath := ExpandFileName('../02_Toolhelp32Snapshot\Project1.exe');

  hFileHandle := CreateFile(PChar(ExeFullPath),
                            GENERIC_READ or GENERIC_WRITE,
                            0,
                            nil,
                            OPEN_EXISTING,
                            FILE_ATTRIBUTE_NORMAL,
                            0);
  try
    ErrorID := GetLastError;
    if hFileHandle = INVALID_HANDLE_VALUE then begin
      if ErrorID = ERROR_SHARING_VIOLATION then begin
        MessageBox(0, 'アプリは既に起動しています', '情報', MB_ICONINFORMATION);



      end else begin
        //ディレクトリやファイルが存在しない時のエラー等
        MessageBox(0, PChar(SysErrorMessage(ErrorID)), '情報', MB_ICONINFORMATION);

      end;
    end else begin
      MessageBox(0, 'アプリはまだ起動していません', '情報', MB_ICONINFORMATION);
    end;
  finally
    //ファイルハンドルを閉じる(ファイルを閉じるわけではない)
    CloseHandle(hFileHandle);
  end;
end;

end.




09_EXE のフルパスからウィンドウハンドルを取得


これまでのサンプルでやったように、CreateToolhelp32Snapshot を使用して、パス名が同じ時のプロセス ID を取得します。次にトップレベルのウィンドウを EnumWindows で調べます。検出したウィンドウハンドルから GetWindowThreadProcessId 関数でプロセス ID を取得します。
このプロセス ID の値と、取得しておいた値と比較して、一致すれば、その時のウィンドウハンドルが目的のウィンドウハンドルとなります。

あるいは、EnumWindows のコールバック関数内で、ウィンドウを検出する度に、そのウィンドウのフルパスを取得して比較することになります。

もちろん対象のアプリケーションが起動していなければ取得できません。上のコードは、そういった状況での確認にも利用できます。

逆に、ウィンドウハンドルの値から EXE のパス名を求めるには、EnumWindows のコールバック関数内で GetWindowThreadProcessId 関数でプロセス ID の値を取得します。
このプロセス ID 値を使用して、OpenProcess でプロセスのオープンハンドルを取得し、そのウィンドウのフルパスを求めることができます。

次のサンプルは、フルパスで指定した EXE ファイル、つまりプログラムが起動していたら、そのウィンドウハンドルを調べて、強制終了させます。02_Toolhelp32Snapshot\Project1.exe を対象のブログラムとしています。
管理者権限での実行の必要性の有無については、ここまでの記事を参考にしてください。


図12
実行画面
  • 指定したアプリが起動していた場合

リスト8
フルパスで指定した EXE を強制終了させる
unit Unit1;
interface

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


type
  TForm1 = class(TForm)
    Button1: TButton;
    procedure Button1Click(Sender: TObject);
  private
    { Private 宣言 }
    function GetProcessIDFromPath(AExeFullPath: String): Cardinal;
  public
    { Public 宣言 }
  end;

function QueryFullProcessImageNameW(Process: THandle; Flags: DWORD; Buffer: PChar;
  Size: PDWORD): Boolean; stdcall; external 'kernel32.dll';

var
  Form1: TForm1;

implementation

uses Tlhelp32, Privilege;

{$R *.DFM}

//-----------------------------------------------------------------------------
//  EnumWindowsのコールバック関数
//  ウィンドウのプロセスIDが引数のlParと同じだったら、そのウインドウを閉じる
//-----------------------------------------------------------------------------
function EnumWndProc(hWindow: HWND; lPar: PCardinal):
  Boolean; Stdcall;
var
  dwProcessID : Cardinal;
begin
  Result := True;

  if IsWindowVisible(hWindow) then begin
    GetWindowThreadProcessId(hWindow, dwProcessID);
    if dwProcessID = lPar^ then begin
      PostMessage(hWindow, WM_CLOSE, 0, 0);
      Result := False;
    end;
  end;
end;

//=============================================================================
//  指定したEXEファイルが起動していたらそのプログラムを閉じるテスト
//=============================================================================
procedure TForm1.Button1Click(Sender: TObject);
var
  ExeFullPath : String;
  ProcessID   : Cardinal;
begin
  //対象のプログラムのEXEのフルパス
  ExeFullPath := ExpandFileName('../02_Toolhelp32Snapshot\Project1.exe');

  //ExeFullPathのプロセスIDを取得
  //ExeFullPathのプログラムが起動していないと取得できない
  ProcessID := GetProcessIDFromPath(ExeFullPath);

  if ProcessID > 0 then begin
    //EnumWindowsのコールバック関数内でアプリの閉じる作業を実行
    EnumWindows(@EnumWndProc, LPARAM(@ProcessID));
    MessageBox(Handle, '強制終了させました', '情報', MB_ICONINFORMATION);
  end else begin
    MessageBox(Handle, 'このアプリは起動していません', '情報', MB_ICONINFORMATION);
  end;
end;

//-----------------------------------------------------------------------------
//  引数の実行ファイル名のプロセスIDを取得する関数
//
//  関数QueryFullProcessImageNameWは、Delphi XEにはないので定義する
//  TProcessEntry32,CreateToolhelp32Snapshotの使用には、usesにTlhelp32が必要
//-----------------------------------------------------------------------------
function TForm1.GetProcessIDFromPath(AExeFullPath: String): Cardinal;
const
  PROCESS_QUERY_LIMITED_INFORMATION = $1000;
  PROCESS_NAME_NATIVE = 1;

var
  ListHandle  : Cardinal;
  ProcEntry   : TProcessEntry32;
  ProcessID   : DWORD;
  hProcHandle : THandle;
  ExePath     : String;
  Buff        : array[0..MAX_PATH-1] of Char;
  STR_SIZE    : DWORD;
begin
  Result := 0;

  //デバッグの特権を有効にする
  Privilege.SetPrivilege(SE_DEBUG_NAME, True);

  //プロセスのスナップショットのハンドルを取得
  ListHandle := CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
  if ListHandle > 0 then begin
    try
      //最初のプロセスに関する情報をTProcessEntry32レコード型に取得
      ProcEntry.dwSize := SizeOf(TProcessEntry32);
      Process32First(ListHandle, ProcEntry);
      repeat
        ExePath := '';
        FillChar(Buff, SizeOf(Buff), #0);

        //プロセスIDを取得
        ProcessID := ProcEntry.th32ProcessID;
        //プロセスID値からプロセスのオープンハンドルを取得
        hProcHandle := OpenProcess(PROCESS_QUERY_LIMITED_INFORMATION,
                                   False,
                                   ProcessID);
        try
          //オープンハンドルからパス名を取得
          if hProcHandle > 0 then begin
            //オープンハンドルからパス名を取得
            STR_SIZE := Length(Buff);
            if QueryFullProcessImageNameW(hProcHandle,
                                          0,
                                          @Buff,
                                          @STR_SIZE) then begin
              ExePath := String(Buff);
              //実行ファイル名が同じだったら終了
              if String(Buff) = Trim(AExeFullPath) then begin
                Result := ProcessID;
                break;
              end;
            end;
          end;

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

end.




10_コマンドプロンプトのコマンドによる操作


コマンドプロセッサのコマンドでも、起動中のプロセスのリストを取得したり、指定したプロセスを強制終了させることができます。CSV ファイルとしてもリダイレクトできます。
下図は、tasklist コマンドをオプションの指定なしで実行した結果例です。


図13
tasklist コマンド実行

下図は、taskkill コマンドで起動中の電卓を強制終了させたところです。強制終了は、PID (プロセス ID) 等の指定で終了させることもできます。


図14
taskkill コマンド実行




11_TerminateProcess 関数によるプロセスの終了と WM_CLOSE


メインフォームを持つアプリケーションの終了は、一般的にそのメインフォームのウィンドウハンドルに対して WM_CLOSE メッセージを送信します。

TerminateProcess 関数は、プロセスのハンドルを指定して、指定したプロセスとその全てのスレッドを終了します。指定するプロセスは、対象のアプリが生成したプロセスです。TerminateProcess 関数によるプロセスの終了を阻止することはできません。
別のプロセスを起動して処理するアプリケーションがあります。TerminateProcess 関数は、そのような子プロセスを終了させることはできません。

終了させるアプリが Delphi の VCL フォームアプリケーションの場合、WM_CLOSE でフォームが閉じる際にフォームの OnCloseQuery, OnClose, OnDestroy イベントが発生します。それらのイベント内で終了時の処理を行うことができます。一方、TerminateProcess 関数による終了では OnDestroy イベントは発生しますが、 OnCloseQuery, OnClose イベントfは発生しません。 したがって、それらのイベント内に終了時の処理があっても実行されません。処理中のスレッドも強制的に終了します。このことが問題となる可能性があります。
ウィンドウハンドルを持つアプリの終了は、WM_CLOSE メッセージによる方法が安全と言えます。

WM_CLOSE でアプリを終了させるには、ウィンドウハンドルの値が必要です。ShellExecuteEx 関数、あるいはCreateProcess 関数でアプリを起動した場合は、関数の引数のメンバから、起動したプロセスのハンドルの値が取得できます。CloseHandle でプロセスのハンドルをクロースする前であれば、プロセス ID の値を GetProcessId 関数で取得できます。
EnumWindows 関数のコールバック関数内で、このプロセス ID の値からウィンドウハンドルの値を取得することができます。前述の [EXE のフルパスからウィンドウハンドルを取得] に具体的なサンプルコードがあります。



[備考]
「強制終了」は、通常の操作では終了できなくなったアプリやプロセスを終了させる場合にも使用されます。この場合は TerminateProcess 関数を使用します。WM_CLOSE メッセージでは終了させることはできません。