Skip to content

技術秘話: ブラウザの探し方 ​

本編の通信経路に入る前に、すべての始まりである「ブラウザが置いてある標準の場所」について話します。

ブラウザのインストール場所を探す ​

対応しているブラウザ ​

このツールは現時点で、次の組み合わせを自動で探せます。

ブラウザ開発レベル(チャンネル)
ChromeStable / Beta / Dev / Canary
EdgeStable / Beta / Dev / Canary

また、好きな Chromium 系ブラウザも操作できるように、ブラウザのパスを直接指定することもできます。設定ワークシートで手軽に設定できます。

配置のパターン ​

Chromium 系ブラウザ × 開発レベルの配置には、決まったパターンがあります。

相対パス

[ブラウザメーカー名]\[ブラウザ名][開発レベル]\Application\[実行ファイル名].exe
ブラウザStableBetaDevCanary
ChromeGoogle\ChromeGoogle\Chrome BetaGoogle\Chrome DevGoogle\Chrome SxS
EdgeMicrosoft\EdgeMicrosoft\Edge BetaMicrosoft\Edge DevMicrosoft\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)。

  1. %ProgramFiles(x86)%(マシン単位。以前から入れている Chrome や、Edge)
  2. %ProgramW6432%(マシン単位。最近入れた 64bit 版の Chrome)
  3. %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」)。

  1. すでに読み込まれている DLL の一覧
  2. アプリケーションの実行ファイルがあるフォルダ

WebView2Loader.dll は EXCEL.EXE と同じ Office16 フォルダにあるので、パスを書かなくても見つかります。呼び出した後が本当の地獄なのですが、それは少し後のお話で…。

…つまり逆に言えば、WebView2Loader.dll が入っていない古い Office では動きません。古い Office の頃には、まだ WebView2 という存在すら知られていなかったので。なお、どのバージョンの Office から入っているかは、手元に古い Office が無いため確かめられていません。

WebView2Loader.dll すら使わない方法 ​

これはまだ試していませんが、どうやら WebView2Loader.dll すら使わない POC が、ネットにまさかの形で転がっています。

簡潔に言うと、次のことをしています。

  1. レジストリから、WebView2 の置き場所を読む(HKEY_LOCAL_MACHINE\Software\Microsoft\EdgeUpdate\ClientState\{F3017226-FE2A-4295-8BDF-00C3A9A7E4C5} の EBWebView という値。手元の 64bit の Windows では、32bit 用の WOW6432Node の下にありました。OpenWebView2Loader も、32bit 側を見る指定 KEY_WOW64_32KEY を付けて開いています)
  2. その場所の EBWebView\x64\EmbeddedBrowserWebView.dll を読み込む
  3. その中の 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 では、次のようになっていました。

text
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)

vb
'この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)

vb
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 Long

Lib には、パスを書かずにモジュール名だけを書いています。それでも 1. で読み込んだ DLL が使われるのは、先ほどの「Windows が DLL を探す順番」の1番目、すでに読み込まれている DLL の一覧に入っているからです。WebView2Loader.dll が Office16 フォルダにあるおかげで見つかったのと同じ仕組みが、ここでも効いています。

3. 呼び出す(WebView2Host.cls の RunWebView2)

vb
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)を見てみると、予想外の結果になりました。

呼び方読み込んだ DLLBrowserVersionString
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 のバージョンです。

text
アプリ(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 経路です。

参考資料 ​

Excel VBA × CDP / WebDriver BiDi