75051


Rust/WinRTでクリップボードの中身を取得しようと以下のコードを書いたのですが実行しても`await`の部分で止まって正常に動きません。コンパイルエラーは発生していませんでした。

[code]

    use bindings::Windows::ApplicationModel::DataTransfer::Clipboard;
    use futures::executor::block_on;
    
    async fn clipboard() -> windows::Result<()> {
        let content = Clipboard::GetContent()?.GetTextAsync()?;
        //下で止まる
        let text = content.await?;
        print!("{}", text);
        Ok(())
    }
    
    fn main() -> windows::Result<()> {
        block_on(clipboard())
    }
    
[/code]

一応`await`の部分を`get()`に書き換えてみたりもしましたが同様に動作しませんでした。

[code]

    use bindings::Windows::ApplicationModel::DataTransfer::Clipboard;
    
    fn main() -> windows::Result<()> {
        let content = Clipboard::GetContent()?.GetTextAsync()?
        //同様に下で止まる
        let text = content.get()?;
        print!("{}", text);
        Ok(())
    }
    
[/code]

正しく動作する方法をご存知の方がいらっしゃいましたらご教示ください。


CC BY-SA 4.0
2021-04-01T09:42:08.030
75019
2021-04-02T16:17:27.577
44589
question
windows-10 rust
Rust/WinRTでクリップボードのデータを取得できない


この2つのGitHub issueによりますと、

  * <https://github.com/microsoft/windows-rs/issues/187>
  * <https://github.com/microsoft/windows-rs/issues/317>

クリップボードのようなDataTransfer APIを使うには以下の2点が必要なようです。（さもなければハングするようです）

  * UIスレッドがあること
  * UIスレッドをブロックしないこと

私はWindowsアプリの開発経験がないため、どういうコードを書けばいいのかまではわかりませんでした。

Rust/WinRTには以下のようなサンプルアプリがありますので、それらを見るとUIスレッドについて何かわかるかもしれません。

  * <https://github.com/robmikh/minesweeper-rs>
  * <https://github.com/microsoft/windows-rs/tree/master/examples>


CC BY-SA 4.0
2021-04-01T11:05:41.233
75026
2021-04-01T11:05:41.233
14101
75019
answer


DataTransferのClipboardを使う場合は、メッセージループがあるスレッドでClipboardのAPIを呼び出さなければなりません。  
<https://github.com/microsoft/windows-rs/issues/317#issuecomment-683430909>

あわせてfuturesを使う場合は、[`LocalPool`](https://docs.rs/futures/0.3.13/futures/executor/struct.LocalPool.html)を作り[`try_run_one`](https://docs.rs/futures/0.3.13/futures/executor/struct.LocalPool.html#method.try_run_one)を使えば1つのスレッドでブロックしない形でClipboardのAPIが含まれたタスクを処理することができます。

上記を踏まえ、こちらで動作を確認したコードが以下となります。  
ここではタスクが完了するまでメッセージがない時いわゆるアイドル状態の時に`try_run_one`を呼び出しループを回しています。

[code]

    use bindings::Windows::{
        ApplicationModel::DataTransfer::Clipboard,
        Win32::SystemServices::BOOL,
        Win32::WindowsAndMessaging::{
            DispatchMessageW, PeekMessageW, PeekMessage_wRemoveMsg, PostQuitMessage, HWND, MSG, WM_QUIT,
        },
    };
    use futures::executor::LocalPool;
    use futures::task::LocalSpawnExt;
    
    async fn clipboard() -> windows::Result<()> {
        let content = Clipboard::GetContent()?.GetTextAsync()?;
        let text = content.await?;
        println!("{}", text);
        Ok(())
    }
    
    fn main() {
        let mut pool = LocalPool::new();
        pool.spawner()
            .spawn_local(async {
                clipboard().await.unwrap();
            })
            .unwrap();
        let mut msg = MSG::default();
        unsafe {
            while msg.message != WM_QUIT {
                if PeekMessageW(&mut msg, HWND(0), 0, 0, PeekMessage_wRemoveMsg::PM_REMOVE) == BOOL(1) {
                    DispatchMessageW(&msg);
                } else if pool.try_run_one() {
                    PostQuitMessage(0);
                }
            }
        }
    }
    
[/code]

ちなみに`Clipboard::GetContent()`を呼び出すと（Win32APIで言うGUIスレッドと同等と思われる）UIスレッドになりデータも取得できたので、ClipboardのAPIの呼び出し前にUIスレッドになっている必要はないと思われます。


CC BY-SA 4.0
2021-04-02T15:37:53.497
75051
2021-04-02T16:17:27.577
2021-04-02T16:17:27.577
25520
25520
75019
answer