一般的なプログラミング タスクの 1 つに、実行中のすべての "アプリケーション" の列挙があります。Windows タスク マネージャが良い例で、このプログラムでは 2 つの方法で "アプリケーション" の一覧が表示されます。タスク マネージャの 1 つ目のタブには、デスクトップ上のすべての "アプリケーション ウィンドウ" の一覧が表示されます。2 つ目のタブには、システム内のすべての "プロセス" の一覧が表示されます。この資料では、これらの両方のタスクを実行する方法について詳しく説明します。
トップレベル ウィンドウの列挙
プロセスの列挙と、デスクトップ上のトップレベル ウィンドウの列挙では、トップレベル ウィンドウを列挙する方が比較的簡単です。トップレベル ウィンドウを列挙するには、EnumWindows() 関数を使用します。ウィンドウの一覧の作成には GetWindow() を使用しないようにします。GetWindow() を使用すると、Z オーダーが変更された場合やウィンドウが破棄された場合に、処理で混乱が生じる可能性があります。
EnumWindows() では、コールバック関数へのポインタとユーザー定義の LPARAM 値がパラメータとして使用されます。この関数では、デスクトップ上のウィンドウ (トップレベル ウィンドウ) 1 つに対してコールバック関数が 1 回呼び出されます。呼び出されたコールバック関数では、このウィンドウ ハンドルをリストに追加するなど、何らかの処理が行われます。この方法では、ウィンドウの Z オーダーが変更されても混乱が生じないことが保証されています。ウィンドウ ハンドルを取得したら、GetWindowText() を呼び出して、そのウィンドウのタイトルを取得できます。
プロセスの列挙
システム内のプロセスの一覧の作成は、ウィンドウの列挙に比べると多少複雑です。これは主に、この操作に使用する API 関数が Win32 オペレーティング システムの種類によって異なるためです。Windows 95、Windows 98、Windows Millennium Edition、Windows 2000、および Windows XP では、API の ToolHelp32 ライブラリの関数を使用できます。ただし、Windows NT では、プラットフォーム SDK に含まれている API の PSAPI ライブラリの関数を使用する必要があります。この資料では、この両方の手法について説明します。また、すべての Win32 オペレーティング システムで動作する EnumProcs() というラッパー関数のサンプルを紹介します。
ToolHelp32 ライブラリを使用したプロセスの列挙
最初に、ToolHelp32 を使用した方法について説明します。KERNEL32.dll に含まれている ToolHelp32 関数は、標準 API 関数です。これらの API は Windows NT 4.0 では使用できないことに注意してください。
ToolHelp32 には、システム内のプロセスとスレッドの列挙、およびメモリとモジュールに関する情報の取得を可能にするさまざまな関数が含まれています。ただし、プロセスを列挙する際に必要な関数は、CreateToolhelp32Snapshot()、Process32First()、および Process32Next() の 3 つのみです。
ToolHelp32 関数を使用するには、まず、システム内の情報の "スナップショット" を作成します。この操作には CreateToolhelp32Snapshot() 関数を使用します。この関数では、スナップショットに格納する情報の種類を選択できます。プロセスに関する情報が必要な場合は、TH32CS_SNAPPROCESS フラグを含めるようにします。CreateToolhelp32Snapshot() 関数では、ハンドルが返されます。このハンドルは、使用後は CloseHandle() に渡す必要があります。
次に、スナップショットからプロセスの一覧を取得するには、Process32First を 1 回呼び出し、続いて Process32Next を繰り返し呼び出します。これらの関数のいずれかにより FALSE が返されるまで、この呼び出しを続けます。この呼び出しは、スナップショット内のプロセスすべてに対して繰り返し実行されます。どちらの関数でも、スナップショットへのハンドルと、PROCESSENTRY32 構造体へのポインタがパラメータとして使用されます。
Process32First または Process32Next を呼び出すと、PROCESSENTRY32 構造体にシステム内の 1 つのプロセスに関する有益な情報が格納されます。プロセス ID は、この構造体の th32ProcessID メンバに格納されています。このプロセス ID を OpenProcess() API に渡すと、プロセスへのハンドルを取得できます。プロセスの実行可能ファイルとパスは、同じ構造体の szExeFile メンバに格納されています。この構造体には、他にも有益な情報が格納されています。
注 : Process32First() を呼び出す前に、PROCESSENTRY32 構造体の dwSize メンバに sizeof (PROCESSENTRY32) を設定するようにしてください。
PSAPI ライブラリを使用したプロセスの列挙
Windows NT でプロセスの一覧を作成するには、PSAPI.dll に含まれている PSAPI 関数を使用します。このファイルは、次のマイクロソフト Web サイトからダウンロードできるプラットフォーム SDK で配布されています。
必要な PSAPI.h および PSAPI.lib ファイルも、プラットフォーム SDK に含まれています。
PSAPI ライブラリの関数を使用するには、PSAPI.lib ファイルをプロジェクトに追加し、PSAPI の API を呼び出すすべてのモジュールに PSAPI.h ファイルをインクルードします。Windows NT 4.0 には PSAPI.dll が含まれていないため、配布する実行可能ファイルで PSAPI.dll ファイルを使用する場合は、PSAPI.dll ファイルも忘れずに配布するようにしてください。(完全な Platform SDK を含まない) 再配布可能版の PSAPI.dll は、次の場所からダウンロードできます。
ToolHelp32 関数と同様、PSAPI ライブラリにも他のさまざまな役立つ関数が含まれています。ただし、この資料で説明するのは、プロセスの列挙に関連する EnumProcesses() 関数、EnumProcessModules() 関数、GetModuleFileNameEx() 関数、および GetModuleBaseName() 関数のみについてです。
プロセスの一覧を作成する最初の手順は、EnumProcesses() を呼び出すことです。この関数の宣言を、次に示します。
BOOL EnumProcesses( DWORD *lpidProcess, DWORD cb, DWORD *cbNeeded );
EnumProcesses() では、DWORD 配列 (lpidProcess) へのポインタとその配列のサイズ (cb)、および返されるデータの長さを受け取る DWORD (cbNeeded) へのポインタが使用されます。DWORD 配列には、現在実行中のプロセスのプロセス ID の配列が格納されます。cbNeeded パラメータにより、使用される配列のサイズが返されます。nReturned = cbNeeded / sizeof (DWORD) という計算式により、返されるプロセス ID の数がわかります。
注 : この資料では、返される DWORD を "cbNeeded" と呼んでいますが、配列に渡された値のサイズを実際に確認する方法はありません。EnumProcesses() から、cb パラメータに渡した配列値のサイズを超える値が cbNeeded に返されることはありません。したがって、EnumProcesses() 関数の成功を保証する唯一の方法は、DWORD 配列を割り当て、関数の実行後 cb と cbNeeded が等しい場合はより大きい配列を割り当て、cbNeeded が cb より小さくなるまで同じ処理を繰り返すことです。
これでシステム内の各プロセス ID が格納された配列が作成されました。目的がプロセスの名前を取得することであれば、まずここでハンドルを取得する必要があります。プロセス ID からハンドルを取得するには、OpenProcess() を使用します。
ハンドルを取得したら、プロセスの最初のモジュールを取得する必要があります。あるプロセスの最初のモジュールを取得するには、パラメータを次のように指定して EnumProcessModules() API を呼び出します。
EnumProcessModules( hProcess, &hModule, sizeof(hModule), &cbReturned );
この呼び出しの結果、プロセスの最初のモジュールのハンドルが hModule 変数に格納されます。実際にはプロセスには名前がありませんが、プロセス内の最初のモジュールはそのプロセスの実行可能モジュールになることを覚えておいてください。これで、返されたモジュールのハンドル (hModule) を GetModuleFileNameEx() API または GetModuleBaseName() API で使用して、プロセスの実行可能モジュールのフル パス名または簡単なモジュール名を取得できます。どちらの関数でも、プロセスへのハンドル、モジュールへのハンドル、名前が返されるバッファへのポインタ、バッファのサイズがパラメータとして使用されます。
この処理を EnumProcesses() API から返される各プロセス ID に対して繰り返し実行し、Windows NT のプロセスの一覧を作成します。
16 ビット プロセス
Windows 95、Windows 98、および Windows Millennium Edition で実行される 16 ビット アプリケーションは、ToolHelp32 においては 32 ビット アプリケーションと同じです。16 ビット アプリケーションにも、Win32 アプリケーションと同様にプロセス ID などが存在します。ただし、このことは、Windows NT、Windows 2000、または Windows XP の場合には当てはまりません。これらのオペレーティング システムでは、16 ビット アプリケーションは仮想 DOS マシン (VDM) で実行されます。
Windows NT、Windows 2000、および Windows XP で 16 ビット アプリケーションを列挙するには、VDMEnumTaskWOWEx() という関数を使用する必要があります。ソース モジュールには VDMDBG.h をインクルードし、また VDMDBG.lib ファイルをプロジェクトにリンクする必要があります。これら 2 つのファイルは、プラットフォーム SDK に含まれています。
この関数の宣言は次のとおりです。
INT WINAPI VDMEnumTaskWOWEx( DWORD dwProcessId, TASKENUMPROCEX fp,
LPARAM lparam );
dwProcessId は、16 ビット タスクを列挙する NTVDM プロセスの ID です。fp パラメータは、コールバック列挙関数へのポインタです。lparam パラメータは、列挙関数に渡されるユーザー定義の値です。
列挙関数は、次のように定義する必要があります。
BOOL WINAPI Enum16( DWORD dwThreadId, WORD hMod16, WORD hTask16, PSZ
pszModName, PSZ pszFileName, LPARAM lpUserDefined );
この関数は、VDMEnumTaskWOWEx() に渡される NTVDM プロセスで実行されている各 16 ビット タスクに対して 1 回呼び出されます。プロセスの列挙を続ける場合は FALSE を返し、列挙を終了する場合は TRUE を返します。これは EnumWindows() とは逆であるため、注意してください。
サンプル コード
以下のサンプル コードでは PSAPI 関数と ToolHelp32 関数を EnumProcs() という 1 つの関数にカプセル化します。この関数の動作は、関数へのポインタをパラメータとして使用し、システム内の各プロセスに対して 1 回ずつ関数を繰り返し呼び出すという点において、EnumWindows() に似ています。この関数の宣言は次のとおりです。
BOOL WINAPI EnumProcs( PROCENUMPROC lpProc, LPARAM lParam );
この関数を使用する場合、次のようにコールバック関数を宣言します。
BOOL CALLBACK Proc( DWORD dw, WORD w16, LPCSTR lpstr, LPARAM lParam );
dw パラメータには ID が入ります。"w16" には 16 ビット タスク番号が入りますが、32 ビット プロセスの場合は 0 が入ります (Windows 95 では常にゼロが入ります)。lpstr パラメータはファイル名へのポインタです。lParam は、EnumProcs() に渡されるユーザー定義の lParam です。
EnumProcs() 関数では、ToolHelp32 関数および PSAPI 関数が、一般的な暗黙のリンクではなく明示的なリンクを介して使用されます。この方法は、EnumProcs() 関数を含むコードと、すべての Win32 オペレーティング システムとのバイナリ互換を実現するために使用されます。
//
// EnumProc.c
//
#include <windows.h>
#include <stdio.h>
#include <tlhelp32.h>
#include <vdmdbg.h>
typedef BOOL (CALLBACK *PROCENUMPROC)(DWORD, WORD, LPSTR, LPARAM);
typedef struct {
DWORD dwPID;
PROCENUMPROC lpProc;
DWORD lParam;
BOOL bEnd;
} EnumInfoStruct;
BOOL WINAPI EnumProcs(PROCENUMPROC lpProc, LPARAM lParam);
BOOL WINAPI Enum16(DWORD dwThreadId, WORD hMod16, WORD hTask16,
PSZ pszModName, PSZ pszFileName, LPARAM lpUserDefined);
//
// The EnumProcs function takes a pointer to a callback function
// that will be called once per process with the process filename
// and process ID.
//
// lpProc -- Address of callback routine.
//
// lParam -- A user-defined LPARAM value to be passed to
// the callback routine.
//
// Callback function definition:
// BOOL CALLBACK Proc(DWORD dw, WORD w, LPCSTR lpstr, LPARAM lParam);
//
BOOL WINAPI EnumProcs(PROCENUMPROC lpProc, LPARAM lParam) {
OSVERSIONINFO osver;
HINSTANCE hInstLib = NULL;
HINSTANCE hInstLib2 = NULL;
HANDLE hSnapShot = NULL;
LPDWORD lpdwPIDs = NULL;
PROCESSENTRY32 procentry;
BOOL bFlag;
DWORD dwSize;
DWORD dwSize2;
DWORD dwIndex;
HMODULE hMod;
HANDLE hProcess;
char szFileName[MAX_PATH];
EnumInfoStruct sInfo;
// ToolHelp Function Pointers.
HANDLE (WINAPI *lpfCreateToolhelp32Snapshot)(DWORD, DWORD);
BOOL (WINAPI *lpfProcess32First)(HANDLE, LPPROCESSENTRY32);
BOOL (WINAPI *lpfProcess32Next)(HANDLE, LPPROCESSENTRY32);
// PSAPI Function Pointers.
BOOL (WINAPI *lpfEnumProcesses)(DWORD *, DWORD, DWORD *);
BOOL (WINAPI *lpfEnumProcessModules)(HANDLE, HMODULE *, DWORD,
LPDWORD);
DWORD (WINAPI *lpfGetModuleBaseName)(HANDLE, HMODULE, LPTSTR, DWORD);
// VDMDBG Function Pointers.
INT (WINAPI *lpfVDMEnumTaskWOWEx)(DWORD, TASKENUMPROCEX, LPARAM);
// Retrieve the OS version
osver.dwOSVersionInfoSize = sizeof(osver);
if (!GetVersionEx(&osver))
return FALSE;
// If Windows NT 4.0
if (osver.dwPlatformId == VER_PLATFORM_WIN32_NT
&& osver.dwMajorVersion == 4) {
__try {
// Get the procedure addresses explicitly. We do
// this so we don't have to worry about modules
// failing to load under OSes other than Windows NT 4.0
// because references to PSAPI.DLL can't be resolved.
hInstLib = LoadLibraryA("PSAPI.DLL");
if (hInstLib == NULL)
__leave;
hInstLib2 = LoadLibraryA("VDMDBG.DLL");
if (hInstLib2 == NULL)
__leave;
// Get procedure addresses.
lpfEnumProcesses = (BOOL (WINAPI *)(DWORD *, DWORD, DWORD*))
GetProcAddress(hInstLib, "EnumProcesses");
lpfEnumProcessModules = (BOOL (WINAPI *)(HANDLE, HMODULE *,
DWORD, LPDWORD)) GetProcAddress(hInstLib,
"EnumProcessModules");
lpfGetModuleBaseName = (DWORD (WINAPI *)(HANDLE, HMODULE,
LPTSTR, DWORD)) GetProcAddress(hInstLib,
"GetModuleBaseNameA");
lpfVDMEnumTaskWOWEx = (INT (WINAPI *)(DWORD, TASKENUMPROCEX,
LPARAM)) GetProcAddress(hInstLib2, "VDMEnumTaskWOWEx");
if (lpfEnumProcesses == NULL
|| lpfEnumProcessModules == NULL
|| lpfGetModuleBaseName == NULL
|| lpfVDMEnumTaskWOWEx == NULL)
__leave;
//
// Call the PSAPI function EnumProcesses to get all of the
// ProcID's currently in the system.
//
// NOTE: In the documentation, the third parameter of
// EnumProcesses is named cbNeeded, which implies that you
// can call the function once to find out how much space to
// allocate for a buffer and again to fill the buffer.
// This is not the case. The cbNeeded parameter returns
// the number of PIDs returned, so if your buffer size is
// zero cbNeeded returns zero.
//
// NOTE: The "HeapAlloc" loop here ensures that we
// actually allocate a buffer large enough for all the
// PIDs in the system.
//
dwSize2 = 256 * sizeof(DWORD);
do {
if (lpdwPIDs) {
HeapFree(GetProcessHeap(), 0, lpdwPIDs);
dwSize2 *= 2;
}
lpdwPIDs = (LPDWORD) HeapAlloc(GetProcessHeap(), 0,
dwSize2);
if (lpdwPIDs == NULL)
__leave;
if (!lpfEnumProcesses(lpdwPIDs, dwSize2, &dwSize))
__leave;
} while (dwSize == dwSize2);
// How many ProcID's did we get?
dwSize /= sizeof(DWORD);
// Loop through each ProcID.
for (dwIndex = 0; dwIndex < dwSize; dwIndex++) {
szFileName[0] = 0;
// Open the process (if we can... security does not
// permit every process in the system to be opened).
hProcess = OpenProcess(
PROCESS_QUERY_INFORMATION | PROCESS_VM_READ,
FALSE, lpdwPIDs[dwIndex]);
if (hProcess != NULL) {
// Here we call EnumProcessModules to get only the
// first module in the process. This will be the
// EXE module for which we will retrieve the name.
if (lpfEnumProcessModules(hProcess, &hMod,
sizeof(hMod), &dwSize2)) {
// Get the module name
if (!lpfGetModuleBaseName(hProcess, hMod,
szFileName, sizeof(szFileName)))
szFileName[0] = 0;
}
CloseHandle(hProcess);
}
// Regardless of OpenProcess success or failure, we
// still call the enum func with the ProcID.
if (!lpProc(lpdwPIDs[dwIndex], 0, szFileName, lParam))
break;
// Did we just bump into an NTVDM?
if (_stricmp(szFileName, "NTVDM.EXE") == 0) {
// Fill in some info for the 16-bit enum proc.
sInfo.dwPID = lpdwPIDs[dwIndex];
sInfo.lpProc = lpProc;
sInfo.lParam = (DWORD) lParam;
sInfo.bEnd = FALSE;
// Enum the 16-bit stuff.
lpfVDMEnumTaskWOWEx(lpdwPIDs[dwIndex],
(TASKENUMPROCEX) Enum16, (LPARAM) &sInfo);
// Did our main enum func say quit?
if (sInfo.bEnd)
break;
}
}
} __finally {
if (hInstLib)
FreeLibrary(hInstLib);
if (hInstLib2)
FreeLibrary(hInstLib2);
if (lpdwPIDs)
HeapFree(GetProcessHeap(), 0, lpdwPIDs);
}
// If any OS other than Windows NT 4.0.
} else if (osver.dwPlatformId == VER_PLATFORM_WIN32_WINDOWS
|| (osver.dwPlatformId == VER_PLATFORM_WIN32_NT
&& osver.dwMajorVersion > 4)) {
__try {
hInstLib = LoadLibraryA("Kernel32.DLL");
if (hInstLib == NULL)
__leave;
// If NT-based OS, load VDMDBG.DLL.
if (osver.dwPlatformId == VER_PLATFORM_WIN32_NT) {
hInstLib2 = LoadLibraryA("VDMDBG.DLL");
if (hInstLib2 == NULL)
__leave;
}
// Get procedure addresses. We are linking to
// these functions explicitly, because a module using
// this code would fail to load under Windows NT,
// which does not have the Toolhelp32
// functions in KERNEL32.DLL.
lpfCreateToolhelp32Snapshot =
(HANDLE (WINAPI *)(DWORD,DWORD))
GetProcAddress(hInstLib, "CreateToolhelp32Snapshot");
lpfProcess32First =
(BOOL (WINAPI *)(HANDLE,LPPROCESSENTRY32))
GetProcAddress(hInstLib, "Process32First");
lpfProcess32Next =
(BOOL (WINAPI *)(HANDLE,LPPROCESSENTRY32))
GetProcAddress(hInstLib, "Process32Next");
if (lpfProcess32Next == NULL
|| lpfProcess32First == NULL
|| lpfCreateToolhelp32Snapshot == NULL)
__leave;
if (osver.dwPlatformId == VER_PLATFORM_WIN32_NT) {
lpfVDMEnumTaskWOWEx = (INT (WINAPI *)(DWORD, TASKENUMPROCEX,
LPARAM)) GetProcAddress(hInstLib2, "VDMEnumTaskWOWEx");
if (lpfVDMEnumTaskWOWEx == NULL)
__leave;
}
// Get a handle to a Toolhelp snapshot of all processes.
hSnapShot = lpfCreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
if (hSnapShot == INVALID_HANDLE_VALUE) {
FreeLibrary(hInstLib);
return FALSE;
}
// Get the first process' information.
procentry.dwSize = sizeof(PROCESSENTRY32);
bFlag = lpfProcess32First(hSnapShot, &procentry);
// While there are processes, keep looping.
while (bFlag) {
// Call the enum func with the filename and ProcID.
if (lpProc(procentry.th32ProcessID, 0,
procentry.szExeFile, lParam)) {
// Did we just bump into an NTVDM?
if (_stricmp(procentry.szExeFile, "NTVDM.EXE") == 0) {
// Fill in some info for the 16-bit enum proc.
sInfo.dwPID = procentry.th32ProcessID;
sInfo.lpProc = lpProc;
sInfo.lParam = (DWORD) lParam;
sInfo.bEnd = FALSE;
// Enum the 16-bit stuff.
lpfVDMEnumTaskWOWEx(procentry.th32ProcessID,
(TASKENUMPROCEX) Enum16, (LPARAM) &sInfo);
// Did our main enum func say quit?
if (sInfo.bEnd)
break;
}
procentry.dwSize = sizeof(PROCESSENTRY32);
bFlag = lpfProcess32Next(hSnapShot, &procentry);
} else
bFlag = FALSE;
}
} __finally {
if (hInstLib)
FreeLibrary(hInstLib);
if (hInstLib2)
FreeLibrary(hInstLib2);
}
} else
return FALSE;
// Free the library.
FreeLibrary(hInstLib);
return TRUE;
}
BOOL WINAPI Enum16(DWORD dwThreadId, WORD hMod16, WORD hTask16,
PSZ pszModName, PSZ pszFileName, LPARAM lpUserDefined) {
BOOL bRet;
EnumInfoStruct *psInfo = (EnumInfoStruct *)lpUserDefined;
bRet = psInfo->lpProc(psInfo->dwPID, hTask16, pszFileName,
psInfo->lParam);
if (!bRet)
psInfo->bEnd = TRUE;
return !bRet;
}
BOOL CALLBACK MyProcessEnumerator(DWORD dwPID, WORD wTask,
LPCSTR szProcess, LPARAM lParam) {
if (wTask == 0)
printf("%5u %s\n", dwPID, szProcess);
else
printf(" %5u %s\n", wTask, szProcess);
return TRUE;
}
void main() {
EnumProcs((PROCENUMPROC) MyProcessEnumerator, 0);
}
『Microsoft Systems Journal』 (1996 年 8 月発行第 8 号)、「Under the Hood」 (Matt Pietrek 著)
『Microsoft Systems Journal』 (1996 年 11 月発行第 11 号)、「Under the Hood」 (Matt Pietrek 著)
この資料は米国 Microsoft Corporation から提供されている Knowledge Base の Article ID
175030?
(http://support.microsoft.com/kb/175030/EN-US/
)
(最終更新日 2004-09-27) を基に作成したものです。
この資料に含まれているサンプル コード/プログラムは英語版を前提に書かれたものをありのままに記述しており、日本語環境での動作は確認されておりません。