前の記事 に続けて、今回も Rust で Windows API を呼び出すプログラムを色々作ってみようと思います。
今回は以前に以下の記事でも実装したことがある CreateToolhelp32Snapshot と Process32First/Process32Next を使用してシステム内のプロセス情報を列挙するプログラムを作ってみようと思います。
参考:Win32 API でシステム内のプロセス情報を列挙してみるやつ
詳しい API の説明などは上記で記載した通りなので、今回はとにかくこのコードを Rust で再実装することを目指します。
もくじ
作成したコード
最終的に作成したのは以下のコードです。
// cargo run --bin EnumProcess.rs
use windows::Win32::{
System::Diagnostics::ToolHelp::{
CreateToolhelp32Snapshot, Process32FirstW, Process32NextW, PROCESSENTRY32W, TH32CS_SNAPPROCESS
},
Foundation::{CloseHandle, INVALID_HANDLE_VALUE},
};
fn main() {
unsafe {
//HANDLE CreateToolhelp32Snapshot(
// [in] DWORD dwFlags,
// [in] DWORD th32ProcessID
//);
// Result<HANDLE> を返すため unwrap で HANDLE を取得
let h_toolhelp32_snapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0).unwrap();
if h_toolhelp32_snapshot == INVALID_HANDLE_VALUE {
eprintln!("ERROR: Could not get a Toolhelp32Snapshot");
return;
}
//typedef struct tagPROCESSENTRY32
//{
// DWORD dwSize;
// DWORD cntUsage;
// DWORD th32ProcessID;
// ULONG_PTR th32DefaultHeapID;
// DWORD th32ModuleID;
// DWORD cntThreads;
// DWORD th32ParentProcessID;
// LONG pcPriClassBase;
// DWORD dwFlags;
// CHAR szExeFile[MAX_PATH];
//} PROCESSENTRY32;
// BOOL Process32First(
// [in] HANDLE hSnapshot,
// [in, out] LPPROCESSENTRY32 lppe
// );
let mut pe32 = PROCESSENTRY32W {
dwSize: std::mem::size_of::<PROCESSENTRY32W>() as u32,
..Default::default()
};
match Process32FirstW(h_toolhelp32_snapshot, &mut pe32) {
Ok(_) => {
eprintln!("Got process snapshot.");
loop {
let exe_file = {
let len = pe32.szExeFile.iter()
.position(|&c| c == 0)
.unwrap_or(pe32.szExeFile.len());
String::from_utf16_lossy(&pe32.szExeFile[..len])
};
eprintln!("Process name: {}", exe_file);
eprintln!("=====> PID: {}", pe32.th32ProcessID);
eprintln!("=====> Threads count: {}", pe32.cntThreads);
match Process32NextW(h_toolhelp32_snapshot, &mut pe32) {
Ok(_) => { },
Err(_e) => {
// eprintln!("Failed to get next process: {}", e);
break;
}
}
}
},
Err(e) => {
eprintln!("Failed to get first process: {}", e);
}
}
// Close h_toolhelp32_snapshot handle.
let handle_name = "h_toolhelp32_snapshot";
match CloseHandle(h_toolhelp32_snapshot) {
Ok(_) => {
eprintln!("Handle closed: {}", handle_name);
},
Err(e) => {
eprintln!("Failed to close handle:{} {}", handle_name, e);
}
}
}
return;
}
このコードをビルドして実行すると、C で作成していたコードと同じようにシステム内のプロセス情報を列挙することができました。
依存関係の追加
Windows Crate を使用して上記のコードを実装するため、features で Win32_System_Diagnostics_ToolHelp
を指定した設定を Cargo.toml に追加しました。
[dependencies]
windows = { version = "0.60.0", features = [
"Win32_System_Diagnostics_ToolHelp"
] }
モジュールのロード
プロセスの列挙に必要な Windows API を呼び出すため、以下のモジュールをロードしました。
use windows::Win32::{
System::Diagnostics::ToolHelp::{
CreateToolhelp32Snapshot, Process32FirstW, Process32NextW, PROCESSENTRY32W, TH32CS_SNAPPROCESS
},
Foundation::{CloseHandle, INVALID_HANDLE_VALUE},
};
CreateToolhelp32Snapshot と Process32FirstW/Process32NextW の 3 つの API は Windows Crate の windows::Win32::System::Diagnostics::ToolHelp
に存在しているようでしたのでこちらをロードしています。
参考:windows::Win32::System::Diagnostics::ToolHelp - Rust
また、作成したハンドルをクローズするための CloseHandle もロードしています。
参考:CloseHandle in windows::Win32::Foundation - Rust
CreateToolhelp32Snapshot によるハンドルの取得
main 関数内の unsafe ブロックの中で、まず始めに CreateToolhelp32Snapshot API を呼び出してプロセスのスナップショットに対するハンドルを取得しています。
//HANDLE CreateToolhelp32Snapshot(
// [in] DWORD dwFlags,
// [in] DWORD th32ProcessID
//);
// Result<HANDLE> を返すため unwrap で HANDLE を取得
let h_toolhelp32_snapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0).unwrap();
if h_toolhelp32_snapshot == INVALID_HANDLE_VALUE {
eprintln!("ERROR: Could not get a Toolhelp32Snapshot");
return;
}
Windows Crate の CreateToolhelp32Snapshot は HANDLE ではなく Result<HANDLE>
型の戻り値を返す点が C で実装した場合と異なっていました。
参考:CreateToolhelp32Snapshot in windows::Win32::System::Diagnostics::ToolHelp - Rust
このような Result 型は、Rust でエラーハンドリングのために用意された特別な型らしいです。
基本的には、Result は Ok と Err の 2 つのバリアントを持つ列挙型であり、返された値に応じて異なるアクションを実装できるようになっているようです。
enum Result<T, E> {
Ok(T),
Err(E),
}
参考:Recoverable Errors with Result - The Rust Programming Language
Result 型からの戻り値の取り出し方法はいくつかあるようですが、今回は unwrap を使用しました。
unwrap を使用すると、Result 型の値が Ok の場合に、関数の返り値を取り出し、Err の場合には panic!
によってプログラムをクラッシュさせてくれるようです。
これによって、変数 _h_toolhelp32_snapshot
に取得したハンドルを格納しています。
なお、この時 hToolhelp32Snapshot
という変数を使用しようとしたところ、コンパイラにスネークケースの変数名を使用するよう怒られてしまいました。
一応 Cargo.toml の設定でこの警告を発生させないようにすることができるようですが、とりあえず Rust のコーディング規約に従ってスネークケースを使用することにしました。
参考:Rust の勉強: snake case name ? | Jura-Zakki 樹羅雑記
未使用変数の扱い
このコードの中では、Ok(T)
の T など、いくつかコード内で使用しない変数を定義する必要があったのですが、そのまま実装したところビルド時にコンパイラから警告されることになりました。
回避方法を探してみたところ、変数名の先頭に _
を付与することで未使用変数の警告を出力させないようにしたり、変数名を _
のみにすることでその変数自体を使用しないことを示すことができるという情報がありました。
参考:Rustの変数名におけるアンダースコアの意味 | rs.nkmk.me
そこで、今回のコードでは Ok(_)
のような使用しない変数についてはアンダースコアを使用することにして警告を回避しました。
ミュータブルな変数の宣言
Rust では、let で宣言した変数は基本的にミュータブルな変数として扱われるらしく、変数の値を変更するとエラーが発生します。
そのため、あとから値を変更する変数を宣言する場合は mut を使用してイミュータブルな変数として宣言してあげる必要があります。
そこで、PROCESSENTRY32 構造体の方法を格納する pe32 変数は以下のように let mut pe32
のように宣言しています。
//typedef struct tagPROCESSENTRY32
//{
// DWORD dwSize;
// DWORD cntUsage;
// DWORD th32ProcessID;
// ULONG_PTR th32DefaultHeapID;
// DWORD th32ModuleID;
// DWORD cntThreads;
// DWORD th32ParentProcessID;
// LONG pcPriClassBase;
// DWORD dwFlags;
// CHAR szExeFile[MAX_PATH];
//} PROCESSENTRY32;
let mut pe32 = PROCESSENTRY32W {
dwSize: std::mem::size_of::<PROCESSENTRY32W>() as u32,
..Default::default()
};
また、その際この構造体の dwSize に構造体のサイズを指定し、構造体のメンバを初期化する必要があります。
構造体のメンバの初期化は以下のように明示的に指定する形でも実装できるようですが、今回は ..Default::default()
により他の構造体のメンバをデフォルト値に設定しています。
// Initialise a process entry. In order to use `Process32First`, you need to set `dwSize`.
let mut process_entry : PROCESSENTRY32W = PROCESSENTRY32W {
dwSize: mem::size_of::<PROCESSENTRY32W>() as u32,
cntUsage: 0,
th32ProcessID: 0,
th32DefaultHeapID: 0,
th32ModuleID: 0,
cntThreads: 0,
th32ParentProcessID: 0,
pcPriClassBase: 0,
dwFlags: 0,
szExeFile: [0; MAX_PATH],
};
参考:Comparing a string to an array of i8 - help - The Rust Programming Language Forum
この方法による初期化は Default トレイトというものを用意している構造体で使用できるようです。
参考:Rustのstd::default::Default – うたもく
PROCESSENTRY32W では、(たぶん) 以下の記載の通り Default トレイトを使用できます。
ループ処理の実装
ここまでのすべての操作が完了したら、後は Process32FirstW/Process32NextW の API を使用してスナップショットのハンドルからプロセス情報を順に取り出していきます。
Windows Crate の Process32FirstW と Process32NextW はどちらも空(?)の Result を戻り値として返します。
そのため、今回は unwrap ではなく match を使用しています。
match Process32FirstW(h_toolhelp32_snapshot, &mut pe32) {
Ok(_) => {
eprintln!("Got process snapshot.");
loop {
let exe_file = {
let len = pe32.szExeFile.iter()
.position(|&c| c == 0)
.unwrap_or(pe32.szExeFile.len());
String::from_utf16_lossy(&pe32.szExeFile[..len])
};
eprintln!("Process name: {}", exe_file);
eprintln!("=====> PID: {}", pe32.th32ProcessID);
eprintln!("=====> Threads count: {}", pe32.cntThreads);
match Process32NextW(h_toolhelp32_snapshot, &mut pe32) {
Ok(_) => { },
Err(_e) => {
// eprintln!("Failed to get next process: {}", e);
break;
}
}
}
},
Err(e) => {
eprintln!("Failed to get first process: {}", e);
}
}
Result に対して match を使用すると、Ok と Err が返却された場合のそれぞれに対する処理を実装できます。
上記の例では、Process32FirstW による情報の取得に成功した処理の中で loop ブロックによるループ処理を行い、Process32NextW による情報の取得に失敗するまでプロセス情報を出力し続ける処理を行っています。
ハンドルのクローズ処理
すべての処理が完了した後には CloseHandle で取得したプロセススナップショットのハンドルをクローズします。
// Close h_toolhelp32_snapshot handle.
let handle_name = "h_toolhelp32_snapshot";
match CloseHandle(h_toolhelp32_snapshot) {
Ok(_) => {
eprintln!("Handle closed: {}", handle_name);
},
Err(e) => {
eprintln!("Failed to close handle:{} {}", handle_name, e);
}
}
まとめ
Rust は結構癖が強いように思いますが、書いていて結構楽しいです。