Open8

C#のコンソールアプリ(headless)でWebView2を使ってレンダリングをする(ことが出来なかった時のメモ)

kenichiudakenichiuda

コンソールアプリで、svgファイルのレンダリングをしたい。これのWebView2版。

スレッド モデル - Microsoft Edge Development | Microsoft Docsには、

WebView2 は UI スレッド上に作成する必要があります。 具体的には、メッセージ ポンプを持つスレッドです。 そのスレッドですべてのコールバックが発生し、WebView2 への要求は、そのスレッドで実行する必要があります。 別のスレッドから WebView2 を使用しても安全ではありません。

と記載があるので、コンソールアプリでも手動でメッセージポンプを回せばできるはず。

kenichiudakenichiuda

始めにWebView2.EnsureCoreWebView2Asyncを呼び出す必要があるっぽいので、
下記の様なコードを書いて動かすと…エラーになる。

NG
        [STAThread]
        static async Task Main(string[] args)
        {
            Application.EnableVisualStyles();
            Application.SetCompatibleTextRenderingDefault(false);

            WebView2 webview = new WebView2();
            await webview.EnsureCoreWebView2Async();
        }
System.Runtime.InteropServices.COMException
  HResult=0x80010106
  Message=スレッド モードを設定してから変更することはできません。 (HRESULT からの例外:0x80010106 (RPC_E_CHANGED_MODE))
  Source=mscorlib
  スタック トレース:
   at System.Runtime.InteropServices.Marshal.ThrowExceptionForHRInternal(Int32 errorCode, IntPtr errorInfo)
   at System.Runtime.InteropServices.Marshal.ThrowExceptionForHR(Int32 errorCode)

「は?STAなのになんで?」って思って「async/awaitの呼び出し方が悪い?」とか「WebView2のバグ?」とか思ったけど、結局は
async Mainは本当のメインメソッドじゃない(コンパイラー生成のMainがいる)ので、[STAThread]は意味がないことに気付くまで結構時間がかかった。

メインメソッドは別にした。

[STAThread]
static void Main(string[] args)
{
    AsyncMain(args).Wait();
}
static async Task AsyncMain(string[] args)
{
    //...
}
kenichiudakenichiuda

手動でメッセージループを回すには、例によってApplication.DoEvents();

static void WaitTask(Task task)
{
    while (!task.IsCompleted)
    {
        Application.DoEvents();
    }
    if (task.Exception != null)
    {
        throw task.Exception;
    }
}
// ...
WaitTask(webView.EnsureCoreWebView2Async());
// ...

例外もチェックしないと、誤りに気付かないので注意。

kenichiudakenichiuda

WebView2.NavigateToStringを呼び出した後のナビゲーション完了した後に処理したいけど、メインメソッドで待ち合わせをしないといけない。

SpinWaitとかの使いどころな気がするけど、メッセージループを回せないので状態変数でビジーループする。

webView.NavigationCompleted += WebView_NavigationCompletedAsync;
// ...
webView.NavigateToString(content);

while (completed == false)
    Application.DoEvents();
// ...
static volatile bool completed = false;
private static void WebView_NavigationCompletedAsync(object? sender, Microsoft.Web.WebView2.Core.CoreWebView2NavigationCompletedEventArgs e)
{
    completed = true;
}
kenichiudakenichiuda

さていよいよレンダリング。
まずControl.DrawToBitmapは期待薄だが、予想通りなにも出力されない。
(これはFormsから呼び出しても一緒。)

WebView2.CoreWebView2.CapturePreviewAsyncを試したけど、Taskがいつまでも完了しない。
Formsから試すと大丈夫なので、何かが間違っている。

Visible = true; Show();してもダメ。

AdditionalBrowserArguments--disable-gpuを指定しても変わらず。
--headlessを付けると、EnsureCoreWebView2AsyncのTaskが完了しない。

詰んだ。以上。