eduwin WebView2 模块 API
头文件:通过
eduwin.h统一引用(@public_api已自动合并)
封装 Microsoft Edge WebView2,在 Eduwin 窗口中嵌入现代浏览器引擎。纯 C 实现,通过 COM lpVtbl + 手写 vtable 回调接入 WebView2 的三层 API(Loader flat 入口 / ICoreWebView2 接口调用 / 异步 CompletedHandler 回调)。
两种 Runtime 模式:
| 模式 | Runtime 来源 | 包体 | 客户端要求 |
|---|---|---|---|
| Evergreen(默认) | 系统共享 | — | 需预装 Runtime |
| Fixed Version | 应用本地打包 | ~150MB | 零依赖 |
⚠️ Runtime 获取:
eduwin_pub.zip不含 WebView2 Runtime(体积过大)。
- Evergreen(推荐开发/测试用):下载 Evergreen Bootstrapper(约 2MB),双击安装,重启即可
- Fixed Version(推荐分发用):下载 Fixed Version Runtime(x64,约 150MB),解压后目录传给
webview_set_runtime_path()两种下载均在同一页面,选择 "Evergreen Bootstrapper" 或 "Fixed Version" → 架构选 x64。
初始化 / 清理
void webview_set_runtime_path(const WCHAR *path); /* 必须在 webview_init() 之前 */
int webview_init(void);
void webview_cleanup(void);
webview_set_runtime_path() 设置 Runtime 路径。NULL = Evergreen 模式(默认),传路径 = Fixed Version 模式。
webview_init() 必须在 init_app() 之前调用。返回值 0 成功,非零失败。
webview_cleanup() 在 run_app() 返回后调用,释放全局 COM 环境。
/* Fixed Version:客户端零依赖 */
webview_set_runtime_path(L"../runtime/");
if (webview_init() != 0) {
init_app();
alert(L"错误", L"WebView2 Runtime 初始化失败");
return 1;
}
/*
* Evergreen(默认):
* 不调 webview_set_runtime_path,webview_init() 自动用系统 Runtime。
* Windows 11 已预装,Windows 10 需手动安装。
*/
init_app();
// ... 创建 WebView 窗口 ...
run_app();
webview_cleanup();
创建 WebView
HWND create_webview(HWND parent, int x, int y, int w, int h);
在父窗口中创建 WebView2 子控件。参数与 create_button / create_label 一致:(x, y) 左上角坐标,(w, h) 宽高。
WebView 的 Environment 和 Controller 是异步创建的,函数返回后 WebView 尚未就绪——需配合 on_webview_ready 回调使用。
HWND win = create_window(L"WebView Demo", 960, 640);
HWND wv = create_webview(win, 0, 0, 960, 600);
导航 & 内容
void webview_navigate(HWND hwv, const WCHAR *url);
void webview_load_html(HWND hwv, const WCHAR *html);
webview_navigate 导航到指定 URL。通常在 on_webview_ready 回调中调用,确保 WebView 已就绪。
webview_load_html 加载 HTML 字符串(<html>...</html>)。
如果在 ready 之前调用,请求会自动排队,WebView 就绪后执行。
static void on_ready(HWND hwv, void *data) {
webview_navigate(hwv, L"https://www.bing.com");
// 或:
// webview_load_html(hwv, L"<html><body><h1>Hello</h1></body></html>");
}
就绪回调
typedef void (*webview_ready_fn)(HWND hwv, void *data);
void on_webview_ready(HWND hwv, webview_ready_fn cb, void *data);
WebView2 异步初始化完成后触发。这是安全调用 webview_navigate / webview_load_html / webview_post_message 的时机。
JS 双向通信
C 与 WebView 中页面可通过以下两种方式进行双向通信。
方式一:原始 postMessage
typedef void (*webview_message_fn)(HWND hwv, const char *json, void *data);
void on_webview_message(HWND hwv, webview_message_fn cb, void *data);
void webview_post_message(HWND hwv, const char *json);
C → JS: webview_post_message 向 WebView 中页面发送 JSON 字符串。页面通过 window.chrome.webview.addEventListener('message', ...) 接收。
JS → C: 页面通过 window.chrome.webview.postMessage(json) 发送消息,C 端通过 on_webview_message 注册的回调接收。
// C 端接收 JS 消息
static void on_msg(HWND hwv, const char *json, void *data) {
// json 是从 JS 发来的字符串
log_info(L"收到 JS 消息");
// 回复消息给 JS
webview_post_message(hwv, "{\"reply\": \"你好 JS\"}");
}
// 注册
on_webview_message(wv, on_msg, NULL);
页面端示例:
// JS 端发送消息到 C
window.chrome.webview.postMessage(JSON.stringify({ type: "click", value: 42 }));
// JS 端接收 C 消息
window.chrome.webview.addEventListener("message", (e) => {
console.log("C 发来:", e.data);
});
方式二:Bridge(推荐)
Bridge 在页面启动时自动注入 window.eduwin 对象,封装了原始 postMessage,提供更简洁的双向调用方式。
void webview_bridge_init(HWND hwv);
启用 Bridge。调用后 WebView 中所有页面自动获得 window.eduwin.on_message(cb) 和 window.eduwin.call(func, params, callback) 方法。
typedef void (*webview_bridge_fn)(HWND hwv, const char *func_name, const char *params_json, void *data);
void webview_bridge_register(const char *func_name, webview_bridge_fn cb, void *data);
注册一个 C 端 bridge 处理函数,供 JS 通过 eduwin.call(func_name, params) 调用。params_json 是参数字符串(JSON 格式),如 {"name":"eduwin"}。
/* C 端注册 */
static void on_hello(HWND hwv, const char *func, const char *params, void *d) {
webview_post_message(hwv, "{\"reply\":\"你好!\"}");
}
webview_bridge_register("hello", on_hello, NULL);
/* JS 端调用 */
eduwin.call("hello", { name: "eduwin" }, function (r) {
console.log(r.reply); // "你好!"
});
Bridge 的底层仍基于 postMessage,消息类型为 { type: "bridge_call", func, params, id }。C 端自动路由到已注册的处理函数,未被识别为 bridge 的消息仍会传给 on_webview_message 回调。
导航控制
void webview_back(HWND hwv);
void webview_forward(HWND hwv);
void webview_reload(HWND hwv);
浏览器的后退、前进、刷新操作。仅在 WebView 就绪后有效。
导航完成回调
typedef void (*webview_nav_complete_fn)(HWND hwv, int success, int status_code, void *data);
void on_navigation_completed(HWND hwv, webview_nav_complete_fn cb, void *data);
注册导航完成回调。页面加载完成后触发,success 为 1 表示成功,0 表示失败。
static void on_nav(HWND hwv, int success, int code, void *d) {
set_text(g_status, success ? L"Done" : L"Failed");
}
on_navigation_completed(wv, on_nav, NULL);
Virtual Host — 加载本地网页文件
将本地文件夹映射为虚拟域名,绕过 file:/// 的限制,正常加载外部 HTML/CSS/JS。
void webview_set_virtual_host(HWND hwv, const WCHAR *host_name, const WCHAR *folder_path);
host_name 为虚拟域名(如 L"eduwin.local"),folder_path 为本地文件夹路径(相对路径基于 exe 所在目录,也支持绝对路径)。
/* 映射当前 exe 目录到 eduwin.local */
webview_set_virtual_host(hwv, L"eduwin.local", L".");
/* 然后通过 https 访问本地文件 */
webview_navigate(hwv, L"https://eduwin.local/index.html");
Virtual Host 的访问权限为
COREWEBVIEW2_HOST_RESOURCE_ACCESS_KIND_DENY_CORS,禁止跨域请求但允许正常加载同域资源。
完整示例
参见:
samples/webview_browser/—— 简易浏览器(工具栏 + 地址栏 + 状态栏)samples/webview_bridge/—— Bridge 演示(C ↔ JS 双向通信 + 外部 HTML/CSS/JS 加载)
链接库
使用 WebView2 模块时,build.bat 需额外链接:
link ... WebView2LoaderStatic.lib ole32.lib oleaut32.lib version.lib advapi32.lib
| 库 | 用途 |
|---|---|
WebView2LoaderStatic.lib | WebView2 环境加载器 |
ole32.lib | COM 基础(CoInitializeEx 等) |
oleaut32.lib | COM 自动化 |
version.lib | 版本信息查询 |
advapi32.lib | 注册表(检测 WebView2 Runtime) |
实现架构
eduwin_webview.c (纯 C)
├── 模式 A: Flat API ── CreateCoreWebView2EnvironmentWithOptions(...)
├── 模式 B: lpVtbl ──── controller->lpVtbl->put_Bounds(...)
│ webview->lpVtbl->Navigate(...)
└── 模式 C: 回调 vtable ─ env_handler_t / ctrl_handler_t / msg_handler_t
(手写 QueryInterface / AddRef / Release / Invoke)
三种 COM 互操作模式全部在纯 C 中完成,未引入 .cpp 文件。