EternalWindows
DLL / モジュールの列挙 (PSAPI)

今回は、PSAPI(Process Status Helper)のEnumProcessModulesを 利用したモジュールの列挙について説明します。

BOOL EnumProcessModules(
  HANDLE hProcess,
  HMODULE* lphModule,
  DWORD cb,
  LPDWORD lpcbNeeded
);

hProcessは、モジュールを列挙したいプロセスのハンドルです。 lphModuleで、そのプロセスがマッピングしているモジュールを受け取ります。 そのため、モジュールを受け取れるだけの配列を指定することになります。 cbは、lphModuleのサイズです。 lpcbNeededは、モジュールを格納できるだけのサイズが返ります。

EnumProcessModulesを実際にどう呼び出すことになるのかですが、 第4引数にモジュールを格納できるだけのサイズが返るという性質を考えると、 以下のようにするのが正攻法でしょうか。

DWORD   dwSize;
HMODULE *lphmod;

EnumProcessModules(hProcess, NULL, 0, &dwSize);
lphmod = HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, dwSize);
EnumProcessModules(hProcess, lphmod, dwSize, &dwSize);

この方法は問題なく機能しますが、メモリの開放と破棄が伴ううえに、 EnumProcessModulesを2回呼び出すというのは冗長であるといえます。 事前に大きなサイズを持った配列を宣言しておいて、 それを指定するのが最も効率的でしょう。

DWORD   dwSize;
HMODULE hmod[100];

EnumProcessModules(hProcess, hmod, sizeof(hmod), &dwSize);

hProcessの取得方法については、コードの説明と共に行います。 また、EnumProcessModulesはモジュールがマッピングされたアドレスを返すだけで、 そのモジュールの名前などは返していません。 これを取得する方法もコードと共に説明します。

#include <windows.h>
#include <tlhelp32.h>
#include <psapi.h>

#pragma comment (lib, "psapi.lib")

void EnumModule(HWND hwndListBox, HANDLE hProcess);
DWORD GetProcessIdFromExe(LPTSTR lpszExeFile);
LRESULT CALLBACK WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam);

int WINAPI WinMain(HINSTANCE hinst, HINSTANCE hinstPrev, LPSTR lpszCmdLine, int nCmdShow)
{
	TCHAR      szAppName[] = TEXT("sample");
	HWND       hwnd;
	MSG        msg;
	WNDCLASSEX wc;

	wc.cbSize        = sizeof(WNDCLASSEX);
	wc.style         = 0;
	wc.lpfnWndProc   = WindowProc;
	wc.cbClsExtra    = 0;
	wc.cbWndExtra    = 0;
	wc.hInstance     = hinst;
	wc.hIcon         = (HICON)LoadImage(NULL, IDI_APPLICATION, IMAGE_ICON, 0, 0, LR_SHARED);
	wc.hCursor       = (HCURSOR)LoadImage(NULL, IDC_ARROW, IMAGE_CURSOR, 0, 0, LR_SHARED);
	wc.hbrBackground = (HBRUSH)GetStockObject(WHITE_BRUSH);
	wc.lpszMenuName  = NULL;
	wc.lpszClassName = szAppName;
	wc.hIconSm       = (HICON)LoadImage(NULL, IDI_APPLICATION, IMAGE_ICON, 0, 0, LR_SHARED);
	
	if (RegisterClassEx(&wc) == 0)
		return 0;

	hwnd = CreateWindowEx(0, szAppName, szAppName, WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, NULL, NULL, hinst, NULL);
	if (hwnd == NULL)
		return 0;

	ShowWindow(hwnd, nCmdShow);
	UpdateWindow(hwnd);
	
	while (GetMessage(&msg, NULL, 0, 0) > 0) {
		TranslateMessage(&msg);
		DispatchMessage(&msg);
	}

	return (int)msg.wParam;
}

LRESULT CALLBACK WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
	static HWND hwndListBox = NULL;

	switch (uMsg) {

	case WM_CREATE: {
		DWORD  dwProcessId;
		HANDLE hProcess;
		
		hwndListBox = CreateWindowEx(0, TEXT("LISTBOX"), NULL, WS_CHILD | WS_VISIBLE | WS_VSCROLL, 0, 0, 0, 0, hwnd, (HMENU)1, ((LPCREATESTRUCT)lParam)->hInstance, NULL);
		
		dwProcessId = GetProcessIdFromExe(TEXT("explorer.exe"));
		hProcess = OpenProcess(GENERIC_ALL, FALSE, dwProcessId);
		if (hProcess == NULL)
			return -1;

		EnumModule(hwndListBox, hProcess);

		return 0;
	}

	case WM_SIZE:
		MoveWindow(hwndListBox, 0, 0, LOWORD(lParam), HIWORD(lParam), TRUE);
		return 0;

	case WM_DESTROY:
		PostQuitMessage(0);
		return 0;

	default:
		break;

	}

	return DefWindowProc(hwnd, uMsg, wParam, lParam);
}

void EnumModule(HWND hwndListBox, HANDLE hProcess)
{
	DWORD      i;
	DWORD      dwSize;
	TCHAR      szBuf[256];
	TCHAR      szModule[256];
	HMODULE    hmod[100];
	MODULEINFO mi;

	EnumProcessModules(hProcess, hmod, sizeof(hmod), &dwSize);

	for (i = 0; i < dwSize / sizeof(HMODULE); i++) {
		GetModuleBaseName(hProcess, hmod[i], szModule, sizeof(szModule));
		GetModuleInformation(hProcess, hmod[i], &mi, sizeof(MODULEINFO));
		wsprintf(szBuf, TEXT("%s %x"), szModule, mi.lpBaseOfDll);
		SendMessage(hwndListBox, LB_ADDSTRING, 0, (LPARAM)szBuf);
	}
}

DWORD GetProcessIdFromExe(LPTSTR lpszExeFile)
{
	DWORD          dwProcessId;
	HANDLE         hSnapshot;
	PROCESSENTRY32 pe;
	
	hSnapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
	if (hSnapshot == INVALID_HANDLE_VALUE)
		return 0;

	pe.dwSize = sizeof(PROCESSENTRY32);
	Process32First(hSnapshot, &pe);

	dwProcessId = 0;
	do {
		if (lstrcmp(pe.szExeFile, lpszExeFile) == 0) {
			dwProcessId = pe.th32ProcessID;
			break;
		}
	} while (Process32Next(hSnapshot, &pe));
	
	CloseHandle(hSnapshot);

	return dwProcessId;
}

プログラムの内容は前節と同じように、プロセスのアドレス空間にマッピングされている モジュールの名前とベースアドレスをリストボックスに追加するものです。 しかし今回は、モジュールを列挙するプロセスを選択できる機能を持っています。

dwProcessId = GetProcessIdFromExe(TEXT("explorer.exe"));
hProcess = OpenProcess(GENERIC_ALL, FALSE, dwProcessId);
if (hProcess == NULL)
	return -1;

GetProcessIdFromExeという自作関数は、 プロセスの名前からプロセスIDを取得します。 このプロセスIDは、プロセスハンドル取得のためOpenProcessに使われます。 GetProcessIdFromImageの内部は、後ほど説明します。 次のコードは、EnumModuleの内部です。

EnumProcessModules(hProcess, hmod, sizeof(hmod), &dwSize);

for (i = 0; i < dwSize / sizeof(HMODULE); i++) {
	GetModuleBaseName(hProcess, hmod[i], szModule, sizeof(szModule));
	GetModuleInformation(hProcess, hmod[i], &mi, sizeof(MODULEINFO));
	wsprintf(szBuf, TEXT("%s %x"), szModule, mi.lpBaseOfDll);
	SendMessage(hwndListBox, LB_ADDSTRING, 0, (LPARAM)szBuf);
}

まず、EnumProcessModulesでモジュールの配列を取得します。 配列の要素数は、dwSize / sizeof(HMODULE)で導けます。 GetModuleBaseNameというPSAPIは、第2引数のモジュールのベース名を取得します。 ベース名というのはモジュールの名前のことで、 モジュールのフルパスを取得したいときはGetModuleFileNameExという PSAPIを呼び出すとよいでしょう。 GetModuleInformationというPSAPIは、第2引数のモジュールに関する情報を 第4引数のMODULEINFO構造体に格納します。

typedef struct _MODULEINFO {
  LPVOID lpBaseOfDll;
  DWORD SizeOfImage;
  LPVOID EntryPoint;
} MODULEINFO, *LPMODULEINFO;

lpBaseOfDllは、モジュールがロードされたアドレスです。 この値は、hmodと同じ値になるであろうと思われます。 SizeOfImageは、モジュールのPEヘッダのSizeOfImage値です。 EntryPointは、モジュールのPEヘッダのAddressOfEntryPoint値です。 PEヘッダについてはここでは説明しませんが、 このEntryPointというのは、EXEならWinMainのアドレス、 DLLならDllMainのアドレスというふうに考えてはいけません。

さて、プロセスの名前からプロセスIDを取得するGetProcessIdFromExeですが、 この関数はプロセスを列挙してプロセスに関する構造体を初期化し、 構造体のメンバが引数のプロセス名と一致するかを調べています。 そのため、モジュールの列挙に関する話題ではないのですが、 関数内部がTool Helpで実装されていることもあり、 理解しておくにこしたことはありません。 次のコードは、GetProcessIdFromExeの一部です。

hSnapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
if (hSnapshot == INVALID_HANDLE_VALUE)
	return 0;

pe.dwSize = sizeof(PROCESSENTRY32);
Process32First(hSnapshot, &pe);

dwProcessId = 0;

今回はプロセスのリストを取得するので、 CreateToolhelp32Snapshotの第1引数はTH32CS_SNAPPROCESSにします。 スナップショットに含まれるプロセスのリストを走査するのはProcess32Firstで、 PROCESSENTRY32構造体を要求します。 前節のModule32Firstと同様に、dwSizeメンバは事前に初期化しておきます。 dwProcessIdは、GetProcessIdFromExeの戻り値となる変数で、 この値が変化することがなければ、指定したプロセスは存在しないということになります。

do {
	if (lstrcmp(pe.szExeFile, lpszExeFile) == 0) {
		dwProcessId = pe.th32ProcessID;
		break;
	}
} while (Process32Next(hSnapshot, &pe));

PROCESSENTRY32構造体のpe.szExeFileメンバは、 そのプロセスのEXEファイルです。 これと引数で指定した名前が一致すれば、 目的のプロセスを発見できたことになります。 プロセスIDは、th32ProcessIDから参照できます。

前節と今回の内容から、一口にモジュールの列挙といっても いくつかの方法があることがわかりました。 Tool HelpとPSAPIのどちらが優れているかは 様々な角度から考えないと難しいところですが、 Tool HelpはWindows95でも利用できることに加え、 プロセス、スレッド、モジュール、ヒープとどれも似たような関数で 列挙が可能というのは大きな魅力でしょう。 PSAPIはWindwos2000から使用可能な関数群で、 列挙という枠に捉われず様々な用途の関数が用意されています。 また、WindwosXPになってもいくつかの関数が追加されているのは魅力です。


戻る