C#のコンソールアプリ(headless)でWebView2を使ってレンダリングをする(ことが出来なかった時のメモ)
コンソールアプリで、svgファイルのレンダリングをしたい。これのWebView2版。
スレッド モデル - Microsoft Edge Development | Microsoft Docsには、
WebView2 は UI スレッド上に作成する必要があります。 具体的には、メッセージ ポンプを持つスレッドです。 そのスレッドですべてのコールバックが発生し、WebView2 への要求は、そのスレッドで実行する必要があります。 別のスレッドから WebView2 を使用しても安全ではありません。
と記載があるので、コンソールアプリでも手動でメッセージポンプを回せばできるはず。
始めにWebView2.EnsureCoreWebView2Async
を呼び出す必要があるっぽいので、
下記の様なコードを書いて動かすと…エラーになる。
[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)
{
//...
}
手動でメッセージループを回すには、例によってApplication.DoEvents();
static void WaitTask(Task task)
{
while (!task.IsCompleted)
{
Application.DoEvents();
}
if (task.Exception != null)
{
throw task.Exception;
}
}
// ...
WaitTask(webView.EnsureCoreWebView2Async());
// ...
例外もチェックしないと、誤りに気付かないので注意。
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;
}
さていよいよレンダリング。
まずControl.DrawToBitmap
は期待薄だが、予想通りなにも出力されない。
(これはFormsから呼び出しても一緒。)
WebView2.CoreWebView2.CapturePreviewAsync
を試したけど、Taskがいつまでも完了しない。
Formsから試すと大丈夫なので、何かが間違っている。
Visible = true;
Show();
してもダメ。
AdditionalBrowserArguments
で--disable-gpu
を指定しても変わらず。
--headless
を付けると、EnsureCoreWebView2Async
のTaskが完了しない。
詰んだ。以上。
WS_EX_NOREDIRECTIONBITMAP
を使ったら変わるかも…?
このIssueが解決すればできるようになるかも…