概要
<手前ウィンドウのタイトル>と、<手前ウィンドウを表示するプロセスのモジュールのフルパス>をコンソール出力するツールを作成した。
 |
| 動作の様子。500ミリ秒おきにコンソール出力する。 |
製造
百聞は一見に如かず。
ソースコードの主要な個所を以下に示す。
メイン関数
#include <fcntl.h>
#include <io.h>
#include <stdlib.h>
#include <wchar.h>
#include <windows.h>
#include "process_tools.h"
int main(void) {
HWND hWnd = NULL;
DWORD processId = 0;
HANDLE hProcess = NULL;
std::vector<std::wstring> moduleNames;
wchar_t title[MAX_PATH] = {0};
// Use Unicode.
_setmode(_fileno(stdout), _O_U8TEXT);
while (true) {
// Open process handle.
// Window handle -> Process ID -> Process handle
hWnd = GetForegroundWindow();
GetWindowThreadProcessId(hWnd, &processId);
hProcess = OpenProcess(PROCESS_QUERY_INFORMATION | PROCESS_VM_READ, FALSE,
processId);
if (hProcess == NULL) {
return false;
}
// Get name of each module.
tools::EnumModuleNames(hProcess, moduleNames);
// Get title of window.
GetWindowText(hWnd, title, MAX_PATH);
// Show window title and module names.
wprintf(L"%ls\n", title);
wprintf(L"%u modules\n", moduleNames.size());
for (auto& module : moduleNames) {
wprintf(L" %ls\n", module.c_str());
}
wprintf(L"--------------------------------------------------\n");
wprintf(L"\n");
// Close process handle.
CloseHandle(hProcess);
// Interval in read.
Sleep(500);
}
return EXIT_SUCCESS;
}
ポイント
関数ヘッダファイル
#pragma once
#include <wchar.h>
#include <windows.h>
#include <string>
#include <vector>
namespace tools {
constexpr int ARGUMENT_ERROR = -1;
// (中略)
int EnumModuleNames(HANDLE hProcess, std::vector<std::wstring>& moduleNames);
} // namespace tools
関数ソースファイル
#include "./process_tools.h"
#include <psapi.h>
#include <windows.h>
#include <algorithm>
// (中略)
namespace tools {
// (中略)
//
// @brief <br> Get the module file path of given window.
// @param [in] hProcess process handle.
// @param [out] moduleNames Full path for modules.
// @return Actually read module number.
//
int EnumModuleNames(HANDLE hProcess, std::vector<std::wstring>& moduleNames) {
if (hProcess == NULL) {
return ARGUMENT_ERROR;
}
// Enumerate modules of process.
DWORD dwSize = 0;
EnumProcessModulesEx(hProcess, NULL, 0, &dwSize, LIST_MODULES_ALL);
std::vector hModules(dwSize / sizeof(HMODULE));
if (EnumProcessModulesEx(
hProcess, hModules.data(),
static_cast(sizeof(HMODULE) * hModules.size()), &dwSize,
LIST_MODULES_ALL) == 0) {
return 0;
}
// Get file path of modules.
moduleNames.clear();
wchar_t strBuf[MAX_PATH] = {0};
for (auto& hModule : hModules) {
GetModuleFileNameEx(hProcess, hModule, strBuf, MAX_PATH);
moduleNames.push_back(strBuf);
}
return static_cast<int>(moduleNames.size());
}
} // namespace tools
ポイント
- EnumProcessModules を 2回呼び出す
1回目の呼び出しではプロセスのモジュールの数を取得し、
2回目の呼び出しではプロセスのモジュールのハンドルを取得する。
この方法のメリットは、先に数が分かり、動的に配列を準備できることだ。
この方法のデメリットは、2回の呼び出しに冗長であることと、
「1回目と2回目の呼び出しの間に、モジュールの数が変わらないの?」
という懸念があることだ。
※尚、MSDN では決め打ちで十分に大きな固定長配列を準備することが
推奨されている。
- 処理の流れ
処理の流れを以下に示す(参考文献1、参考文献2)。
1. 手前ウィンドウのウィンドウハンドルを取得する(main関数)
2. ウィンドウハンドルからプロセスIDを取得する(main関数)
3. プロセスIDのプロセスを開く(main関数)
4. プロセスのモジュールハンドルを列挙する(本関数)
5. モジュールのフルパスを取得する(本関数)
※EnumProcessModulesEx について、詳しくは MSDN の
リファレンスを見てほしい(参考文献3)
テスト
googletest を用いて EnumModuleNames 関数の単体テスト(C0)を実施した。
NuGet を利用し gmock v1.10.0 パッケージマネージャをインストールした。
(main関数の方は適当やで)
環境
開発環境および動作確認を実施したPCの情報を以下に示す。
開発環境
Visual Studio Community 2015
PC情報
OS 名: Microsoft Windows 8.1
OS バージョン: 6.3.9600 N/A ビルド 9600
システム モデル: dynabook KIRA V73/PS
システムの種類: x64-based PC
物理メモリの合計: 8,103 MB
プロセッサ: Intel64 Family 6 Model 61 Stepping 4 GenuineIntel ~2200 Mhz
なぜ作った
必要を感じたから。
- 見切れているウィンドウタイトルの全体を知りたい
- 長いウィンドウタイトルをコピーしたい
稀によくあるシチュエーションである。
感想
単体テストしないと落ち着かない性質になってしまった。
バグを出さないことは重要であり、そのためには多大な労力が必要なんだな
と感じる今日この頃である。
参考文献
- ウィンドウハンドルから実行ファイル名を取得する
- DLL / モジュールの列挙 (PSAPI)
- EnumProcessModulesEx function
- Windowsのwprintf関数はUnicodeを出力できない?
- Google C++ Testing Frameworkをはじめよう