概要
<手前ウィンドウのタイトル>と、<手前ウィンドウを表示するプロセスのモジュールのフルパス>をコンソール出力するツールを作成した。![]() |
| 動作の様子。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;
}
ポイント
- EXE と DLL
アプリケーションの EXE だけでなく、アプリケーションがロードした DLL を
合わせてコンソールに表示する。EnumProcessModulesEx 関数がフルパスの一覧を
取得する。 - Unicode 出力について
ウィンドウテキストが日本語の場合もコンソール出力で困らないように
以下のステップで標準出力を UTF-8 にする。setmode(_fileno(stdout), _O_U8TEXT);
※このステップを入れずにコードページを 65001 とし、
フォントを日本語対応フォント(MS Gothic)とした際は、
最初の1文字以降が尻切れになる問題が発生した。
原因を調べていたところ、参考文献4にこの方法を見つけ、
問題は解決した。
関数ヘッダファイル
#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情報開発環境
Visual Studio Community 2015
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
なぜ作った
必要を感じたから。- 見切れているウィンドウタイトルの全体を知りたい
- 長いウィンドウタイトルをコピーしたい
稀によくあるシチュエーションである。
感想
単体テストしないと落ち着かない性質になってしまった。バグを出さないことは重要であり、そのためには多大な労力が必要なんだな
と感じる今日この頃である。

0 件のコメント:
コメントを投稿
コメント表示は承認制に設定しています