Open4

🪟 windows-rsの文字列操作を理解する

ピン留めされたアイテム
sheeplasheepla

🦀「文字列ってnつあんねん」🪟「文字列ってmつあんねん」

🐑「文字列がいっぱい...なにこれ...

Rustを使ったWindows APIプログラミングでは、Rustの文字列に関連する型とWindows API固有の文字列型の扱い方を両方理解する必要がある。この記事では、windows-rsに入門する上でのガイドになるよういろいろな型の扱い方を整理してみる。

sheeplasheepla

🦀 Rust の基本的な文字列に関連する型のおさらい

基本的な文字列型

  • String:
    ヒープに確保される可変の文字列型。Rust において最も一般的に使用される文字列型であり、常にUTF-8でエンコードされている。

  • &str:
    文字列スライス。通常 String や文字列リテラルの一部を参照する。UTF-8 エンコードされたバイト列への不変参照。文字列のリテラルを作ると &strになる。

OSのネイティブ文字列

std::ffi名前空間には、OSネイティブな文字列を統合的に扱うための型である OsStringOsStr が用意されている。OS固有のインタラクション例えば、ファイルパス、環境変数などにおける低レベルな操作に使われる。
内部的にはWindowsではUTF-16 (Vec<u16>)が、Unixでは(システムのロケールに依存するが一般には)UTF-8 Vec<u8>が使われる。

  • OsString:
    OS ネイティブの文字列を表す可変な型。String&str への変換が比較的安価。

  • OsStr:
    OS ネイティブの文字列のスライスを表す不変の型。&strString への変換は不可逆で、効率的に扱えるが、非 Unicode のデータが含まれている可能性がある。

ファイルパス

std::path 名前空間には、OSに依存しない形でファイルパスを扱えるようにするための専用の型である PathPathBufが用意されている。

  • &Path:
    ファイルシステム上のパスを表す不変参照型。Rust の std::path::Path で使用され、プラットフォームに依存しない方法でパスを管理できる。

  • PathBuf:
    &Path の可変で所有権を持つバージョン。パスの操作や変更が可能で、&Path に変換できる。

バイト列

  • Vec<u8>:
    バイトの可変配列。直接的な文字列型ではないが、UTF-8 や他のエンコーディングの文字列を表すために使用可能。

  • &[u8]:
    バイト列スライス。不変なバイトシーケンスを表し、UTF-8 エンコードされた文字列などに利用される。

  • Vec<u16>:
    16 ビット符号なし整数の可変配列。直接的な文字列型ではないが、UTF-16 エンコードされた文字列を表すのに使用可能。

  • &[u16]:
    16 ビット符号なし整数のスライス。UTF-16 文字列のシーケンスを表すために使用される。

sheeplasheepla

🪟 Windows 固有の文字列型 (windows_strings クレート)

Windows API との相互運用のためにいろいろな文字列が仕様される。以下は Windows でよく使われる windows_strings クレートの文字列型。

WinRT 文字列

  • HSTRING
    Windows Runtime (WinRT) で使用される、参照カウントされた不変の UTF-16 文字列。API 間で効率的に共有されるよう設計されている。

COM (Component Object Model) 文字列

  • BSTR
    COM プログラミングで使用される、長さが前置された可変の UTF-16 文字列。BSTR は、null 文字を含むことができるのが特徴。

C++では通常、BSTRのメモリ確保には SysAllocStringSysAllocStringLen を、メモリの解放には SysFreeString を明示的に呼び出す必要があった。
一方、Rustの場合はBSTRの作成時に SysAllocString が呼び出され、drop時に自動で SysFreeString が呼び出されるらしい。便利!

impl BSTR {
    // -- 中略 --
    pub fn from_wide(value: &[u16]) -> Self {
        if value.is_empty() {
            return Self::new();
        }
        // BSTRの作成時にSysAllocStringLenを呼び出している
        let result = unsafe {
            Self(bindings::SysAllocStringLen(
                value.as_ptr(),
                value.len().try_into().unwrap(),
            ))
        };

        if result.is_empty() {
            panic!("allocation failed");
        }

        result
    }
}
impl Drop for BSTR {
    fn drop(&mut self) {
        if !self.0.is_null() {
            unsafe { bindings::SysFreeString(self.0) } // drop時にSysFreeStringを呼び出している
        }
    }
}

https://docs.rs/windows-strings/latest/src/windows_strings/bstr.rs.html#160-166

C スタイルの文字列 (null 終端文字列ポインタ)

低レベルの Windows API 呼び出しや相互運用で広く使用される。

  • PSTR
    可変の ANSI 文字列へのポインタ (*mut u8)。レガシーな Windows API で使用される ANSI エンコーディング (Windows-1252 やシステムロケール) を表す。

  • PCSTR
    不変の ANSI 文字列へのポインタ (*const u8)。読み取り専用の ANSI 文字列が必要な関数で使用。

  • PWSTR
    可変の UTF-16 ワイド文字列へのポインタ (*mut u16)。Windows API で Unicode 文字列 (ファイルパス、レジストリ値など) に使用。

  • PCWSTR
    不変の UTF-16 ワイド文字列へのポインタ (*const u16)。読み取り専用のワイド文字列が必要な関数で使用。

sheeplasheepla

🦀→🪟 Rust の文字列型からWindows固有の文字列型への変換

  • Rustの文字列からnull終端ワイド文字列(PWSTR, PCWSTR)を作るには、HSTRING を経由することで実現できる。
  • windows::coreには文字列リテラルからWindowsで使われる文字列型への変換を行える h!, s!, w!マクロが用意されている。

それぞれの型の変換方法を図にすると次のようになる。