今回は 前回までの記事 に引き続き、Rust で Windows Crate を使用して CreateFileMapping などの API を使用したプログラムを実装してみました。
もくじ
作成したコード
以下のプログラムでは、ページングファイルとテキストファイルのそれぞれを使用したファイルマッピングオブジェクトをプロセスメモリ内に作成し、データの書き込みを行っています。
// cargo run --bin CreateFileMap
use windows::Win32::{
Foundation::{ HANDLE, INVALID_HANDLE_VALUE, CloseHandle },
System::Memory::{ CreateFileMappingW, MapViewOfFile, UnmapViewOfFile, FILE_MAP_ALL_ACCESS, PAGE_READWRITE}
};
use std::{
fs::OpenOptions,
io::stdin,
os::windows::io::AsRawHandle,
ptr::{ copy_nonoverlapping, null_mut }
};
fn get_input(input_string: &str) {
println!("{}", input_string);
let mut input = String::new();
stdin().read_line(&mut input).expect("Failed to read input.");
return;
}
fn create_memory_mapping() {
unsafe {
let size: usize = 1024;
let h_mapping = CreateFileMappingW(
INVALID_HANDLE_VALUE,
Some(null_mut()),
PAGE_READWRITE,
0,
size as u32,
None,
).unwrap();
if h_mapping == INVALID_HANDLE_VALUE {
eprintln!("CreateFileMappingW failed");
} else {
println!("Mapping handle created: {:?}.", h_mapping);
}
get_input("Please enter the key...");
let p_view = MapViewOfFile(h_mapping, FILE_MAP_ALL_ACCESS, 0, 0, size);
if p_view.Value.is_null() {
eprintln!("MapViewOfFile failed");
} else {
println!("MapViewOfFile created: {:?}.", p_view);
}
get_input("Please enter the key...");
let data = b"Hello from Rust!";
std::ptr::copy_nonoverlapping(data.as_ptr(), p_view.Value as *mut u8, data.len());
println!("Data written to shared memory: {:?}", &data);
get_input("create_memory_mapping finished.");
let _ = UnmapViewOfFile(p_view);
let _ = CloseHandle(h_mapping);
}
return;
}
fn read_file_and_create_file_maipping() {
let size: usize = 32;
let path = "C:\\Users\\kash1064\\Documents\\Dev\\WinAPITest\\test.txt";
let file = OpenOptions::new().read(true).write(true).create(true).open(path).unwrap();
// let file_size = file.metadata().unwrap().len();
let h_file = HANDLE(file.as_raw_handle());
unsafe {
let h_mapping = CreateFileMappingW(
h_file,
Some(null_mut()),
PAGE_READWRITE,
0,
size as u32,
None,
).unwrap();
if h_mapping == INVALID_HANDLE_VALUE {
eprintln!("CreateFileMappingW failed");
} else {
println!("Mapping handle created: {:?}.", h_mapping);
}
get_input("Please enter the key...");
let p_view = MapViewOfFile(h_mapping, FILE_MAP_ALL_ACCESS, 0, 0, size);
if p_view.Value.is_null() {
eprintln!("MapViewOfFile failed");
} else {
println!("MapViewOfFile created: {:?}.", p_view);
}
get_input("Please enter the key...");
let data = b"Hello from Rust!";
copy_nonoverlapping(data.as_ptr(), p_view.Value as *mut u8, data.len());
println!("Data written to shared memory: {:?}", &data);
get_input("read_file_and_create_file_maipping finished.");
let _ = UnmapViewOfFile(p_view);
let _ = CloseHandle(h_mapping);
}
return;
}
fn main() {
create_memory_mapping();
read_file_and_create_file_maipping();
return;
}
ロードしたモジュール
今回は以下のモジュールをロードしています。
標準ライブラリをいくつか使用しています。
use windows::Win32::{
Foundation::{ HANDLE, INVALID_HANDLE_VALUE, CloseHandle },
System::Memory::{ CreateFileMappingW, MapViewOfFile, UnmapViewOfFile, FILE_MAP_ALL_ACCESS, PAGE_READWRITE}
};
use std::{
fs::OpenOptions,
io::stdin,
os::windows::io::AsRawHandle,
ptr::null_mut
};
標準入力の受け取り
今回は標準入力を受け取るために以下の関数を作成しています。
fn get_input(input_string: &str) {
println!("{}", input_string);
let mut input = String::new();
stdin().read_line(&mut input).expect("Failed to read input.");
return;
}
Rust は標準入力を受け取るコードの記述量が他の言語より若干多い印象でした。
Python で言うところの input("Please input: ")
みたいなことをやりたかったのですが全く同じ感覚で使えるライブラリ関数はなさそうでしたので、get_input 関数として独自に定義しています。
入力自体は stdin().read_line(&mut input)
で受け取っています。
これは戻り値として Result<usize>
を返すので、expect で panic 時のエラーメッセージをカスタムしています。
CreateFileMappingW API を使用する
今回のプログラムでは、CreateFileMappingW API を使用する関数を 2 つ作成しています。
片方は、CreateFileMappingW 関数の第 1 引数を INVALID_HANDLE_VALUE
に設定したものでページングファイルを使用しており、もう一方はローカルストレージ内の test.txt というファイルを使用します。
参考:CreateFileMappingW 関数 (memoryapi.h) - Win32 apps | Microsoft Learn
参考:名前付き共有メモリの作成 - Win32 apps | Microsoft Learn
ページファイルマッピングを作成する
creatememorymapping 関数の方では以下のコードを実装しています。
Windows Crate の CreateFileMappingW は Result<HANDLE>
を返すので、unwrap() で HANDLE オブジェクトを取得しています。
また、この時第 1 引数には INVALID_HANDLE_VALUE
を指定しているため、返される HANDLE はメモリにマップされたページングファイルのハンドルになるはずです。
let size: usize = 1024;
let h_mapping = CreateFileMappingW(
INVALID_HANDLE_VALUE,
Some(null_mut()),
PAGE_READWRITE,
0,
size as u32,
None,
).unwrap();
if h_mapping == INVALID_HANDLE_VALUE {
eprintln!("CreateFileMappingW failed");
} else {
println!("Mapping handle created: {:?}.", h_mapping);
}
CreateFileMappingW の第 2 引数には子プロセスが返されたハンドルを継承することができるかを指定する LPSECURITY_ATTRIBUTES
を登録する必要がありますが、今回は不要なので NULL を指定します。
その際、Windows Crate の CreateFileMappingW では Option<*const SECURITY_ATTRIBUTES>
として引数を渡す必要があるので、NULL ポインタを生成する標準ライブラリ関数 null_mut() を Option 型として渡すために Some() でくくっています。
参考:CreateFileMappingW in windows::Win32::System::Memory - Rust
ファイルをメモリにマッピングする
もう一方の readfileandcreatefile_maipping 関数では、ローカル端末の C:\Folder\test.txt
に存在している test.txt のファイルハンドルを取得し、CreateFileMappingW でプロセスメモリに割り当てを行っています。
let size: usize = 32;
let path = "C:\\Folder\\test.txt";
let file = OpenOptions::new().read(true).write(true).create(true).open(path).unwrap();
// let file_size = file.metadata().unwrap().len();
let h_file = HANDLE(file.as_raw_handle());
let h_mapping = CreateFileMappingW(
h_file,
Some(null_mut()),
PAGE_READWRITE,
0,
size as u32,
None,
).unwrap();
if h_mapping == INVALID_HANDLE_VALUE {
eprintln!("CreateFileMappingW failed");
} else {
println!("Mapping handle created: {:?}.", h_mapping);
}
ファイルハンドルの取得には標準ライブラリ関数 std::fs::OpenOptions
と std::os::windows::io::AsRawHandle
を使用しています。
OpenOptions 関数では以下の例のようにオプションを指定した上でファイルを開くことができます、
OpenOptions::new().open(path)
は Result<File>
を返すので、unwrap() で File オブジェクトを取り出しています。
参考:OpenOptions in std::fs - Rust
続けて、ここで取得した File オブジェクトから、AsRawHandle を使用して HANDLE オブジェクトを取得しています。これは Windows のみで利用可能な関数です。
参考:AsRawHandle in std::os::windows::io - Rust
この HANDLE オブジェクトはそのまま Windows Crate の CreateFileMappingW 関数の引数に使用することができるので、これを使用して test.txt のマッピングオブジェクトへのハンドルを取得しています。
MapViewOfFile でファイルマッピングをプロセスメモリにロードする
上記のコードで取得したファイルマッピングオブジェクトへのハンドルを引数として MapViewOfFile によりプロセスメモリへのロードを行い、そのアドレスを取得します。
let p_view = MapViewOfFile(h_mapping, FILE_MAP_ALL_ACCESS, 0, 0, size);
if p_view.Value.is_null() {
eprintln!("MapViewOfFile failed");
} else {
println!("MapViewOfFile created: {:?}.", p_view);
}
参考:MapViewOfFile in windows::Win32::System::Memory - Rust
アクセス権については読み書きが可能な FILE_MAP_ALL_ACCESS
を指定しています。
参考:MapViewOfFile 関数 (memoryapi.h) - Win32 apps | Microsoft Learn
マッピングしたメモリにデータを書き込む
最後に、プロセスメモリにマッピングされた領域に copy_nonoverlapping
でデータを書き込みします。
let data = b"Hello from Rust!";
copy_nonoverlapping(data.as_ptr(), p_view.Value as *mut u8, data.len());
println!("Data written to shared memory: {:?}", &data);
copy_nonoverlapping 関数は指定のアドレスに任意のサイズ分のデータをコピーする関数で、C の memcpy と同等の操作を行うことができます。
そのため、ハードコードしたテキストのポインタと、MapViewOfFile の返却した MEMORYMAPPEDVIEWADDRESS から .Value で取り出した `*mut cvoidを
*mut u8` にキャストしたポインタアドレスを引数として、プロセスメモリ内にデータの書き込みを行います。
参考:copy_nonoverlapping in std::ptr - Rust
このコードを実行することで、マッピングされているプロセスメモリの領域に指定したテキストが書き込まれます。
また、test.txt ファイルをマッピングした領域にデータを書き込むことで、対象ファイルに対して Hello from Rust!
を含む 32 バイト分のデータが書き込まれることを確認できます。
まとめ
Rust の勉強を兼ねて色々な Windows API を使っています。
少しずつ手になじんできた感じがあって嬉しいです。