Open22

🦀 Rust関連の参考資料

hzuikahzuika

https://qiita.com/benki/items/635867b654783da0322f

COMの流れは,
CoInitialize -> CoCreateInstanceで作ったオブジェクトで何かする -> CoUninitialize

COMのCoUninitialize()をDropトレイトに実装することで,スコープを抜けるときに実行されるようにしているのは良いなと思った.

struct Com;
impl Drop for Com {
    fn drop(&mut self) {
        unsafe {
            CoUninitialize();
        }
    }
}

fn main() -> Result<()> {
    CoInitialize(ptr::null())?;
    let _com = Com;

anyhowライブラリが使われている

参考記事

https://zenn.dev/yukinarit/articles/b39cd42820f29e

hzuikahzuika

プロジェクト作成から実行

cargo new project_name
cd project_name
cargo run
$OutputEncoding = [console]::InputEncoding = [console]::OutputEncoding = New-Object System.Text.UTF8Encoding
lsd --all --tree --classic | clip
.
├── .git
│   ├── config
│   ├── description
│   ├── HEAD
│   ├── hooks
│   │   └── README.sample
│   ├── info
│   │   └── exclude
│   ├── objects
│   │   ├── info
│   │   └── pack
│   └── refs
│       ├── heads
│       └── tags
├── .gitignore
├── Cargo.toml
└── src
    └── main.rs

GitリポジトリのPush(GitHub CLIを使用)

gh repo create project_name --private --source=. --remote=origin
git push -u origin main

https://cli.github.com/manual/gh_repo_create

hzuikahzuika

メッセージボックス

https://github.com/microsoft/windows-rs/blob/master/crates/samples/message_box/src/main.rs

windowsクレートのビルド時間短縮のために,Cargo.tomlのfeaturesで必要な機能を選ぶ.

サンプルコード
[package]
name = "message_box"
version = "0.1.0"
edition = "2021"

[dependencies.windows]
version = "0.43.0"
features = [
    "Win32_Foundation",
    "Win32_UI_WindowsAndMessaging",
]
use windows::{h, Win32::UI::WindowsAndMessaging::{MessageBoxW, MB_OK}};

fn main() {
    unsafe {
        MessageBoxW(None, h!("テキストの内容"), h!("ウィンドウタイトル"), MB_OK);
    }
}

hzuikahzuika

COM

Cargo.tomlのfeaturesにWin32_System_Comを追加する.

Cargo.toml
[dependencies.windows]
version = "0.43.0"
features = [
    "Win32_System_Com"
]

Win32::System::Com::* をインポートする.

main.rs
use windows::{
    Win32::System::Com::*,
};

https://microsoft.github.io/windows-docs-rs/doc/windows/Win32/System/Com/fn.CoInitializeEx.html

CoInitializeExを実行する.

(windows-rsの公式サンプルだと実行されていないが,念のため)CoUninitializeを呼び出しておく.
最後に実行したいので,Unit like struct の Dropトレイトに実装する
参考: Rust で COM を使って WAVE ファイルを再生してみる - Qiita

https://microsoft.github.io/windows-docs-rs/doc/windows/Win32/System/Com/fn.CoUninitialize.html

main.rs
struct Com;
impl Drop for Com {
    fn drop(&mut self) {
        unsafe {
            CoUninitialize();
        }
    }
}   

fn main() -> Result<()> {
    unsafe {
        CoInitializeEx(None, COINIT_MULTITHREADED)?;
        let _com = Com;
hzuikahzuika

TSF

Cargo.tomlにWin32_UI_TextServicesを追加する.

Cargo.toml
[dependencies.windows]
version = "0.43.0"
features = [
    "Win32_UI_TextServices"
]

main.rsでWin32::UI::TextServices::*モジュールを追加する.

main.rs
use windows::{
    Win32::UI::TextServices::*,
};
hzuikahzuika

スレッドマネージャーのActivate関数を呼び出して,クライアント識別子を取得する.
終了時にはDeactivate関数も忘れず呼び出す.
C++では,Activateの引数にTfClientIdへのポインタを渡してクライアント識別子を取得しているが,RustではActivateの引数がなく,戻り値がクライアント識別子になる.

Activateの戻り値がHRESULTではないことを確認した.

RustのActivateの戻り値はResult<u32>だけど,これはHRESULTなのかクライアント識別子なのか...?
でも,ITfClientIdは構造体として定義されているから違うか...?
TfClientIdITfClientIdは別物である.

TfClientIdtypedef DWORD TfClientId;である.
DWORDは32ビット符号なし整数である.(参考)
Activateの戻り値はS_OK , E_INVALIDARG , E_UNEXPECTED のいずれかである.(参考)
println!で表示してみると,

Return value of `Activate()` = 32, S_OK = 0x00000000, E_INVALIDARG = 0x80070057, E_UNEXPECTED = 0x8000FFFF

となった.

main.rs
        let client_id = thread_manager.Activate()?;

        //終了時に呼び出す.
        thread_manager.Deactivate()?;

https://github.com/microsoft/Windows-classic-samples/blob/7cbd99ac1d2b4a0beffbaba29ea63d024ceff700/Samples/Win7Samples/winui/input/tsf/tsfapps/tsfpad-step1/TsfPad.cpp#L50

https://microsoft.github.io/windows-docs-rs/doc/windows/Win32/UI/TextServices/struct.ITfThreadMgr.html#method.Activate

https://learn.microsoft.com/ja-jp/windows/win32/tsf/client-identifiers

https://microsoft.github.io/windows-docs-rs/doc/windows/Win32/UI/TextServices/struct.ITfClientId.html

hzuikahzuika

スレッドマネージャーを使って,ドキュメントマネージャーを作る

main.rs
        let document_manager = thread_manager.CreateDocumentMgr()?;
hzuikahzuika

ITextStoreACP インターフェイスを持つ構造体を作る.

ITextStoreACP_Implトレイトは,三つのfeaturesが必要なので,Cargo.tomlのfeaturesに追加する.

windows-0.43.0\src\Windows\Win32\UI\TextServices\impl.rs
#[cfg(all(feature = "Win32_Foundation", feature = "Win32_System_Com", feature = "Win32_System_Ole"))]
pub trait ITextStoreACP_Impl: Sized {

構造体定義と空のトレイトの実装を書いてビルドすれば,エラーメッセージにトレイトの関数がtodo!マクロと一緒に表示されるのでコピペすると楽.

main.rs
#[implement(ITextStoreACP)]
struct TextStore();
#[allow(non_snake_case)]
impl ITextStoreACP_Impl for TextStore {
}
hzuikahzuika

ドキュメントマネージャーを使ってコンテキストを作る

main.rs
        let text_store = TextStore;
        let mut input_context: Option<ITfContext> = None;
        let mut edit_cookie: u32 = 0;
        document_manager.CreateContext::<&ITextStoreACP>(client_id, 0, &text_store.into(), &mut input_context, &mut edit_cookie)?;
pub unsafe fn CreateContext<'a, P0>(
    &self,
    tidowner: u32,
    dwflags: u32,
    punk: P0,
    ppic: *mut Option<ITfContext>,
    pectextstore: *mut u32
) -> Result<()>
where
    P0: Into<InParam<'a, IUnknown>>,

https://microsoft.github.io/windows-docs-rs/doc/windows/Win32/UI/TextServices/struct.ITfDocumentMgr.html#method.CreateContext

punk: P0, P0: Into<InParam<'a, IUnknown>> は何をいれるべきか?

InParamの説明.

InParam<'a, T> に変換できる一般的なものを次に示します.
タイプ T の値への参照 (つまり、&'a T).
&'a T に変換できるものなら何でも.
たとえば、多くの COM インターフェイスには、親インターフェイスへのこのような変換があります。たとえば、関数が InParam<'a, IUnknown> を必要とする場合、IInspectable は IUnknown から継承されるため、&'a IInspectable を渡すことができます。

今回の場合は &ITextStoreACP を入れる.

https://microsoft.github.io/windows-docs-rs/doc/windows/core/struct.InParam.html

エラーが出たので修正した記録.

最初はこんな感じのコードを書いていた.

main.rs
        let text_store = TextStore;
        let input_context: ITfContext;
        let edit_cookie: u32;
        document_manager.CreateContext(client_id, 0, &text_store, &mut Some(input_context) as *mut _, &mut edit_cookie)?;
cargo build 
   Compiling tsf_sample_rust v0.1.0 (Z:\dev\tsf_sample_rust)
error[E0277]: the trait bound `&windows::core::IUnknown: From<&TextStore>` is not satisfied
    --> src\main.rs:236:54
     |
236  |         document_manager.CreateContext(client_id, 0, &text_store, &mut Some(input_context) as *mut _, &mut edit_cookie)?;
     |                          -------------               ^^^^^^^^^^^ the trait `From<&TextStore>` is not implemented for `&windows::core::IUnknown`
     |                          |
     |                          required by a bound introduced by this call
     |
     = help: the following other types implement trait `From<T>`:
               <&windows::core::IUnknown as From<&AsyncIAdviseSink2>>
               <&windows::core::IUnknown as From<&AsyncIAdviseSink>>
               <&windows::core::IUnknown as From<&AsyncIMultiQI>>
               <&windows::core::IUnknown as From<&AsyncIPipeByte>>
               <&windows::core::IUnknown as From<&AsyncIPipeDouble>>
               <&windows::core::IUnknown as From<&AsyncIPipeLong>>
               <&windows::core::IUnknown as From<&AsyncIUnknown>>
               <&windows::core::IUnknown as From<&IAccClientDocMgr>>
             and 1076 others
     = note: required for `&TextStore` to implement `Into<&windows::core::IUnknown>`
     = note: required for `windows::core::Borrowed<'_, windows::core::IUnknown>` to implement `From<&TextStore>`
     = note: 3 redundant requirements hidden
     = note: required for `&TextStore` to implement `Into<InParam<'_, windows::core::IUnknown>>`
note: required by a bound in `ITfDocumentMgr::CreateContext`
    --> C:\Users\hashi\scoop\persist\rustup-msvc\.cargo\registry\src\github.com-1ecc6299db9ec823\windows-0.43.0\src\Windows\Win32\UI\TextServices\mod.rs:4407:13      
     |
4407 |         P0: ::std::convert::Into<::windows::core::InParam<'a, ::windows::core::IUnknown>>,
     |             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ required by this bound in `ITfDocumentMgr::CreateContext`

For more information about this error, try `rustc --explain E0277`.
error: could not compile `tsf_sample_rust` due to previous error

このエラーのhelpにあるように,AsyncIAdviseSink2はimpl From<&AsyncIAdviseSink2> for &IUnknownを実装している.

https://microsoft.github.io/windows-docs-rs/doc/windows/Win32/System/Com/struct.AsyncIAdviseSink2.html#impl-From<%26AsyncIAdviseSink2>-for-%26IUnknown

ITextStoreACP も impl From<&ITextStoreACP> for &IUnknown を実装している.

https://microsoft.github.io/windows-docs-rs/doc/windows/Win32/UI/TextServices/struct.ITextStoreACP.html#impl-From<%26ITextStoreACP>-for-%26IUnknown

into()で変換して,<&ITextStoreACP>で変換先を明記してみる.
document_manager.CreateContext::<&ITextStoreACP>と,&text_store.into()にして解決できた.

main.rs
        document_manager.CreateContext::<&ITextStoreACP>(client_id, 0, &text_store.into(), &mut Some(input_context) as *mut _, &mut edit_cookie)?;

input_contextと,edit_cookieで初期化のエラーが出たので,修正した.

main.rs
        let text_store = TextStore;
        let mut input_context: Option<ITfContext> = None;
        let mut edit_cookie: u32 = 0;
        document_manager.CreateContext::<&ITextStoreACP>(client_id, 0, &text_store.into(), &mut input_context as *mut _, &mut edit_cookie)?;

&mut input_context as *mut _&mut input_contextにしてもエラーが出なかった.

C++での定義.
HRESULT CreateContext(
  [in]  TfClientId   tidOwner,
  [in]  DWORD        dwFlags,
  [in]  IUnknown     *punk,
  [out] ITfContext   **ppic,
  [out] TfEditCookie *pecTextStore
);

[in] punk
ITextStoreACP または ITfContextOwnerCompositionSink インターフェイスをサポートするオブジェクトへのポインター。 この値は NULL にすることができます。

https://learn.microsoft.com/ja-jp/windows/win32/api/msctf/nf-msctf-itfdocumentmgr-createcontext

hzuikahzuika
hzuikahzuika
hzuikahzuika

COMインターフェースの実装

GitHub

構造体定義時にimplementマクロを使う.
その際は,Cargo.tomlのfeaturesに"implement"を追加する.

windows = { version = "..", features = ["implement"] }

*_Implトレイトを実装する.
とりあえず impl COMインターフェイス名_Impl for 構造体名 {}を書いて,ビルドすれば,実装すべき関数がエラーメッセージとして出力されたので,それを{}の中にコピペすれば楽だった.

https://github.com/microsoft/windows-rs/blob/cfcea04457b595d3bfe5ef33e944e33355291407/crates/samples/core_app/src/main.rs#L13-L22

hzuikahzuika

RefCellを調べる

https://doc.rust-jp.rs/book-ja/ch15-05-interior-mutability.html

Rc<T>は、同じデータに複数の所有者
Box<T>とRefCell<T>は単独の所有者

Rc<T>では不変借用のみがコンパイル時に精査
Box<T>では、不変借用も可変借用もコンパイル時に精査
RefCell<T>では、不変借用も可変借用も実行時に精査

RefCell<T>は実行時に精査される可変借用を許可する
RefCell<T>が不変でも、 RefCell<T>内の値を可変化できる

https://doc.rust-lang.org/std/cell/struct.RefCell.html