尧图建网站 尧图建网站 YAOTU WEB BUILD 免费咨询
ARTICLE DETAIL

资讯详情

深耕网站建设与建站编程的一线实战洞察。

web-view桥接实战:3步打通Rust与JavaScript双向通信

web-view桥接实战:3步打通Rust与JavaScript双向通信 web-view桥接实战3步打通Rust与JavaScript双向通信【免费下载链接】web-viewRust bindings for webview, a tiny cross-platform library to render web-based GUIs for desktop applications项目地址: https://gitcode.com/gh_mirrors/we/web-viewweb-view是Rust调用系统浏览器内核渲染桌面应用的库界面用HTMLJS写、逻辑用Rust写。读完能3步打通Rust与JavaScript双向通信JS发指令给Rust、Rust改完状态推回前端。全程就三个动作——Rust注册回调、JS发指令、Rust推回渲染下面按三条链路拆开讲每条都配最小代码和验证方法。JS调Rust给invoke_handler挂上window.external.invoke按钮点了半天Rust侧一行日志都没有多半是前端根本不知道消息该往哪儿发。机制其实不复杂webview启动时会往页面注入一个window.external对象它的invoke方法把JSON字符串通过各平台原生通道投递回Rust——Linux走WebKit的script-message、macOS走webkit.messageHandlers、Windows挂在COM的IDispatch上。三个平台注入的JS略有差异但暴露给前端的API统一是window.external.invoke你写前端时完全不用关心平台差异。字符串穿过这条桥后落到你builder里注册的invoke_handler闭包。Rust端注册invoke_handler接收指令.user_data(vec![]) .invoke_handler(|webview, arg| { let tasks webview.user_data_mut(); match serde_json::from_str::Cmd(arg).unwrap() { Cmd::AddTask { name } tasks.push(Task { name, done: false }), Cmd::MarkTask { index, done } tasks[index].done done, _ (), } render(webview) })user_data是存放在webview线程上的任意值闭包直接读改不用加锁。命令分发用serde的tag cmd做枚举todo.rs里AddTask、MarkTask、ClearDoneTasks就是这么写的。JS端window.external.invoke发指令function invoke(arg) { window.external.invoke(JSON.stringify(arg)); } function addTask(name) { invoke({ cmd: addTask, name: name }); }Vue示例里每个按钮动作都包了一层这种函数参考 examples/todo-vue/src/rpc.js 看完整调用链Linux上桥的注入代码在 webview-sys/gtk.rs想看别的平台就翻同目录的cocoa和mshtml文件。怎么确认生效开debug(true)挂上检查器点一次按钮Rust侧println!打出JSON、窗口标题跟着任务数变化链路就通了。Rust调JS用eval把状态推回前端Rust这边状态已经改了前端界面却纹丝不动因为消息要有人推桥不会自己同步两边。eval做的事很直接把一段JS字符串丢给页面执行返回WVResult表示执行成功与否。先泼盆冷水——这版webview的eval收不到表达式的求值结果别指望eval(11)能拿回2。想让JS把值带给Rust走上一节的invoke回传别把eval当RPC用。fn render(webview: mut WebViewVecTask) - WVResult { let json serde_json::to_string(webview.user_data()).unwrap(); // 任务列表序列化后推给前端调它自己的渲染函数 webview.eval(format!(rpc.render({}), json)) }inject_css底层也是eval就是往页面里塞了个style标签——凡是从Rust动前端出口都是它。怎么确认生效eval返回Ok只说明执行了真正要看UI变了没有打开检查器看DOM或者像timer.rs那样把计数器直接写进一个p标签每秒跳一下。后台线程更新UI用handle.dispatch代替直接eval定时器线程里想刷一下界面却发现WebView过不了线程边界原因WebView绑死在UI线程上不实现Send跨线程拿不到引用。出路是handle它只持有一个原始指针加一个Weak引用实现了Send Sync可以Clone、可以move进任意线程。dispatch把闭包扔进webview的事件队列下一轮step()时在UI线程执行闭包要求Send static返回的Err不会当场炸而是延迟到下一次step()才吐回主循环。let handle webview.handle(); thread::spawn(move || loop { handle.dispatch(move |webview| { render(webview, *counter.lock().unwrap()) })?; thread::sleep(Duration::from_secs(1)); });timer示例里这个线程每秒dispatch一次页面上的计数随之跳动——看到数字在走dispatch链路就正常。下一步照着两个示例扩展你的通信链路先把仓库拉下来跑起来git clone https://gitcode.com/gh_mirrors/we/web-view两个具体的扩展动作一是打开 examples/todo-vue/src/rpc.js 对照 examples/todo-vue.rs学一套cmd命令枚举JSON的完整协议写法原样搬进你自己的应用二是拿 examples/timer.rs 当骨架给应用加一个后台推送线程练习dispatch和主线程的分工。 最后提醒一个容易踩的坑invoke_handler里返回的Err不会当场炸而是延迟到下一次step()才暴露。调试时如果看到凭空出现的错误先往前翻上一次handler调用的记录再决定怎么改。【免费下载链接】web-viewRust bindings for webview, a tiny cross-platform library to render web-based GUIs for desktop applications项目地址: https://gitcode.com/gh_mirrors/we/web-view创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表