ReviewOS

also looking at this

craft-native/craft

fix(windows): initialise COM before creating the WebView2 environment

#348
Closed glennmichael123 wants to merge fix/windows-com-init into main
1 file +99 -0

Review threads live on the whole diff, not on one commit, so none are shown here - a thread's line means something in the branch's final form, and painting it into an intermediate step would put it on code it is not about.

packages/zig/src/windows.zigmodified+46-24
Changes to packages/zig/src/windows.zig
@@ -1088,39 +1088,60 @@ threadlocal var com_apartment_present = false;
10881088/// a `CoUninitialize` against a `CoInitializeEx` that never took a reference.
10891089threadlocal var com_initialized_here = false;
10901090
1091/// Put the calling thread in a COM apartment, which WebView2 requires.
1091/// Put the calling thread in a single-threaded apartment, which WebView2 needs.
10921092///
1093/// `CreateCoreWebView2EnvironmentWithOptions` fails with `CO_E_NOTINITIALIZED`
1094/// (0x800401f0) when the calling thread has no apartment, and craft used to
1095/// create none: `ole32` was linked for `CoTaskMemFree` alone. An embedder that
1096/// spawns craft with `CREATE_NO_WINDOW` gets no console, nothing else
1097/// initialises COM on its behalf, and no window is ever created.
1093/// Craft used to create none — `ole32` was linked for `CoTaskMemFree` alone —
1094/// and an embedder that spawns it with `CREATE_NO_WINDOW` gets no console,
1095/// nothing else initialises COM on its behalf, and
1096/// `CreateCoreWebView2EnvironmentWithOptions` fails with
1097/// `CO_E_NOTINITIALIZED` before any window exists.
10981098///
1099/// An STA, because WebView2 hosts UI on the thread that pumps messages. The
1100/// three outcomes are not the same thing and are not treated as such:
1099/// The apartment model is not a preference here. Microsoft's reference for
1100/// that function is explicit:
1101///
1102/// > The WebView2 environment and all other WebView2 objects are single
1103/// > threaded and have dependencies on Windows components that require COM
1104/// > to be initialized for a single-threaded apartment. The app is expected
1105/// > to run `CoInitializeEx` before running
1106/// > `CreateCoreWebView2EnvironmentWithOptions`.
1107///
1108/// > If `CoInitializeEx` did not run or previously ran with
1109/// > `COINIT_MULTITHREADED`, `CreateCoreWebView2EnvironmentWithOptions`
1110/// > fails with one of the following errors.
1111/// > CO_E_NOTINITIALIZED — if CoInitializeEx was not called
1112/// > RPC_E_CHANGED_MODE — if CoInitializeEx was previously called with
1113/// > COINIT_MULTITHREADED
1114///
1115/// So each result gets the treatment it deserves:
11011116///
11021117/// - `S_OK` — this call created the apartment.
11031118/// - `S_FALSE` — the thread already had one in the same mode and this call
11041119/// took a reference on it. Ours to release, like `S_OK`.
1105/// - `RPC_E_CHANGED_MODE` — the thread is already in an apartment with a
1106/// different concurrency model, so an embedder chose an MTA before craft
1107/// got here. Nothing was initialised and nothing may be released, but COM
1108/// is usable and the error above cannot occur, so this is not a failure
1109/// and craft does not override a choice that is the embedder's to make.
1110/// It is reported, because WebView2 expects an STA and that is where to
1111/// start looking if it misbehaves.
1112fn ensureComInitialized() void {
1120/// - `RPC_E_CHANGED_MODE` — the thread is in a multithreaded apartment.
1121/// Nothing was initialised and nothing may be released, and crucially
1122/// WebView2 will *not* work here: environment creation fails with this
1123/// same code. An earlier version of this carried on and warned, which only
1124/// moved the failure later and made it harder to read. It now refuses, so
1125/// the thread that is wrong is named at the point it is wrong. Hosting the
1126/// webview on a dedicated STA thread would also answer this, and is a
1127/// larger change than one call site.
1128/// - anything else — initialisation genuinely failed. Refused for the same
1129/// reason: continuing only produces `CO_E_NOTINITIALIZED` further along,
1130/// where it says less about what went wrong.
1131fn ensureComInitialized() !void {
11131132 if (com_apartment_present) return;
11141133
11151134 const hr = CoInitializeEx(null, COINIT_APARTMENTTHREADED);
11161135 if (hr == RPC_E_CHANGED_MODE) {
1117 std.debug.print("[WebView2] COM is already initialised on this thread as an MTA; WebView2 expects an STA\n", .{});
1118 com_apartment_present = true;
1119 return;
1136 std.debug.print(
1137 "[WebView2] This thread is in a multithreaded apartment. WebView2 requires a single-threaded apartment and would fail with RPC_E_CHANGED_MODE; create the window on a thread that is an STA, or one craft can initialise.\n",
1138 .{},
1139 );
1140 return error.ComApartmentIsMultithreaded;
11201141 }
11211142 if (!succeeded(hr)) {
11221143 std.debug.print("[WebView2] CoInitializeEx failed: 0x{x}\n", .{@as(u32, @bitCast(hr))});
1123 return;
1144 return error.ComInitializationFailed;
11241145 }
11251146
11261147 com_apartment_present = true;
@@ -1830,10 +1851,11 @@ pub const Window = struct {
18301851 y: i32,
18311852
18321853 pub fn create(options: .WindowOptions) !Window {
1833 // Before anything asks WebView2 for an environment. See
1834 // ensureComInitialized: without an apartment on this thread, creation
1835 // fails with CO_E_NOTINITIALIZED and no window is ever created.
1836 ensureComInitialized();
1854 // Before anything asks WebView2 for an environment, and fatal when it
1855 // cannot be done: every reason it fails is also a reason environment
1856 // creation would fail, so carrying on would only report the same
1857 // problem later and less clearly.
1858 try ensureComInitialized();
18371859
18381860 const hInstance = GetModuleHandleW(null) orelse return error.WindowCreationFailed;
18391861