🦀 Rust関連の参考資料

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ライブラリが使われている
参考記事

プロジェクト作成から実行
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

公式のsampleプログラムがたくさんある
CoCreateInstance
で検索すれば,COMが使われているsampleを探すことができる.
全部 CoInitializeEx(None, COINIT_MULTITHREADED)?;
を使っている.
ドキュメントには,
CoInitializeEx の呼び出しが成功するたびに、スレッドが終了する前に CoUninitialize を呼び出す必要があります。
と書かれているが,RustのサンプルではCoUninitializeが呼ばれていない.
CoInitializeExの解説記事

windowsクレートを使ったマインスイーパーのサンプル

CalculatorのWindowを探して,実行ファイル名を出力する

ウィンドウの作成

メッセージボックス
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);
}
}

Rustの開発ツール
clippy リントツール

COM
Cargo.tomlのfeaturesにWin32_System_Com
を追加する.
[dependencies.windows]
version = "0.43.0"
features = [
"Win32_System_Com"
]
Win32::System::Com::*
をインポートする.
use windows::{
Win32::System::Com::*,
};
CoInitializeEx
を実行する.
(windows-rsの公式サンプルだと実行されていないが,念のため)CoUninitialize
を呼び出しておく.
最後に実行したいので,Unit like struct の Dropトレイトに実装する
参考: Rust で COM を使って WAVE ファイルを再生してみる - Qiita
struct Com;
impl Drop for Com {
fn drop(&mut self) {
unsafe {
CoUninitialize();
}
}
}
fn main() -> Result<()> {
unsafe {
CoInitializeEx(None, COINIT_MULTITHREADED)?;
let _com = Com;

TSF
Cargo.tomlにWin32_UI_TextServices
を追加する.
[dependencies.windows]
version = "0.43.0"
features = [
"Win32_UI_TextServices"
]
main.rsでWin32::UI::TextServices::*
モジュールを追加する.
use windows::{
Win32::UI::TextServices::*,
};

スレッドマネージャーをCoCreateInstance
で作成する.
let thread_manager: ITfThreadMgr = CoCreateInstance(&CLSID_TF_ThreadMgr, None, CLSCTX_INPROC_SERVER)?;

スレッドマネージャーのActivate
関数を呼び出して,クライアント識別子を取得する.
終了時にはDeactivate
関数も忘れず呼び出す.
C++では,Activateの引数にTfClientId
へのポインタを渡してクライアント識別子を取得しているが,RustではActivateの引数がなく,戻り値がクライアント識別子になる.
Activateの戻り値がHRESULTではないことを確認した.
RustのActivateの戻り値はResult<u32>だけど,これはHRESULTなのかクライアント識別子なのか...?
でも,ITfClientId
は構造体として定義されているから違うか...?
TfClientId
とITfClientId
は別物である.
TfClientId
はtypedef 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
となった.
let client_id = thread_manager.Activate()?;
//終了時に呼び出す.
thread_manager.Deactivate()?;

スレッドマネージャーを使って,ドキュメントマネージャーを作る
let document_manager = thread_manager.CreateDocumentMgr()?;

ITextStoreACP インターフェイスを持つ構造体を作る.
ITextStoreACP_Impl
トレイトは,三つのfeaturesが必要なので,Cargo.tomlのfeaturesに追加する.
#[cfg(all(feature = "Win32_Foundation", feature = "Win32_System_Com", feature = "Win32_System_Ole"))]
pub trait ITextStoreACP_Impl: Sized {
構造体定義と空のトレイトの実装を書いてビルドすれば,エラーメッセージにトレイトの関数がtodo!マクロと一緒に表示されるのでコピペすると楽.
#[implement(ITextStoreACP)]
struct TextStore();
#[allow(non_snake_case)]
impl ITextStoreACP_Impl for TextStore {
}

ドキュメントマネージャーを使ってコンテキストを作る
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>>,
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
を入れる.
エラーが出たので修正した記録.
最初はこんな感じのコードを書いていた.
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
を実装している.
ITextStoreACP も impl From<&ITextStoreACP> for &IUnknown
を実装している.
into()
で変換して,<&ITextStoreACP>で変換先を明記してみる.
document_manager.CreateContext::<&ITextStoreACP>
と,&text_store.into()
にして解決できた.
document_manager.CreateContext::<&ITextStoreACP>(client_id, 0, &text_store.into(), &mut Some(input_context) as *mut _, &mut edit_cookie)?;
input_contextと,edit_cookieで初期化のエラーが出たので,修正した.
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 にすることができます。

ドキュメントマネージャーにコンテキストをPush, Pop
document_manager.Push(&input_context.unwrap())?;
// 終了時
document_manager.Pop(TF_POPF_ALL)?;
Optionの中身をunwrapで取り出した.

スレッドマネージャーのAssociateFocusでウィンドウハンドルとドキュメントマネージャーを関連付ける.
AssociateFocusを使わずにWM_SETFOCUS
時にpThreadMgr->SetFocus(pDocMgr);
としてもよい.
pub unsafe fn AssociateFocus<'a, P0, P1>(
&self,
hwnd: P0,
pdimnew: P1
) -> Result<ITfDocumentMgr>
where
P0: Into<HWND>,
P1: Into<InParam<'a, ITfDocumentMgr>>,
hwndはWM_CREATE受信時の引数.

ウィンドウプロシージャ (wndproc)
CreateWindowExで渡していたselfをWM_NCCREATE (WM_CREATEの前に送信される通知)か,WM_CREATEで受け取り,SetWindowLongPtrで設定する.
それ以降はGetWindowLongPtrで取得する.

COMインターフェースの実装
構造体定義時にimplementマクロを使う.
その際は,Cargo.tomlのfeaturesに"implement"
を追加する.
windows = { version = "..", features = ["implement"] }
*_Impl
トレイトを実装する.
とりあえず impl COMインターフェイス名_Impl for 構造体名 {}
を書いて,ビルドすれば,実装すべき関数がエラーメッセージとして出力されたので,それを{}
の中にコピペすれば楽だった.

RefCellを調べる
Rc<T>は、同じデータに複数の所有者
Box<T>とRefCell<T>は単独の所有者
Rc<T>では不変借用のみがコンパイル時に精査
Box<T>では、不変借用も可変借用もコンパイル時に精査
RefCell<T>では、不変借用も可変借用も実行時に精査
RefCell<T>は実行時に精査される可変借用を許可する
RefCell<T>が不変でも、 RefCell<T>内の値を可変化できる