gpui-dotnet深入浅出1/2
大家好呀,我是搬砖工.上文感谢大家的热情关注和点赞支持.我马不停蹄的开始gpui-donet系列的第二篇文章.内容太长.计划按2节展开.
言归正传,我们开始今天的正文.我们先熟悉下项目结构.
项目结构
gpui-dotnet/
├── Directory.Build.props # 全局 MSBuild 属性(版本/作者/许可等)
├── Gpui.slnx # 解决方案(.slnx 新格式)
├── LICENSE # MIT 许可
├── NOTICE # 第三方归属说明(Zed/GPUI 引用)
├── NUGET_README.md # NuGet 包页 README
├── README.md # 项目主 README
├── dotnet-tools.json # 本地 .NET 工具清单
│
├── bindings/
│ └── schema.json # ★ 语义组件/操作 Schema(生成器的单一事实源)
│
├── include/
│ └── gpui_dotnet.h # 公开 C 头文件(ABI v1 完整声明)
│
├── crates/
│ └── gpui-dotnet/ # ★ Rust 原生宿主库
│ ├── Cargo.toml # crate 清单(依赖 pinned GPUI)
│ ├── Cargo.lock # 锁文件
│ ├── build.rs # csbindgen 生成 C# 绑定
│ └── src/ # 见「第三节」
│
├── src/
│ ├── Gpui/ # ★ 托管公共 API 与运行时源码
│ ├── Gpui.Core/ # 平台中立包项目(GPUI.NET.Core)
│ ├── Gpui.Native/ # RID 原生包项目(win/linux/osx)
│ └── Gpui.Generators/ # ★ Roslyn 源生成器
│
├── samples/
│ ├── Gpui.Sample/ # ★ 交互式组件画廊示例
│ └── Gpui.Bench/ # 基准测试
│
├── tests/
│ └── Gpui.Tests/ # ★ 托管契约与生成器测试
│
├── tools/
│ ├── Gpui.Bindings.Generator/ # ★ 语义绑定生成器(schema → C#/Rust)
│ └── uidrive/ # Windows UI 驱动(点击/滚轮/截图)
│
└── docs/ # 设计文档
项目结构清晰.层次划分很明确.通过封装gpui的Rust库.生成了gpui-dotnet的rs库,生成c约定的dll.同时生成c#的调用:

Rust和C#有明确的边界
提示:我们C#侧和Rust侧底色不一样.大家可以很快速区分功能实现在那一端.
再提示:我们第一次编译Sample,需要先编译rust版的dll.需要几分钟时间,请耐心等待一下.并没有出错
我们C#侧的项目依赖如下:

通过依赖SG生成器.我们自己的View可以生成被Rust调用的方法.上篇我们介绍过了.
接下来我们再来看整体架构
整体架构
整个框架围绕一条清晰的边界线展开:C# 拥有什么,Rust 拥有什么。跨边界的唯一通道是 C ABI,且只允许「状态转换 / 粗粒度批量」通过。记住这个批量.这个让我们和Rust的交互不用设置一次就调用一次.对性能有很大影响.

要特别关注下虚拟行的渲染.我们昨天已经演示过了.他的方法参数如下:
[GpuiListItem]
private Element ServiceRow(int index, ref RenderContext ui)
{
...//根据index获取u
return ui.Button(
"service-row",
ui.HStack(
ui.TableCell(
0,
ui.Text($"{u.Id}")
.FontSize(Px(theme.Typography.BodySmall))
.TextColor(colors.Text)
),
).ItemsCenter();
...
}
传入index索引.我们可以根据索引结合列表获取到对应Model,再通过ref的ui渲染成行.
语言边界

这里的思路和gpui-commpent推出的shell思路是一样的.不过shell使用的JS做业务语言.我们使用了C#做业务语言.
C#侧不处理渲染和绘制.他通过描述RenderArena来表述UI.通过ABI传递给GPUI侧
当然,shell项目我们也有介绍,请大家移步:Gpui-shell介绍
渲染过程
一次「脏」渲染(dirty render)从 View.Render(ref RenderContext) 开始,把整棵语义树写入一个平坦的 arena,Rust 校验后保留解码快照。

RenderArena
RenderArean是个struct.你可以在gpui-dotnet里随处可见的基本上都是struct.合理使用struct可以有效减少GC停顿.乍一看性能是不是更好了,别急,我们后面回说道这个设计.arena主要有以下四个属性来描述UI
| 属性 | 内容 |
|---|---|
| NodeRecrod[] | 组件 ID、标志位、UTF-8 数据区间 |
| OpRecord[] | 类型化语义操作(定长 C 布局 gpui_op_record) |
| ChildRecord[] | parent/child 记录(铺平) |
| UTF-8编码byte* | 文本与 key 的原始字节 |
我们创建的组件,放置在NodeRecrod数组里.对组件的样式设置.放置在OpRecord数组.OpRecord数组.也放了我们的事件回调.
OpRecord的Code类型我们粗略看下:
internal enum OpCode : ushort
{
Flex = 1,
VStack = 2,
ItemsCenter = 3,
JustifyCenter = 4,
JustifyBetween = 5,
FlexGrow = 6,
GapPx = 100,
PaddingPx = 101,
WidthPx = 102,
WidthPercent = 103,
...
OnClick = 200,
...
ScrollAxis = 301,
SmoothScroll = 302,
}
基本上看这个Enum,我们就知道他支持了多少gpui的功能.
我们看下我们一次渲染的调用堆栈.

多么简洁高效的调用.红框部分为Rust部分实现.
我们不再使用INPC的双向Binding.带来的时自己来驱动数据变化.手动触发渲染.不多不少.一点不浪费.
正文部分今天我们先到这里,明天我们会继续更新.接下来聊聊这个项目.
从昨天作者发布preview.1到今天.我们昨天测试Sample,今天已经开始解析项目实现.而这一切都要感谢AI.没有强大的AI.作者不可能3天就实现了从dotnet到gpui的调用.AI时代确实来了.我们以前按月按年计划的功能.可能在合理的AI协助下.快速高效的实现了.
但在AI时代.我们还是需要一批人,帮我们把最新的资讯.最新的技术通过肉身带到我们大家身边.搬砖工一直以来,愿意做这样的人.
希望大家多多关注点赞.再次谢谢大家
截至目前我观察,没有实现的功能大体有:
- flex_grow_0/1, flex_shrik_0/.1, p_0/1, m_0/1
- hotkey
- command
缺失部分已经咨询过作者.作者表示都在推进中.
不光有这些,后续应该还有gui-commpent组件的实现.关注搬砖工,我带给大家带一手资讯
下篇我们会接着深入到View的生命周期,各个线程如何调用展开.我把昨天编译的sample上传到csdn上了.可以按需下载
浙公网安备 33010602011771号