also looking at this
fix(windows): initialise COM before creating the WebView2 environment
#348
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.
| @@ -1088,39 +1088,60 @@ threadlocal var com_apartment_present = false; | ||
| 1088 | 1088 | /// a `CoUninitialize` against a `CoInitializeEx` that never took a reference. |
| 1089 | 1089 | threadlocal var com_initialized_here = false; |
| 1090 | 1090 | |
| 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. | |
| 1092 | 1092 | /// |
| 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. | |
| 1098 | 1098 | /// |
| 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: | |
| 1101 | 1116 | /// |
| 1102 | 1117 | /// - `S_OK` — this call created the apartment. |
| 1103 | 1118 | /// - `S_FALSE` — the thread already had one in the same mode and this call |
| 1104 | 1119 | /// 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. | |
| 1112 | fn 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. | |
| 1131 | fn ensureComInitialized() !void { | |
| 1113 | 1132 | if (com_apartment_present) return; |
| 1114 | 1133 | |
| 1115 | 1134 | const hr = CoInitializeEx(null, COINIT_APARTMENTTHREADED); |
| 1116 | 1135 | 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; | |
| 1120 | 1141 | } |
| 1121 | 1142 | if (!succeeded(hr)) { |
| 1122 | 1143 | std.debug.print("[WebView2] CoInitializeEx failed: 0x{x}\n", .{@as(u32, @bitCast(hr))}); |
| 1123 | return; | |
| 1144 | return error.ComInitializationFailed; | |
| 1124 | 1145 | } |
| 1125 | 1146 | |
| 1126 | 1147 | com_apartment_present = true; |
| @@ -1830,10 +1851,11 @@ pub const Window = struct { | ||
| 1830 | 1851 | y: i32, |
| 1831 | 1852 | |
| 1832 | 1853 | 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(); | |
| 1837 | 1859 | |
| 1838 | 1860 | const hInstance = GetModuleHandleW(null) orelse return error.WindowCreationFailed; |
| 1839 | 1861 | |