技術秘話: ブラウザの探し方
本編の通信経路に入る前に、すべての始まりである「ブラウザが置いてある標準の場所」について話します。
ブラウザのインストール場所を探す
対応しているブラウザ
このツールは現時点で、次の組み合わせを自動で探せます。
| ブラウザ | 開発レベル(チャンネル) |
|---|---|
| Chrome | Stable / Beta / Dev / Canary |
| Edge | Stable / Beta / Dev / Canary |
また、好きな Chromium 系ブラウザも操作できるように、ブラウザのパスを直接指定することもできます。設定ワークシートで手軽に設定できます。
配置のパターン
Chromium 系ブラウザ × 開発レベルの配置には、決まったパターンがあります。
相対パス
[ブラウザメーカー名]\[ブラウザ名][開発レベル]\Application\[実行ファイル名].exe| ブラウザ | Stable | Beta | Dev | Canary |
|---|---|---|---|---|
| Chrome | Google\Chrome | Google\Chrome Beta | Google\Chrome Dev | Google\Chrome SxS |
| Edge | Microsoft\Edge | Microsoft\Edge Beta | Microsoft\Edge Dev | Microsoft\Edge SxS |
実行ファイル名は、Chrome が
chrome.exe、Edge がmsedge.exeです。フォルダ名の [ブラウザ名] と、実行ファイル名は必ずしも一致しませんStable には、開発レベルの名前が付きません。Canary は「SxS」になっています
SxS は「Side-by-Side」のことで、通常版と並べて同じ PC に入れられる版、という意味です。Chromium のソース(chrome/install_static/google_chrome_install_modes.h)では、Beta・Dev・Canary の3つを「the side-by-side … channel」と書き、そのうち Canary の名前にだけ「 SxS」を付けています。なお、SxS が Side-by-Side の略だと明記した一文は、ソースの中には見つかりませんでした。
親フォルダ
親フォルダは、インストールするときの権限で2つのルートに分かれます。
| ルート | 入る場所 | いつこうなるか |
|---|---|---|
| マシン単位 | %ProgramFiles% または %ProgramFiles(x86)% | 管理者権限で入れたとき |
| ユーザー単位 | %LOCALAPPDATA% | 管理者権限が無いとき。Canary は常にこちら |
製品版を配布ページから入れた場合、開発レベルとの関係は次のとおりです。
Chrome の Stable〜Dev:管理者権限があればマシン単位、無ければユーザー単位です。Google の配布ページ(
www.google.com/chrome/)の JavaScript は、Windows 向けのインストーラーにneedsadmin=prefersを付けています。Google Update(Omaha)のソース(omaha/common/const_goopdate.h)では、prefersは「権限があればマシン単位、無ければユーザー単位」と定義されています。管理者権限が無い場合や、UAC で「いいえ」を選んだ場合は、%LOCALAPPDATA%に入りますEdge の Stable〜Dev:マシン単位です。Microsoft の文書(WebView2 の配布の説明)に、Canary 以外の Edge にはマシン単位の Edge Updater が付いてくる、とあります。手元の PC の Edge Stable も、
C:\Program Files (x86)\Microsoft\Edge\Application\msedge.exeにありました。ただし、会社のポリシー(Edge Update の「Force Installs (Per-User)」)でユーザー単位にもできますCanary(Chrome・Edge):常にユーザー単位で、
%LOCALAPPDATA%に入ります。Chrome は配布ページが Canary だけneedsadmin=falseを付けており、Chromium のソース(google_chrome_install_modes.h)でも、Canary はマシン単位に対応していません(supports_system_level = false)。Edge も、マシン単位の Edge Updater が付いてこないのは Canary だけです
マシン単位のときに %ProgramFiles% と %ProgramFiles(x86)% のどちらになるかは、Chrome では入れた時期で変わります。
2020年6月の Chromium の変更(「Install 64-bit browser versions under "C:\Program Files" by default」)以降に新しく入れた 64bit 版は、
C:\Program Filesそれより前に
C:\Program Files (x86)に入れたものは、そこに入ったまま更新され続ける
ここまで押さえれば、自動で探すロジックは一通り作れます。ユーザー自身がインストール場所を変えない限り、このロジックで見つかります。
このツールが探す順番
Stable / Beta / Dev は、次の順に探し、最初に見つかった場所を使います。Canary は、ユーザー単位(%LOCALAPPDATA%)だけを見ます(CDPHost.cls の getBrowserPath)。
%ProgramFiles(x86)%(マシン単位。以前から入れている Chrome や、Edge)%ProgramW6432%(マシン単位。最近入れた 64bit 版の Chrome)%LOCALAPPDATA%(ユーザー単位。管理者権限が無かった、UAC で「いいえ」を選んだ、Edge のポリシーでユーザー単位にした、などの場合)
2番目に %ProgramFiles% ではなく %ProgramW6432% を使っているのは、32bit 版の Excel から %ProgramFiles% を読むと、C:\Program Files (x86) が返ってくるためです。%ProgramW6432% なら、Excel が 32bit でも 64bit でも C:\Program Files が返ります。この変数が取れない環境では、%ProgramFiles% を使います。
自分でビルドした Chromium の場合
Chromium のインストーラー(mini_installer.exe)を直接実行した場合は、既定がユーザー単位(%LOCALAPPDATA%)で、--system-level を付けたときだけマシン単位になります(chrome/installer/mini_installer/README.md)。このツールの自動探索は製品版を対象にしているので、汎用の Chromium は設定ワークシートでパスを指定してください。
レジストリを追えば、ユーザーが変えたインストール場所も特定できるかもしれません。ただ、このツールはそこまでは追いません。面倒なので…。
Puppeteer や Playwright も、そこまでは追っていません。
Puppeteer(
packages/browsers/src/browser-data/chrome.ts):PROGRAMFILES、ProgramW6432、ProgramFiles(x86)、LOCALAPPDATAの環境変数と、C:\Program Filesなどの予備の候補に、決まった相対パスをつなげて探しますPlaywright(
packages/playwright-core/src/server/registry/index.ts):LOCALAPPDATA、PROGRAMFILES、PROGRAMFILES(X86)、HOMEDRIVEのProgram Filesに、決まった相対パスをつなげて探します
どちらのソースにも、ブラウザの場所をレジストリから読む処理はありません。インストール場所を変えた場合は、このツールと同じく、パスを直接指定する作りです。このツールでは、ここは先ほどの設定ワークシートに任せています。
WebView2 について
このツールには、WebView2 を制御する仕組みも入っています。では、WebView2 はどうやって探しているのでしょうか。
実はこれは、WebView2Loader.dll に任せています。
「あれ、このツールって外部依存禁止の縛りじゃなかったっけ?」 はい、そうです。それは守っています。少なくとも、今の基準では。
実は、最新の Office(特に Microsoft 365)には、この WebView2Loader.dll が最初から入っています。手元の PC では、C:\Program Files\Microsoft Office\root\Office16\WebView2Loader.dll にありました(ファイルの製品名は「Microsoft Edge Embedded Browser WebView Loader」、バージョンは 1.0.3485.44)。
Microsoft の文書でも、Microsoft 365 Apps の Version 2101 以降には、2021年4月から WebView2 Runtime を入れ始めたこと、Office アドインが WebView2 を使っていることが書かれています。一方で、Office に WebView2Loader.dll を同梱していると書いた文書は、見つかりませんでした。
VBA は、Office のインストールのついでに入った WebView2Loader.dll を使っているだけです。なので、LoadLibrary で DLL の場所を指定する、といった外部 DLL を読み込むための手順は要りません。普段の Win32 API と同じ感覚で、宣言を書くだけで動きます。
このツールも、Declare で Lib "WebView2Loader" とパスなしで書いているだけです(WebView2Host.cls)。それでも動くのは、Windows が DLL を探す順番のおかげです(Microsoft Learn「Dynamic-link library search order」)。
- すでに読み込まれている DLL の一覧
- アプリケーションの実行ファイルがあるフォルダ
WebView2Loader.dll は EXCEL.EXE と同じ Office16 フォルダにあるので、パスを書かなくても見つかります。呼び出した後が本当の地獄なのですが、それは少し後のお話で…。
…つまり逆に言えば、WebView2Loader.dll が入っていない古い Office では動きません。古い Office の頃には、まだ WebView2 という存在すら知られていなかったので。なお、どのバージョンの Office から入っているかは、手元に古い Office が無いため確かめられていません。
WebView2Loader.dll すら使わない方法
これはまだ試していませんが、どうやら WebView2Loader.dll すら使わない POC が、ネットにまさかの形で転がっています。
簡潔に言うと、次のことをしています。
- レジストリから、WebView2 の置き場所を読む(
HKEY_LOCAL_MACHINE\Software\Microsoft\EdgeUpdate\ClientState\{F3017226-FE2A-4295-8BDF-00C3A9A7E4C5}のEBWebViewという値。手元の 64bit の Windows では、32bit 用のWOW6432Nodeの下にありました。OpenWebView2Loader も、32bit 側を見る指定KEY_WOW64_32KEYを付けて開いています) - その場所の
EBWebView\x64\EmbeddedBrowserWebView.dllを読み込む - その中の
CreateWebViewEnvironmentWithOptionsInternalという関数を呼ぶ
そうです。WebView2Loader.dll が最終的に読み込んで呼んでいるのは、この EmbeddedBrowserWebView.dll です。WebView2Loader.dll は名前からお察しの通り、WebView2 を探して読み込むだけの、言ってしまえば簡易なヘルパー DLL です。Qiita の記事でも、ローダー DLL がしていることは「レジストリを読んでWebView2のインストールフォルダを特定し、WebView2(COMサーバー)を起動するぐらい」と書かれています。
では、この DLL こそが WebView2 の本体なのかというと、そう単純でもありませんでした。その話は、後で実際に試したところでします。
置き場所は、ブラウザの探し方とは少し違います。
| チャンネル | 置き場所の例 |
|---|---|
| WebView2 Runtime(Stable) | %ProgramFiles(x86)%\Microsoft\EdgeWebView\Application\[バージョン]\EBWebView\[アーキテクチャ]\EmbeddedBrowserWebView.dll |
| Edge の Beta / Dev / Canary | 各ブラウザの Application\[バージョン]\EBWebView\[アーキテクチャ]\ |
Stable は、Edge ブラウザ本体ではなく、別に入っている WebView2 Runtime(
EdgeWebView)を使います。手元の PC のレジストリでも、上の{F3017226-…}は「Microsoft Edge WebView2 Runtime」で、EBWebViewの値はEdgeWebViewのフォルダを指していました。Edge Stable のフォルダにもEBWebViewはありますが、Edge Stable のレジストリにはEBWebViewの値がありませんでしたWebView2Loader の再実装(OpenWebView2Loader)も、同じ順番で探しています。探すチャンネルは、Stable(WebView2 Runtime)、Beta、Dev、Canary です
[アーキテクチャ] は、
x64、x86、arm64のどれかです。呼び出す側(Excel)のビット数に合わせます
見ての通り、一般的なブラウザの探し方よりも、次の情報が追加で必要になります。
バージョン(レジストリの値に含まれている)
アーキテクチャ(Excel のビット数)
これを自動で特定できれば、もしかしたら古い Office でも動くかもしれない…そんなロマンのあることが起こるかもしれないのです。
しかし、CreateWebViewEnvironmentWithOptionsInternal は、あくまでも非公開の API です。OpenWebView2Loader のソースにも「This routine is not documented!」(この関数は文書化されていない)とあり、その README も、たいていの用途では公式の WebView2Loader を使うべきだと書いています。Edge の更新で、突然動かなくなるリスクが増えます。
…が、時間があったらやってみる価値はあるかもしれません。
手に入るのは、「古い Office でも WebView2 が動く」。ただそれだけかもしれません。サポートが終わった製品まで気にしてしまうなんて、ボクはどうしてしまったんだろうね。
試してみた:exe のメタ情報から読み込む
…と書いていたら、思わぬところから道が開けました。
きっかけは、mapperTab.js をブラウザのバージョンに合わせて取ってくる仕組み(assset\WebDriverBiDiOnlineUpdater.bas)を作っていたときです。ブラウザの exe には、ファイルのバージョン情報(エクスプローラーのプロパティの「詳細」に出るもの)として、ブラウザのバージョンがそのまま入っています。VBA なら、Scripting.FileSystemObject の GetFileVersion で、ブラウザを起動せずに読めます(CDPHost.cls の getBrowserVersion)。
そして Edge は、exe とバージョン番号のフォルダが隣どうしに並んでいます。手元の PC では、次のようになっていました。
C:\Program Files (x86)\Microsoft\Edge\Application\
├── msedge.exe ← バージョン情報は 156.0.4314.50
└── 156.0.4314.50\
└── EBWebView\x64\EmbeddedBrowserWebView.dllつまり、exe のパスとバージョン情報があれば、EmbeddedBrowserWebView.dll の場所が決まります。先ほど「追加で必要」と書いたバージョンは、レジストリを読まなくても手に入ったわけです。{F3017226-…} のような中カッコの文字列も要りません。
実際に必要だった変更は、数行の追記だけでした。
1. DLL のパスを組み立てて、読み込む(CDPHost.cls の DirectLoadWebView2)
'このVBAを動かしてるビット数で、アーキテクチャのフォルダを決める
#If Win64 Then
UseBit = "x64"
#Else
UseBit = "x86"
#End If
'exeのパス・exeのバージョン情報・ビット数から、DLLの絶対パスを組み立てて読み込む
EmbeddedBrowserWebViewPath = getBrowserPath & "\..\" & getBrowserVersion & "\EBWebView\" & UseBit & "\EmbeddedBrowserWebView.dll"
DirectLoadWebView2 = LoadLibrary(EmbeddedBrowserWebViewPath)2. 関数を宣言する(WebView2Host.cls)
Private Declare PtrSafe Function CreateWebViewEnvironmentWithOptionsInternal Lib "EmbeddedBrowserWebView" ( _
ByVal unknown As Long, _
ByVal runtimeType As Long, _
ByVal userDataFolder As LongPtr, _
ByVal EnvironmentOptions As LongPtr, _
ByVal environmentCreatedHandler As LongPtr) As LongLib には、パスを書かずにモジュール名だけを書いています。それでも 1. で読み込んだ DLL が使われるのは、先ほどの「Windows が DLL を探す順番」の1番目、すでに読み込まれている DLL の一覧に入っているからです。WebView2Loader.dll が Office16 フォルダにあるおかげで見つかったのと同じ仕組みが、ここでも効いています。
3. 呼び出す(WebView2Host.cls の RunWebView2)
hr = CreateWebViewEnvironmentWithOptionsInternal(1, 0, StrPtr(WebView2UserDataDirPath), EnvironmentOptions, pEnvHandlerObj)引数の形は、OpenWebView2Loader に合わせました。
1番目は、OpenWebView2Loader が常に
trueを渡しているので、1で固定しています2番目の
0は、インストール版のランタイムを表す値です(OpenWebView2Loader のWebView2RunTimeTypeのkInstalled)。フォルダを指定した配布版を表す1(kRedistributable)との違いは、下で試しています残りの3つは、
WebView2Loader.dllのCreateCoreWebView2EnvironmentWithOptionsに渡していたものと同じです。CreateCoreWebView2EnvironmentWithOptionsにあった「ブラウザのフォルダ」の引数はありません
どちらで呼ぶかは、WebView2Host.cls の定数 UseEmbeddedBrowserWebView で切り替えます。DLL を読み込めなかった場合は、これまでどおり WebView2Loader.dll 経由で呼びます(今は、確かめやすいように Stop で止まるようにしています)。
…レジストリ操作なんて、不要だったんです🙃
…と思ったら、動いていたのは Edge 本体ではなかった
ところが、起動した WebView2 のバージョン(ICoreWebView2Environment の BrowserVersionString)を見てみると、予想外の結果になりました。
| 呼び方 | 読み込んだ DLL | BrowserVersionString |
|---|---|---|
WebView2Loader.dll 経由 | (ローダー任せ) | 155.0.4283.62 |
直接(2番目の引数が 0) | Edge 本体の DLL(156.0.4314.50) | 155.0.4283.62 |
直接(2番目の引数が 1) | Edge 本体の DLL(156.0.4314.50) | 失敗(0x80070032) |
直接(2番目の引数が 1) | WebView2 Runtime の DLL(155.0.4283.62) | 155.0.4283.62 |
Edge 本体の DLL を読み込んだのに、動いていたのは WebView2 Runtime(155.0.4283.62)でした。BrowserVersionString が返すのは、DLL のバージョンではなく、起動したブラウザのプロセス(msedgewebview2.exe)のバージョンです。
フォルダの中身を比べると、理由が見えてきます。
| Edge 本体(156.0.4314.50) | WebView2 Runtime(155.0.4283.62) | |
|---|---|---|
EBWebView\x64\EmbeddedBrowserWebView.dll | ある | ある |
msedgewebview2.exe | 無い(署名ファイルの msedgewebview2.exe.sig だけ) | ある |
Edge 本体のフォルダには、WebView2 のブラウザとして起動できる msedgewebview2.exe がありません。そのため、2番目の引数が 0(インストール版)のときは、DLL がインストール済みの WebView2 Runtime を起動したと考えられます。手元の PC で動いていた他のアプリ(Teams・OneDrive・Outlook など)の msedgewebview2.exe も、すべて WebView2 Runtime のフォルダから起動されていました。DLL が Runtime をどうやって見つけているのか(DLL の中でレジストリを読んでいるのか、など)は、確かめられていません。
2番目の引数を 1(フォルダ指定の配布版)にすると、0x80070032 で失敗しました。これは Win32 のエラー 50(ERROR_NOT_SUPPORTED、要求はサポートされていません)で、「ファイルが見つからない」(0x80070002)ではありません。Edge 本体のフォルダは、配布版のランタイムとしては扱えないようです。
では、msedgewebview2.exe がある WebView2 Runtime のフォルダから DLL を読み込み、1 で呼ぶとどうなるか。こちらは動き、155.0.4283.62 になりました。
2番目の引数と、3つの役割
ここまでの結果から、2番目の引数は次のように働いていると考えられます。
0(kInstalled):DLL が、インストール済みの WebView2 Runtime を自分で探して起動する。DLL をどのフォルダから読み込んだかは関係ない1(kRedistributable):DLL が、自分の隣にあるmsedgewebview2.exeを起動する。隣に無ければ「サポートされていません」で失敗する。Microsoft が「Fixed Version」として配布している、フォルダごと持ち運ぶ版の Runtime を使うときの動きだと思われます
0 のときの DLL は、「インストール済みの Runtime を探して起動する」という点で、WebView2Loader.dll とほぼ同じ役割をしています。ただし、再実装の OpenWebView2Loader は、ほかにもチャンネルの優先順位(Beta・Dev・Canary を優先する設定)や、ポリシー・環境変数による上書き、最低バージョン(86.0.616.0)の確認をしています。DLL の側が同じことをしているかは、確かめていません。手元の PC には Edge の Beta・Dev・Canary が入っていないので、チャンネルの優先順位は試せていません。
そして、本当の意味で WebView2 の本体と呼べるのは、EmbeddedBrowserWebView.dll ではなく、起動される msedgewebview2.exe のほうでした。描画も、JavaScript の実行も、CDP も、この exe のプロセスで動いています。BrowserVersionString が返すのも、この exe のバージョンです。
アプリ(VBA / Excel)
│ CreateCoreWebView2EnvironmentWithOptions
▼
WebView2Loader.dll … DLL を探して読み込むだけ(ローダーのローダー)
│ LoadLibrary → CreateWebViewEnvironmentWithOptionsInternal
▼
EmbeddedBrowserWebView.dll … アプリのプロセスの中で動く窓口。ブラウザを選んで起動し、COM のオブジェクトを渡す
│ プロセスをまたいだやり取り
▼
msedgewebview2.exe … 本当のブラウザ(Chromium)OpenWebView2Loader のソースも、EmbeddedBrowserWebView.dll を「client DLL」と呼んでいます。関数名は FindInstalledClientDll や CreateWebViewEnvironmentWithClientDll で、読み込んだ DLL を入れる変数の名前も clientDll です。「コア」ではなく、ブラウザにつなぐ側の部品という扱いです。DLL と exe の間でどんなやり取りをしているかは、確かめていません。
まとめると、この方法で省けたのは、「DLL を探すためのレジストリ」と「WebView2Loader.dll」です。どのブラウザで動くかは、2番目の引数で決まります。0 なら DLL が探したインストール済みの Runtime、1 なら DLL の隣の msedgewebview2.exe です。
DLL とブラウザをそろえたいなら、WebView2 Runtime のフォルダの DLL を、1 で呼ぶのが筋が良さそうです。DLL とブラウザが同じフォルダ・同じバージョンになり、どの Runtime を使うかもツールの側で決められます。WebView2 Runtime のフォルダには exe が直下に無いので、バージョンのフォルダを列挙して探すことになります(これもレジストリは要りません)。今の DirectLoadWebView2 は、Edge 本体のフォルダの DLL を 0 で呼ぶ作りです。
この方法の注意点
Edge 本体の DLL を
0で呼ぶと、DLL とブラウザのバージョンがずれます。 読み込むのは Edge 本体の DLL(手元の PC では 156.0.4314.50)で、起動するのは WebView2 Runtime(155.0.4283.62)です。本物のWebView2Loader.dllは、同じフォルダの DLL とブラウザを組み合わせて使います。今は動いていますが、DLL とブラウザの間のやり取りが食い違うと、動かなくなるおそれがあります。WebView2 Runtime のフォルダの DLL を1で呼べば、そろえられます(上の「2番目の引数と、3つの役割」)。なお、DirectLoadWebView2が今対象にしているのは、Edge 系統のパスだけですポリシーによる上書きが効かない可能性があります。 再実装の OpenWebView2Loader は、
Software\Policies\Microsoft\Edge\WebView2\などのレジストリや、環境変数WEBVIEW2_BROWSER_EXECUTABLE_FOLDERによる上書きを見ています。この方法は、ローダーのこの処理を通りません。DLL の側が同じ設定を見ているかは、確かめていません非公開の API であることは変わりません。 Edge の更新で関数の名前や引数が変わると、動かなくなります。
WebView2Loader.dll経由に戻せる作りを残しているのは、このためです古い Office で動くかは、まだ確かめていません。 手元に古い Office が無いためです。ロマンは、まだロマンのままです
さて、ブラウザの探し方はこの辺にして、次は本編の各通信経路について話します。最初は Pipe 経路です。
参考資料
chrome/installer/mini_installer/README.md(Chromium のインストーラーを直接実行したときの、マシン単位とユーザー単位のインストール場所)
google/omaha omaha/common/const_goopdate.h(
needsadminのprefersの定義)Chromium の updater の仕様(functional_spec.md)(
needsadmin=true/prefersのときに管理者として起動し直す動き)Reland "Install 64-bit browser versions under "C:\Program Files" by default"(2020年6月)
Distribute your app and the WebView2 Runtime(Microsoft Learn)(Canary 以外の Edge にはマシン単位の Edge Updater が付いてくる)
Microsoft Edge Update policies(Microsoft Learn)(マシン単位とユーザー単位のポリシー)
chrome/install_static/google_chrome_install_modes.h(side-by-side のチャンネル、
SxS、Canary はマシン単位に非対応)Playwright packages/playwright-core/src/server/registry/index.ts
OpenWebView2Loader(WebView2Loader の再実装)
WebView2をC言語から利用する(WebView2Loader.dll不使用)(Qiita) / takazin11de/my_webview2

