惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

S
SegmentFault 最新的问题
J
Java Code Geeks
V
V2EX
Blog — PlanetScale
Blog — PlanetScale
博客园 - 司徒正美
Hugging Face - Blog
Hugging Face - Blog
F
Fortinet All Blogs
aimingoo的专栏
aimingoo的专栏
B
Blog
A
About on SuperTechFans
有赞技术团队
有赞技术团队
月光博客
月光博客
Microsoft Azure Blog
Microsoft Azure Blog
阮一峰的网络日志
阮一峰的网络日志
腾讯CDC
美团技术团队
大猫的无限游戏
大猫的无限游戏
爱范儿
爱范儿
N
Netflix TechBlog - Medium
C
Check Point Blog
Recent Announcements
Recent Announcements
博客园 - Franky
博客园 - 叶小钗
T
Tailwind CSS Blog

V2EX

我用 AI 写代码,但终端管理反而成了累赘——于是我做了 codux [调研] 各位在公司都用什么 ide 和 agent 写代码? 老运维 share 一个运维平台 看到有公司考核 token 指标,很好奇大家上个月的 AI 账单是多少 GLM-Coding 调用持续报错: z.ai 的 Lite 套餐几乎无法使用,官方 Pro/Max 是否稳定? 现在还有什么渠道可以稳定安全地使用 Claude 吗? 上海漕河泾内推,本组有 2 个 hc,一个后端,一个前端,预算都是 20k 左右,不打卡,氛围好 如果 V2EX 上有一组不永久保存聊天记录(比如只保存 7 天或者 24 小时)的聊天室,那么会开启哪些有用或者有趣的可能? gemini cli 貌似挂了,一直返回 403 第一次在自媒体上赚到钱 收集了最近在使用的低价 GPT, Gemini,邮箱等 AI 会员的小店合集 讨论个大实话:现在企业还在说 AI 编程提效 20%, 30%的,真的太落后,没用懂 AI。因为包括很多前沿公司,已经狂奔到提效 200%-500%的情况 [招聘][远程][币安] 前端/后端/QA/iOS/Android 至少 3 年以上经验 目前有大量 HC 欢迎投递 Chatgpt Pro 用量用不完的可以开这些设置 面试的时候好像遇到钓鱼了,给各位避个坑 cursor 年续费 22 号到期, 自动续费是否还是老的计次套餐呢 被两件破事毁掉的一下午,琐碎的内耗消磨人的精力 使用 Planet 存储 Codex 的会话或者重要信息 如果业务部门领导不要你开发功能,而是要求你教会它用 claude code 开发功能,你会怎么做? 分享一个 MacOS 接绿联 CM818 USB 转 DP 转接器使用感受 我的 HR 朋友 10 年老 Java ,非全大专,大家帮忙看看简历 开源了一个 AI 口语练习工具,音素级发音评分,完全免费可自部署 V2EX 上有哪些你觉得很有趣、印象深刻的妹纸? 字节为啥不出个国内版 Vercel? 有在大马的朋友吗? 问个运营商问题 你们在有领导的公司大群发过的最大胆的消息是什么 公司裁员,目前没有工作。想试试摆摊,做一个移动鲜啤打酒车 我的硬盘 Memblaze Pblaze 5 Linux 下不识别,给 Linux 内核提交了补丁, AI 说有望被合并 只有我一个人觉得 codex 不好用?
Nature 编程语言集成 WebView
weiwenhao · 2026-01-22 · via V2EX

一位开发者尝试在 nature 编程语言 中集成 WebView (项目地址:https://github.com/wzzc-dev/nature-webview),采用类似 Rust Tauri 的集成方式,最终打包完成后可以得到体积小巧、轻量化的可执行桌面程序。

这个简单的桌面程序就是由 nature + webview 打包而来,在 macos arm64 上只有 680KB 的大小,下面是一个更加简单的 hello world 代码示例

import webview as w
import webview.{webview_t}
import libc.{cstr, to_cfn}
import co.{sleep}
import co
import runtime

fn interval_callback() {
    co.yield() // Yield coroutine every 10ms to allow GC to run
}

fn other() {
    for true {
        println('other co')
        sleep(1000)
    }
}

fn main() {
    println("Hello, Nature Webview!")

    @async(other(), co.SAME)

    var window = w.create(1, null)
    w.set_title(window, "Hello World".to_cstr())
    w.bind(window, "interval_callback".to_cstr(), to_cfn(interval_callback as anyptr), 0)

    var html = `
      <html>
          <body>
              <script>setInterval(() => {window.interval_callback();}, 10);</script>
              <h1>Hello, Nature!</h1>
          </body>
      </html>`

    w.set_html(window, html.to_cstr())

    w.run(window) // blocking coroutine!
    w.destroy(window)
}

nature 是一款基于全局协程模型的编程语言,它与 WebView 的兼容性并不理想。起初该方案完全无法运行,nature-webview 的作者与我进行了沟通, 我认为这是一项非常有意义的工作,是 nature 核心 GUI 特性的重要环节,因此我着手排查并解决导致运行失败的问题,经过一个周末努力,主要问题都被解决,我迫不及待的将其分享出来!

WebView 必须运行在 t0 系统栈 + 系统线程中

在 macOS 系统上,编译完成后程序虽能正常启动,但通过 JS 回调 nature 时却发生了崩溃,且这个崩溃的调用栈非常深,难以追溯。即便通过 Debug 方式构建 WebView ,依旧无法获取完整的调用栈。

最终发现这是因为 WebView 动态依赖 WebKit 库,而 WebKit 库无法追踪调用栈。经过多次尝试后偶然发现了 macos 的崩溃报告,我发现其中包含了完整的堆栈追踪日志!原来我一直忽略了如此重要的信息。

命令行执行程序崩溃时,也可以通过 ~/Library/Logs/DiagnosticReports/ 直接查看堆栈追踪日志

Crashed Thread:        0  Dispatch queue: com.apple.main-thread
Exception Type:        EXC_BREAKPOINT (SIGTRAP)

Termination Reason:    Namespace SIGNAL, Code 5 Trace/BPT trap: 5
Terminating Process:   exc handler [37708]

Thread 0 Crashed::  Dispatch queue: com.apple.main-thread
0   JavaScriptCore                	    0x7ff828e4b7d1 JSC::LocalAllocator::allocate(JSC::Heap&, JSC::GCDeferralContext*, JSC::AllocationFailureMode)::'lambda'()::operator()() const + 6801
1   JavaScriptCore                	    0x7ff829574b24 JSC::VM::VM(JSC::VM::VMType, JSC::HeapType, WTF::RunLoop*, bool*) + 24452

根据日志可以判断这是一个通过类似 assert 抛出来的断点类型的异常,并且发生在内存分配中,说明这极有可能和运行环境相关。

于是我尝试使用 C + WebView 进行对比测试并复现问题,nature 是运行在协程和协程栈中,所以通过 c 库的 mcontext + mmap stack 模拟 nature 的运行模式,果然重现了错误。到这里已经大概猜测是 WebKit 会进行运行时栈检测,并且是通过 pthread_get_stackaddr_np 的方式直接读取线程默认栈。

这是一个棘手的问题,如果需要兼容 WebView 需要对 nature 的基础架构进行一定改动。到目前为止都还只是猜测,为了证实测测我去下载了一个多 G 的 WebKit 代码定位堆栈追踪中的函数。最终定位到了下面的关键逻辑

ALWAYS_INLINE void* LocalAllocator::allocate(JSC::Heap& heap, size_t cellSize, GCDeferralContext* deferralContext, AllocationFailureMode failureMode)
{
    VM& vm = heap.vm();
    if constexpr (validateDFGDoesGC)
        vm.verifyCanGC();
    return m_freeList.allocateWithCellSize(
        [&]() ALWAYS_INLINE_LAMBDA {
            sanitizeStackForVM(vm);
            return static_cast<HeapCell*>(allocateSlowCase(heap, cellSize, deferralContext, failureMode));
        }, cellSize);
}

这个函数的结构和堆栈追踪中显示的一致,虽然有版本变动带来的影响,但是大概率就是该函数导致了崩溃,进一步追踪发现其中的 sanitizeStackForVM 函数果然使用了 pthread_get_stackaddr_np 来获取栈信息并进行 assert 判断。

问题到这里基本已经确定,可以尝试解决该问题。过去我知道 UI 必须在系统默认线程上渲染,但是没想到有些 UI 库对栈的要求也如此严格。由于 nature 的协程运行在共享栈中,所以解决起来也比较简单,直接拿系统栈作为共享栈使用即可,不过系统栈还需要驱动 procesor 运行,所以我暂时拿其中一半系统栈来作为协程的共享栈。

当然这是一个临时解决方案,在较新的 macos 26.2 系统中并不会发生类似的崩溃,以后也许可以继续使用 mmap 进行栈分配。

WebView 必须使用 glibc

由于 macos 系统发行版本单一,并且强制要求使用动态库链接,所以 nature 在 macos 上没有遇到编译问题,但是在 linux 上则出现了该问题。

nature 在 linux 系统上基于 musl 进行纯静态编译,libruntime.a + libuv.a + libc.a 是 nature 依赖的核心,这三件套都是 musl libc 纯静态编译的,所以跨平台性很好,一次编译到处运行。

WebView 在 linux 上依赖的 webkit 和 gtk 必须基于 glibc 动态编译,这和 nature 现在的编译理念产生了严重的冲突。动态编译的 webkit 和 gtk 在不同的 linux 发行版本上也有着兼容问题。难道这无法解决么?还真不行,Rust 的 tarui 在 linux 系统上也同样有着这样的兼容困扰。

既然现在 webkit 和 gtk 直接打破了静态编译的幻想,必须动态编译,必须依赖 glibc ,牺牲一定的跨平台性,那 nature 索性直接放弃静态编译和交叉编译的思路,直接使用 glibc 进行动态编译好了。

使用系统自带的链接器 ld 将 nature 依赖的静态库代码和 webkit 相关库进行动静态库混合链接后,终于在 ubuntu 22.04 desktop 上运行成功。

nature 手写的链接器只实现了静态编译部分,所以动态编译时需要通过 --ld /usr/local/ld 指定系统链接器。

为了更好的兼容性 nature build 的 --ldflags 参数还需要进行调整,在识别到传递 -lc 参数后,会自动去掉 libc.a 的依赖。有一个比较神奇的地方在于 musl libc 工具链编译的 libruntime.a 和 libuv.a 能够和 glibc 库直接混合链接使用。

最终得到的令人眼花缭乱的编译参数 😄,nature 并不依赖于 gcc 工具链,所以需要在 ldflags 中处理 crtendS 这样的初始化文件。

nature build --ld '/usr/bin/ld' --ldflags "/usr/lib/gcc/x86_64-linux-gnu/11/crtbeginS.o /usr/lib/gcc/x86_64-linux-gnu/11/crtendS.o --dynamic-linker=/lib64/ld-linux-x86-64.so.2 -L/usr/lib/x86_64-linux-gnu -L/usr/lib/gcc/x86_64-linux-gnu/11 --whole-archive /usr/local/lib/libwebview.a --no-whole-archive -lwebkit2gtk-4.1 -lgtk-3 -lgdk-3 -lz -lpangocairo-1.0 -lcairo-gobject -lgdk_pixbuf-2.0 -latk-1.0 -lpango-1.0 -lcairo -lharfbuzz -lsoup-3.0 -lgio-2.0 -lgmodule-2.0 -ljavascriptcoregtk-4.1 -lgobject-2.0 -lglib-2.0 -lstdc++ -lgcc_s -lpthread -lc" test.n --verbose

w.run 阻塞 runtime

现在解决所有的问题了么?注意看这一行代码,它直接指向了一个 c++ 函数 webview_run ,并且这个函数永远不会返回。这会发生什么?

w.run(window) // blocking coroutine!

经过上面的适配,webview 成功在协程中运行,如果你足够了解协程的运行原理,你就会知道这一行阻塞操作会占用核心线程都控制权,这导致在该核心线程上的协程调度器和其他协程永远都不会运行。但这还不够致命,nature 可以轻易的通过创建新的线程来解决这个问题。真正的问题是阻塞系统调用会导致该线程无法进入 STW 状态,从而阻塞整个 GC ,这才是真正无解且致命的问题。

nature 采用了协作式的调度方案,意味着更多的控制权交在了开发者手上,为了更友好的 GUI 体验,也许应该尝试一些不同的方案。于是最终使用了一个比较 hack 的方式解决了该问题。

w.bind(window, "interval_callback".to_cstr(), to_cfn(co.yield as anyptr), 0)

var html = `
  <html>
	  <body>
		  <script>setInterval(() => {window.interval_callback();}, 10);</script>
		  <h1>Hello, Nature!</h1>
	  </body>
  </html>`

我们在 js 代码中创建了一个定时器,每 10ms 调用一次 nature 函数绑定,该函数绑定会 yield 当前协程将控制权交还给协程调度器。 通过这种方式不需要调整任何的 nature 编译器让整个协程调度器能够正常运转,代价大概就是需要 10ms 的延迟,而不是按需调度罢了。但是至少不会产生 GC 问题。

C/C++ 回调 nature fn

C/C++ 是不安全非托管的函数能够回调 nature 函数吗?原则上不行,比如 golang 不允许,因为 golang 的 C 代码和 go 代码运行在了不同的栈中,所以无法直接调用。

nature 和 C 代码在同一个共享栈中运行,所以回调能够正常触发。但在当前的设计中,nature 没有考虑过作为回调被 C 代码调用,所以 GC 不能正确的识别该混合调用模式。但这还算好解决,我们可以通过追溯栈帧获取整个栈上的函数调用链条并进行 GC root mark, C 在大多数情况下同样遵循该栈帧规则。

另外一个问题是闭包问题

fn foo() {}

fn main() {
	int a = 1
	fn() f = foo
	f = fn() {
		var b = a // Reference to external var
	}
	
	f()
	foo()

}

在高级编程语言中通常会区分闭包和全局函数,在上面的代码示例中,面对一个 fn 类型的变量 f ,编译器无法追踪其是全局函数还是闭包。所以通常会采取妥协的策略将 fn 类型变量统一按照闭包进行存储和使用。如图,fn 并不是一个单独的指针指向函数的地址,而是一个 16byte 的 pair 结构。

所以如果直接将 nature 中的 global fn 通过参数进行传递时,该参数是一个 16byte 的奇怪数据,C 并不知道 fn 的地址是什么,所以还需要从闭包中提取 fn 地址传递给 C 。这里的 libc.to_cfn 做了一个简单的工作,就是直接抛弃 env ptr(全局函数的 env ptr 总是为 null)并从中提取 fn ptr 传递给 C 函数。

w.bind(window, "interval_callback".to_cstr(), libc.to_cfn(interval_callback as anyptr), 0)

总结

到这里已知的问题都得到了解决,虽然方案上比较妥协,但是最终还是让 nature 编程语言成功集成了 WebView ,并且能够达到和 C + webview 一样的原生性能体验,同时也能够享受 GC 编程语言带来的安全性和协程的便利性。而 WebView 是一个典型的 C/C++ UI 库的示例,可以采用同样的方式集成类似 sokol/ImGui 等 UI 库。

https://nature-lang.cn/news/20260115 就如上一篇文章最后提到的,可控内存分配器和 unsafe 裸函数支持是原本的 GUI 支持策略基础,但是未料到共享栈模式的协程和 C/C++ 的兼容性如此优秀,直接就完成了适配。不过这种妥协不会是最终的解决方案,unsafe 裸函数和可控内存分配模式的工作会继续推动。到时候 nautre 的 main 函数可以选择独立于协程调度系统采取单独的线程和栈运行,让 nature 更加适应原生的 GUI 开发。

当然图形化操作系统 windows 同样是 GUI 支持重要一个环节,我将会进行看进行支持,由于调整了 nature 的源码,所以需要等到 0.7.3 版本发布时才能体验 nature-webview ,在这期间我会使用 nature 结合 webview 开发一个小项目进一步测试可用性。