【MFC】WebView2: Edge (Chromium) をアプリに埋め込む

【MFC】WebView2: Edge (Chromium) をアプリに埋め込む

レガシーな MFC アプリの中に、CHtmlView を使った画面が残っていることがあります。以前は社内のヘルプ画面や簡単な HTML 表示で使われていましたが、最近のログイン画面や JavaScript を使ったページでは正しく動かないことがあります。
この記事では、MFC アプリに WebView2 を組み込むときに確認する SDK、Runtime、Loader DLL と、MFC 側で必要になる親ウィンドウ、表示範囲、リサイズ処理を解説します。


目次

まず確認するもの

WebView2 では、ビルド時に必要なものと実行時に必要なものが分かれています。WebView2.h が見つかるだけでは、まだ表示できるとは限りません。

確認するもの役割
WebView2 SDKWebView2.h や COM インターフェイスを使うための開発用パッケージ
WebView2 RuntimeEdge Chromium ベースで実際に表示するための実行環境
WebView2Loader.dllアプリから WebView2 Runtime を読み込むための DLL
MFC アプリ内に WebView2 の HTML 表示が埋め込まれている実行結果

最初に SDK を追加し、次に Runtime と Loader DLL を確認します。そのうえで、MFC 側ではどの HWND に WebView2 をぶら下げるか、どの範囲に表示するかを決めます。


WebView2 とは

WebView2 は、Microsoft Edge Chromium のレンダリングエンジンをデスクトップアプリケーションに埋め込むためのコントロールです。MFC から見ると、「MFC のウィンドウ内に、Edge ベースのブラウザー領域を子要素として作る」イメージです。

CHtmlView は IE ベースのため、現代的な JavaScript、CSS、認証画面、管理画面 UI などで問題が出やすくなります。WebView2 へ移行すると、Edge Chromium ベースの表示に置き換えられます。


手順 1: NuGet パッケージを追加する

Visual Studio の NuGet パッケージマネージャーで、対象プロジェクトに次のパッケージを追加します。

  • Microsoft.Web.WebView2

追加後、C++ 側で次のヘッダーを参照できるようになります。

#include <WebView2.h>
#include <wrl.h>
using Microsoft::WRL::ComPtr;

ただし、WebView2.h が見えるだけでは実行確認として不十分です。実行時には WebView2Loader.dll と WebView2 Runtime も必要です。NuGet 復元後に出力フォルダーへ Loader DLL がコピーされているかも確認します。


手順 2: WebView2 を表示する矩形を決める

WebView2 は、MFC のビューやダイアログに自動で配置されるわけではありません。親ウィンドウの中で、どの範囲をブラウザー領域にするかを RECT として決めます。

CRect hostRect;
GetClientRect(&hostRect);
hostRect.DeflateRect(12, 96, 12, 64);

サンプルでは、上部に説明領域、中央に WebView2 の host rectangle、下部にステータス欄を置いています。実際の HTML 表示は、この中央の矩形に対して行います。


手順 3: 非同期初期化の順番を崩さない

WebView2 の初期化は非同期で進みます。環境を作り、Controller を作り、ICoreWebView2 を取得し、最後に Navigate または NavigateToString で表示します。

// メンバ変数
ComPtr<ICoreWebView2Controller> m_controller;
ComPtr<ICoreWebView2> m_webView;

// 親 HWND が有効になった後で呼ぶ
void CMainFrame::StartWebView()
{
    CreateCoreWebView2EnvironmentWithOptions(
        nullptr,
        nullptr,
        nullptr,
        Callback<ICoreWebView2CreateCoreWebView2EnvironmentCompletedHandler>(
            [this](HRESULT result, ICoreWebView2Environment* env) -> HRESULT
            {
                if (FAILED(result) || env == nullptr)
                {
                    return S_OK;
                }

                env->CreateCoreWebView2Controller(
                    m_hWnd,
                    Callback<ICoreWebView2CreateCoreWebView2ControllerCompletedHandler>(
                        [this](HRESULT controllerResult, ICoreWebView2Controller* controller) -> HRESULT
                        {
                            if (FAILED(controllerResult) || controller == nullptr)
                            {
                                return S_OK;
                            }

                            m_controller = controller;
                            m_controller->get_CoreWebView2(&m_webView);

                            RECT bounds = HostBounds();
                            m_controller->put_Bounds(bounds);

                            m_webView->NavigateToString(L"<html><body>WebView2 OK</body></html>");
                            return S_OK;
                        }).Get());
                return S_OK;
            }).Get());
}

よくある失敗は、親ウィンドウの HWND がまだ有効でないタイミングで初期化することです。MFC では、ビューなら OnCreate 以降、ダイアログなら OnInitDialog 以降に寄せると整理しやすくなります。


手順 4: リサイズ時に Bounds を更新する

WebView2 の Controller は、MFC のレイアウト変更に自動追従しません。親ウィンドウのサイズが変わったら、put_Bounds を呼び直します。

void CMainFrame::OnSize(UINT nType, int cx, int cy)
{
    __super::OnSize(nType, cx, cy);

    LayoutClient(CRect(0, 0, cx, cy));

    if (m_controller)
    {
        RECT bounds = HostBounds();
        m_controller->put_Bounds(bounds);
    }
}

この更新を入れておくと、ウィンドウをリサイズしたときに WebView2 の表示領域も MFC 側のレイアウトに追従します。


C++ と JavaScript の連携

WebView2 では、C++ 側から JavaScript を実行できます。たとえば、表示中ページのタイトルを取得する場合は ExecuteScript を使います。

m_webView->ExecuteScript(
    L"document.title",
    Callback<ICoreWebView2ExecuteScriptCompletedHandler>(
        [](HRESULT hr, LPCWSTR result) -> HRESULT
        {
            // result には JSON エンコードされた戻り値が入る
            TRACE(_T("Title: %s\n"), result);
            return S_OK;
        }).Get());

ExecuteScript の戻り値は、そのまま文字列ではなく JSON エンコードされた結果として返る点に注意してください。


CHtmlView からの移行で見る対応関係

CHtmlView / IEWebView2 / Edge Chromium
Navigate2ICoreWebView2::Navigate
GetHtmlDocumentExecuteScript や Web 側の DOM API
ActiveX / IE コンポーネントWebView2 COM API
IE レンダリングEdge Chromium レンダリング

移行時は、単純なメソッド名の置き換えだけでは済まないことがあります。特に、DOM を直接触っていた処理は JavaScript 実行や Web 側のイベント設計へ寄せる必要があります。


まとめ

  • SDK が入ったら、WebView2.h の検出で止めず、実際の WebView2 表示まで確認する
  • MFC 側では、親 HWND、host rectangle、非同期初期化、OnSize の Bounds 更新が重要
  • NuGet 追加後も、実行時には WebView2Loader.dll と WebView2 Runtime が必要
  • CHtmlView からの移行では、DOM 操作や JavaScript 連携の設計も見直す
目次