Delphi Programming / Object Pascal
サイトのトップページへ リンクのページへ ロゴマーク
[掲載 2009年03月31日] [更新 2015年09月25日] Delphi サンプルプログラム集
   
332_NtQuerySystemInformation 関数を使用したプロセスリスト
動作確認等 Windows 7 U64(SP1) + Delphi XE5(UP2) Pro VCL-32
332_ProcessListNtQuery.zip [1,874 KB] 2015年09月25日版 (EXE 同梱)



  • 2010年08月10日
  • [02_JEDIプロジェクトを利用したコード] を追加




01_NtQuerySystemInformation 関数を使用したプロセスリスト


この関数は,MSDN の説明では,将来のバージョンでは廃止されるので,他の関数を使用するようにと書かれています.また,このサンプルで使用する情報用の構造体のメンバーの多くがシステムで使用するために予約されている.と書かれていますが,実際には Vista で使用可能です.たたし,一部の構造体 (レコード型) は,Vista 以降で変更があります.
このサンプルで使用している構造体のメンバーのリストはネット上で検索したものを利用しています.

このサンプルでは数項目しか表示しないようになっていますが,他の値は構造体のメンバから取得することになります.どのメンバーが何に相当するかは,[タスクマネージャー] を起動して,実際に確かめてみることで調査できます.Vista ではタスクマネージャーの表示可能項目は,30 項目もあります.これら全てが NtQuerySystemInformation 関数で求まるわけではありません.他の API 関数を用いて求めることになります.
目的にもよりますが,タスクマネージャーのようなアプリケーションを作成するのであれば,便利な関数ですが,単にプロセスの情報を取得するのであれば,MSDN の説明のように,他の関数を使用した方がいいのかも知れません.


図1
設計時画面
図2
実行結果例
図3
Windwos 7 の [タスクマネージャー]
  • 表示可能項目は 30 ある

リスト1
NtQuerySystemInformation 関数を使用した起動プロセスのリスト
unit Unit1;

interface

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

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

//-----------------------------------------------------------------------------
//  SYSTEM_PROCESS_INFORMATION構造体の定義関係
//  Delphi XEには定義がないので定義する必要あり
//  必要であれば(他のユニットからも利用)別ユニットにした方がいいかも知れない
//-----------------------------------------------------------------------------
  _SYSTEM_PROCESS_INFORMATION = packed record
    NextEntryOffset : DWORD;
    dThreadCount : DWORD;
    dReserved01 : DWORD;
    dReserved02 : DWORD;
    dReserved03 : DWORD;
    dReserved04 : DWORD;
    dReserved05 : DWORD;
    dReserved06 : DWORD;
    qCreateTime : LARGE_INTEGER;
    qUserTime : LARGE_INTEGER;
    qKernelTime : LARGE_INTEGER;
    usLength : WORD;
    usMaximumLength : WORD;
    usName : Pointer;
    BasePriority : DWORD;
    dUniqueProcessId : DWORD;
    dInheritedFromUniqueProcessId : DWORD;
    dHandleCount : DWORD;
    dReserved07 : DWORD;
    dReserved08 : DWORD;
    PeakVirtualSize : DWORD;
    VirtualSize : DWORD;
    PageFaultCount : DWORD;
    PeakWorkingSetSize : DWORD;
    WorkingSetSize : DWORD;
    QuotaPeakPagedPoolUsage : DWORD;
    QuotaPagedPoolUsage : DWORD;
    QuotaPeakNonPagedPoolUsage : DWORD;
    QuotaNonPagedPoolUsage : DWORD;
    PagefileUsage : DWORD;
    PeakPagefileUsage : DWORD;
    dCommitCharge : DWORD;
  end;
  TSYSTEM_PROCESS_INFORMATION = _SYSTEM_PROCESS_INFORMATION;
  PSYSTEM_PROCESS_INFORMATION = ^TSYSTEM_PROCESS_INFORMATION;

  //Delphi XEにはこの関数の定義がないので作成 
  //これはWindowsのdll内関数を利用する1つの方法
  function NtQuerySystemInformation(pSystemInformationClass: Cardinal;
                                    pSystemInformation: PVOID;
                                    pSystemInformationLength: ULONG;
                                    pReturnLength: PULONG): Cardinal;
                                    stdcall; external 'ntdll.dll';

const
  //NtQuerySystemInformation関数の第1引数の値
  //このサンプルでは5しか使用しないが,後の利用のために定義しておく
  SystemBasicInformation = 0;
  SystemPerformanceInformation = 2;
  SystemTimeOfDayInformation = 3;
  SystemProcessInformation = 5;
  SystemProcessorPerformanceInformation = 8;
  SystemInterruptInformation = 23;
  SystemExceptionInformation = 33;
  SystemRegistryQuotaInformation = 37;
  SystemLookasideInformation = 45;

//-----------------------------------------------------------------------------

var
  Form1: TForm1;

implementation

{$R *.dfm}

//=============================================================================
//  ListViewのカラムは設計時に作成しておく
//=============================================================================
procedure TForm1.FormShow(Sender: TObject);
begin
  ListProcessName;
end;

//=============================================================================
//  [表示更新]ボタン
//=============================================================================
procedure TForm1.Button1Click(Sender: TObject);
begin
 ListProcessName;
end;

//=============================================================================
//  NtQuerySystemInformationを使用して現在起動中のプロセスのリストを取得
//  NtQuerySystemInformationの第1引数はSystemProcessInformation(=5)
//
//  これ以外の値はLInfoレコード型の各メンバーから取得可能
//  どのメンバーが何かは実際に表示して,[タスクマネージャ]と比較して調査可能
//  ただし文字列はユニコードなのでDelphi2007以前ではPWideCharでキャストする
//=============================================================================
procedure TForm1.ListProcessName;
var
  LInfo   : PSYSTEM_PROCESS_INFORMATION;
  LBuffer : array of Byte;
  LSize   : Uint64;
  LCount  : Integer;
  LItem   : TListItem;
  LTime   : Int64;
begin
  ListView1.Items.BeginUpdate;
  ListView1.Items.Clear;

  //情報を格納するバッファサイズの取得
  NtQuerySystemInformation(SystemProcessInformation, nil, 0, @LSize);

  //情報格納用のバッファサイズを確保
  SetLength(LBuffer, LSize);
  if (NtQuerySystemInformation(SystemProcessInformation,
                               LBuffer,
                               LSize,
                               nil) = 0) then begin

    //ListViewにプロセス名等を表示
    LCount := 0;
    while True do begin
      //Cnt位置からのプロセスの情報をAInfoに取出す
      LInfo := @LBuffer[LCount];
      LItem := ListView1.Items.Add;

      //プロセス名
      LItem.Caption := PWideChar(LInfo.usName);
      if LItem.Caption ='' then LItem.Caption := 'System Idel Process';

      //PID
      LItem.SubItems.Add(IntToStr(LInfo.dUniqueProcessId));

      //メモリ使用量
      LSize := LInfo.WorkingSetSize div 1024;
      LItem.SubItems.Add(FormatFloat('###,##0 KB', LSize));

      //スレッド
      LItem.SubItems.Add(IntToStr(LInfo.dThreadCount));

      //CPU時間
      //LARGE_INTEGERの定義はWindows.pas参照
      //100ns単位を秒に変換
      LTime := (LInfo.qUserTime.QuadPart+LInfo.qKernelTime.QuadPart) div
               10000000;
      LItem.SubItems.Add(Format('%d:%.2d:%.2d',
                                [LTime div 3600,
                                (LTime mod 3600) div 60,
                                LTime mod 60]));

      //NextEntryOffsetメンバが0を返せばそれ以上の情報はない
      if (LInfo.NextEntryOffset = 0) then Break;

      //次のプロセス情報の開始位置へ移動
      Inc(LCount, LInfo.NextEntryOffset);
    end;
  end;

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

end.




02_JEDI プロジェクトを利用したコード


サンプルプログラム [475_パフォーマンスカウンタで CPU 使用率] で JEDI プロジェクトを利用しています.この JEDI プロジェクトの Win32API 関数類には,Delphi では不足している C++ のヘッダファイルに相当するファイルがほとんど含まれています.また,構造体の定義やメンバー名も MSDN オンラインの記述に準拠しています.
これを利用した場合のコードです.非常にスッキリしています.


図4
実行結果例


リスト2
JEDI プロジェクトの NtQuerySystemInformation 関数を利用したコード
unit Unit1;

interface

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

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

var
  Form1: TForm1;

implementation

uses
  JwaWindows;

{$R *.dfm}

//=============================================================================
//  ListViewのカラムは設計時に作成しておく
//=============================================================================
procedure TForm1.FormShow(Sender: TObject);
begin
  ListProcessName;
end;

//=============================================================================
//  [表示更新]ボタン
//=============================================================================
procedure TForm1.Button1Click(Sender: TObject);
begin
  ListProcessName;
end;

//=============================================================================
//  NtQuerySystemInformationを使用して現在起動中のプロセスのリストを取得
//  NtQuerySystemInformationの第1引数はSystemProcesses(=5)
//
//  これ以外の値はLInfoレコード型の各メンバーから取得可能
//  どのメンバーが何かは実際に表示して,[タスクマネージャ]と比較して調査可能
//  ただし文字列はユニコードなのでDelphi2007以前ではPWideCharでキャストする
//=============================================================================
procedure TForm1.ListProcessName;
var
  LInfo   : PSystemProcesses;
  LBuffer : array of Byte;
  LSize   : UInt64;
  LCount  : Integer;
  LItem   : TListItem;
  LTime   : Int64;
begin
  ListView1.Items.BeginUpdate;
  ListView1.Items.Clear;

  //情報を格納するバッファサイズの取得
  NtQuerySystemInformation(SystemProcessesAndThreadsInformation,
                           nil,
                           0,
                           @LSize);

  //情報格納用のバッファサイズを確保
  SetLength(LBuffer, LSize);

  if (NtQuerySystemInformation(SystemProcessesAndThreadsInformation,
                               LBuffer,
                               LSize,
                               nil) = 0) then begin

    //ListViewにプロセス名等を表示
    LCount := 0;
    while True do begin
      //Cnt位置からのプロセスの情報をAInfoに取出す
      LInfo := @LBuffer[LCount];
      LItem := ListView1.Items.Add;

      //プロセス名
      LItem.Caption := LInfo.ProcessName.Buffer;
      if LItem.Caption = '' then LItem.Caption := 'System Idel Process';

      //PID
      LItem.SubItems.Add(IntToStr(LInfo.ProcessId));

      //メモリ使用量
      LSize := LInfo.VmCounters.WorkingSetSize div 1024;
      LItem.SubItems.Add(FormatFloat('###,##0 KB', LSize));

      //スレッド
      LItem.SubItems.Add(IntToStr(LInfo.ThreadCount));

      //CPU時間
      //LARGE_INTEGERの定義はWindows.pas参照
      //100ns単位を秒に変換
      LTime := (LInfo.UserTime.QuadPart + LInfo.KernelTime.QuadPart) div
               10000000;
      LItem.SubItems.Add(Format('%d:%.2d:%.2d',
                                [LTime div 3600,
                                (LTime mod 3600) div 60,
                                LTime mod 60]));

      //NextEntryDeltaメンバが0を返せばそれ以上の情報はない
      if (LInfo.NextEntryDelta = 0) then Break;

      //次のプロセス情報の開始位置へ移動
      Inc(LCount, LInfo.NextEntryDelta);
    end;
  end;

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

end.