質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

新規登録して質問してみよう
ただいま回答率
85.48%
Windows 10

Windows 10は、マイクロソフト社がリリースしたOSです。Modern UIを標準画面にした8.1から、10では再びデスクトップ主体に戻され、UIも変更されています。PCやスマホ、タブレットなど様々なデバイスに幅広く対応していることが特徴です。

Windows

Windowsは、マイクロソフト社が開発したオペレーティングシステムです。当初は、MS-DOSに変わるOSとして開発されました。 GUIを採用し、主にインテル系のCPUを搭載したコンピューターで動作します。Windows系OSのシェアは、90%を超えるといわれています。 パソコン用以外に、POSシステムやスマートフォンなどの携帯端末用、サーバ用のOSもあります。

Rust

Rustは、MoFoが支援するプログラミング言語。高速性を維持しつつも、メモリ管理を安全に行うことが可能な言語です。同じコンパイル言語であるC言語やC++では困難だったマルチスレッドを実装しやすく、並行性という点においても優れています。

Q&A

解決済

1回答

1526閲覧

RustのMS公式ラッパーAPI経由でCreateProcessWを叩くと60%くらいの確率で失敗する

v_v

総合スコア47

Windows 10

Windows 10は、マイクロソフト社がリリースしたOSです。Modern UIを標準画面にした8.1から、10では再びデスクトップ主体に戻され、UIも変更されています。PCやスマホ、タブレットなど様々なデバイスに幅広く対応していることが特徴です。

Windows

Windowsは、マイクロソフト社が開発したオペレーティングシステムです。当初は、MS-DOSに変わるOSとして開発されました。 GUIを採用し、主にインテル系のCPUを搭載したコンピューターで動作します。Windows系OSのシェアは、90%を超えるといわれています。 パソコン用以外に、POSシステムやスマートフォンなどの携帯端末用、サーバ用のOSもあります。

Rust

Rustは、MoFoが支援するプログラミング言語。高速性を維持しつつも、メモリ管理を安全に行うことが可能な言語です。同じコンパイル言語であるC言語やC++では困難だったマルチスレッドを実装しやすく、並行性という点においても優れています。

1グッド

0クリップ

投稿2022/03/26 14:20

編集2022/03/26 14:45

やりたいこと

下記のラッパーAPIの CreateProcessW を使い、プロセス(calc.exe)を起動することです。
RustのMicrosoft公式のWindows APIラッパーを使っています。
URL:https://github.com/microsoft/windows-rs

数回に1回程度起動しますが、大抵の場合失敗し、エラーコード(GetLastErrorで取得したもの)が
2(指定されたファイルが見つかりません。)と出てしまいます。

確実に存在するプログラム名・パスのはずなのに、数回に1回しか起動せず、しかも起動しない「理由が指定されたパスがない」と言う内容です。(もちろん、"C:\\Windows\\System32\\calc.exe"と絶対パス表記にしても同様の確率で失敗します。)
以下コードのどこに問題点ありそうでしょうか。

開発環境

OS: Windows 10 21H1 build 19043.1586
Rustツールチェイン:stable-x86_64-pc-windows-gnu (default)
Rustコンパイラ:rustc 1.59.0 (9d1b2106e 2022-02-23)
Cargo:cargo 1.59.0 (49d8809dc 2022-02-10)

コード(設定ファイル)

cargo.toml

1[package] 2name = "test" 3version = "0.1.0" 4edition = "2021" 5 6# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 7 8[dependencies] 9 10[dependencies.windows] 11version = "0.34.0" 12features = [ 13 "alloc", 14 "Data_Xml_Dom", 15 "Win32_Foundation", 16 "Win32_Security", 17 "Win32_System_Threading", 18 "Win32_UI_WindowsAndMessaging", 19 "Win32_Storage_FileSystem" 20] 21 22[dependencies.windows-sys] 23version = "0.34.0" 24features = [ 25 "Win32_Foundation", 26 "Win32_System_Diagnostics_Debug", 27 "Win32_Security", 28 "Win32_System_Threading", 29 "Win32_UI_WindowsAndMessaging", 30 "Win32_System_Kernel" 31] 32 33[profile.release] 34opt-level = "z" # Optimize for size. 35lto = true 36codegen-units = 1 37panic = "abort"

コード本体

main.rs

1pub(crate) fn main() { 2 unsafe { 3 process_test("calc.exe"); 4 } 5} 6 7use std::{ 8 ffi::{CString, OsStr, OsString}, 9 mem::size_of, 10 os::windows::prelude::OsStrExt, 11 ptr::{null, null_mut}, 12}; 13 14use windows::{ 15 core::{PCWSTR, PWSTR}, 16 Win32::{Foundation::*, Security, Storage::FileSystem::*, System::Threading::*}, 17}; 18 19use std::mem::zeroed; 20unsafe fn process_test(process: impl Into<String>) { 21 let mut process = HANDLE::default(); 22 let process = process.into(); 23 process = create_process(process.clone()); 24} 25 26unsafe fn create_process(command_line: String) -> HANDLE { 27 let mut start_info = zeroed::<STARTUPINFOW>(); 28 start_info.cb = size_of::<STARTUPINFOW>() as u32; 29 let mut proc_info = zeroed::<PROCESS_INFORMATION>(); 30 println!("command line: {}", command_line); 31 let command_line= PWSTR( 32 OsString::from(&command_line) 33 .encode_wide() 34 .collect::<Vec<u16>>() 35 .as_mut_ptr(), 36 ); 37 let err = CreateProcessW( 38 null(), 39 command_line, 40 null(), 41 null(), 42 false, 43 CREATE_SUSPENDED, // この状態で起動するとWindowは出ないがタスクマネージャでは確認できる 44 null(), 45 PCWSTR::default(), 46 &mut start_info, 47 &mut proc_info, 48 ); 49 if err == false { 50 println!("{:?}", GetLastError()); 51 } 52}

上記以外になにか不明点有りましたら追記いたしますので、何卒ご回答宜しくお願い致します。

tatsuya6502👍を押しています

気になる質問をクリップする

クリップした質問は、後からいつでもMYページで確認できます。

またクリップした質問に回答があった際、通知やメールを受け取ることができます。

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

cx20

2022/03/26 16:18

試そうと思ったのですが、windows-rs が更新された為か、コンパイルエラーが出て cargo build が通りませんでした。。
guest

回答1

0

ベストアンサー

渡しているコマンドが文字化けしているのが原因のようです。
以下は API Monitor というツールでトレースしてみた結果になります。
イメージ説明

下記のコードを試してもらえますか?(文字化け部分を改善したコードになります。)

rust:main.rs

1pub(crate) fn main() { 2 unsafe { 3 process_test("calc.exe".to_string()); 4 //process_test("notepad.exe".to_string()); 5 } 6} 7 8use std::{ 9 mem::size_of, 10 ptr::{null, null_mut}, 11}; 12 13use windows::{ 14 core::{PCWSTR, PWSTR}, 15 Win32::{ 16 Foundation::*, 17 System::Threading::* 18 }, 19}; 20 21use std::mem::zeroed; 22unsafe fn process_test(cmd_line: String) { 23 create_process(cmd_line) 24} 25 26unsafe fn create_process(command_line: String) { 27 let mut start_info = zeroed::<STARTUPINFOW>(); 28 start_info.cb = size_of::<STARTUPINFOW>() as u32; 29 let mut proc_info = zeroed::<PROCESS_INFORMATION>(); 30 println!("command line: {}", command_line); 31/* 32 let command_line = PWSTR( 33 OsString::from(&command_line) 34 .encode_wide() 35 .collect::<Vec<u16>>() 36 .as_mut_ptr(), 37 ); 38*/ 39 let command_line = 40 PWSTR(std::boxed::Box::<[u16]>::into_raw( 41 command_line 42 .encode_utf16() 43 .chain(std::iter::once(0)) 44 .collect::<std::vec::Vec<u16>>() 45 .into_boxed_slice(), 46 ) as _); 47 let err = CreateProcessW( 48 PCWSTR(null_mut()), 49 command_line, 50 null(), 51 null(), 52 false, 53 //CREATE_SUSPENDED, 54 PROCESS_CREATION_FLAGS(0u32), 55 null(), 56 PCWSTR::default(), 57 &mut start_info, 58 &mut proc_info, 59 ); 60 if err == false { 61 println!("{:?}", GetLastError()); 62 } 63}

投稿2022/03/26 20:13

cx20

総合スコア4633

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

v_v

2022/03/26 23:47

試したら確かに謎のルーレットみたいな未定義挙動がなくなってました。 遅い時間に回答いただき恐縮です。ありがとうございます。 これは、Rustから渡されたスタック上のポインタをAPIが参照していない、ということになるのでしょうかね? なので、Box化してヒープに置いてあげて、その値をAPIに読ませるという話でしょうか? なぜスタック上にあるポインタをAPIが読んでくれないかは謎ですが・・・ 考えてみます、ありがとうございます!
v_v

2022/03/27 00:31 編集

原因がわかりました。 API Monitorを教えていただきありがとうございます!!! 原因は、OsStringで生成したポインタ文字列にNULL終端文字が付いておらず 想定したバッファを超えて文字列が読まれてしまっていたことに起因したようです。 そのため、ある程度の確率でスタック上に存在する環境変数文字列を読みに行ってしまい "calc.exe"と渡したところが "calc.exeenv.dll" みたいな文字列になり そんなものはないよ!と言われたようです。 そのため、cx20様の回答にある「ヒープに入れる」と言う挙動によって、ヒープが想定した文字列+1の長さ以上の箇所に0がいい感じに入ったので事象が収まったように手元の環境では見えました。 本当にありがとうございます! というわけで以下のように修正した所うまくいきました。 let mut cmd_line = OsString::from(&cmd_line) .encode_wide() .collect::<Vec<u16>>(); cmd_line.push(0); // FFI用 終端文字を追加する。 let cmd_line = PWSTR(cmd_line.as_mut_ptr()); // バッファへのポインタを得る 「OsStringには、終端文字が付く」と思いこんでいたのでこのチェック観点が完全に抜けていました。 思わぬ落とし穴でした。
cx20

2022/03/27 00:55 編集

あぁ、文字化けが原因、というわけではなく、NULL終端が付いていなかったことが原因だったようですね。 (文字化けが原因だとしたら、うまくいくケースがあるのはおかしいな、と思っていましたw) ちなみに、こちらのコードは色々とググっていたときに見つけたコード断片で、私が考えたコードではありません。もう既に辿れないのですが URL を見つけたら追記しておきます。自分自身 Rust あまり理解していないので、勉強しないと、ですね・・ >   let command_line = >     PWSTR(std::boxed::Box::<[u16]>::into_raw( >       command_line >         .encode_utf16() >         .chain(std::iter::once(0)) >         .collect::<std::vec::Vec<u16>>() >         .into_boxed_slice(), >     ) as _);
cx20

2022/03/27 01:03

そういえば、cargo.toml に ・windows ・windows-sys の2つの crate が指定されていますが、windows-sys の方は未使用のようなので削ってしまって構わないと思います。 <参考> ■ widnows と windows-crate crate の比較 https://github.com/microsoft/windows-rs/pull/1314
SaitoAtsushi

2022/03/27 04:55 編集

ヌル終端が出来ていないというだけではなくダングリングポインタが生じています。 式の途中の結果は匿名の一時変数に束縛されたかのように動作し、ステートメントの終わりがその一時変数の終わりとして扱われます。 つまり、質問中のコードの let command_line = 省略; が終わった瞬間には Vec は drop されてしまい、ポインタは drop 済みのところを指している (いわゆるダングリングポインタ) ことになります。 似たような状況を解説している記事を見つけました。 https://qiita.com/dalance/items/a40bcf547215b69859fb
v_v

2022/03/27 06:47 編集

あー、なるほど。 まずOsStringで生成された配列を変数にバインドする必要があるんですね。 バインドせずにas_ptrで生成したポインタだけを変数に束縛すると 実体のVecが束縛されていないのでRustはその時点でライフタイムが無いことになって、 死んだポインタになってしまうわけですね。超絶理解です。 中途半端に1行で書こうとすると、こういう事故が起こるんですねw(特にunsafeの中では) とりあえずこの問題の原因は 1. 文字列がNul終端されていない  →明示的にNul終端しなければならない(っぽい) 2. オブジェクトの実体が束縛されずにポインタのみ束縛してしまい、ダングリングポインタとなった。  →Rust仕様(ライフタイム)の無理解  →ポインタを使う時は、実体と参照(ポインタ)の双方を変数に束縛しなければならない。 主にこんな雰囲気ですね 言われたら、めちゃくちゃなるほど!って納得できる内容でした。 ありがとうございます。
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

15分調べてもわからないことは
teratailで質問しよう!

ただいまの回答率
85.48%

質問をまとめることで
思考を整理して素早く解決

テンプレート機能で
簡単に質問をまとめる

質問する

関連した質問