硬边界:COM 互操作的限制

硬边界:COM 互操作的限制

所有功能基本都跑通了,但有一条硬边界碰上了——COM 互操作。

这不只是我们项目遇到的问题。微软官方的 Native AOT 限制列表里明确把 “Windows: No built-in COM” 列在第一条。这个限制的覆盖面比很多人想象的更广——剪贴板、拖放、RichEdit 控件、Shell 接口等底层都涉及 COM。

TDS 的 winform 版本里受影响的场景是:在文件列表上右键,弹出 Windows 系统原生的 Shell 上下文菜单(资源管理器里的"打开方式/发送到/属性"菜单)。核心调用涉及 Marshal.GetTypedObjectForIUnknown——把非托管 IUnknown 指针包装成托管 RCW(Runtime Callable Wrapper)的方法:

var pUnknown = …; // 从 Shell API 获取的 IUnknown 指针
var shellFolder = Marshal.GetTypedObjectForIUnknown(pUnknown, typeof(IShellFolder));
这条路径在 AOT 下无法工作的原因有两层:

RCW 的动态生成——AOT 编译器在编译期无法确定最终会传入什么 IID、什么接口,因此不能预生成对应的包装代码。
Trim 移除元数据——typeof(IShellFolder) 对应的元数据在 trim 阶段可能被视作"没有托管代码直接调用"而移除。
尽管部分第三方库试图通过 ComWrappers 机制绕过内置 COM 路径,可以为 winform 提供有限的 AOT COM 支持,但即便是最新版仍有大部分 API 仍然可能无法正常使用。因此目前还是放弃系统原生 Shell 菜单支持了,aot编译下强行打开系统菜单程序会直接崩溃。

六、结论与展望
回到开头的那个问题:winform 能用 Native AOT 吗?

如果只看结果——我们在 TDS 项目中完成了 dotnet publish -r win-x64 -c Release 并拿到了一个可独立运行的 exe,绝大多数核心功能正常工作——那么答案偏乐观。

但仔细拆开来看,这是一个比较微妙的状态:

winform 团队在 dotnet/winform#4649 里长期推进 trimming 兼容工作,到 .NET 10 时已有部分路径清理完毕。
SDK 层面的 NETSDK1175 错误主要起保护作用,加上 _SuppresswinformTrimError 后确实可编译。社区里有人形容它是"挡板"而非"铁门"。
COM 相关的限制(Marshal.GetTypedObjectForIUnknown、BuiltInComInterop)是 AOT 自身的硬边界,跟 winform 本身无关。
所以更准确的表述是:在 .NET 10 下,不涉及 COM 动态互操作的中等复杂度 winform 项目,通过合理配置和少量代码调整,可以成功产出可运行的 AOT exe。但它不是官方推荐的发布方式,_SuppresswinformTrimError 是一个内部逃逸通道而非公开 API,不保证所有路径都安全。

如果项目重度依赖:

COM 互操作(尤其是动态 IID 解析和 Marshal.GetTypedObjectForIUnknown)
ComponentResourceManager / .resx 反射式资源加载
DataGridView 数据绑定(反射枚举属性路径会被 trim 移除)
RichTextBox(底层 RichEdit COM 控件包装)
大量 System.Reflection 调用
那 AOT 适配的坑会显著增多,建议在评估清楚之前不要轻易上生产。

但反过来看,对于小型工具类应用——快速截屏、文件批量重命名、剪贴板历史管理、系统托盘监控程序、简单数据录入——winform + AOT 的组合确实很有吸引力:

零运行时依赖:exe 发出去就能跑,用户不用装 .NET
启动接近瞬间:AOT 省去了 JIT 编译时间
体积可接受:实测简单 winform AOT exe 约 10–20 MB
两个我们没有用到的"高频高风险"控件:

DataGridView:社区反馈在 AOT 下容易出现列类型解析失败的问题(因为内部的 DataGridViewColumnType 映射依赖反射枚举所有加载程序集的类型)。
RichTextBox:底层的 RichEdit COM 控件包装存在 RCW 生成问题,与第五节描述的 COM 限制同一类。
如果在用这两个控件,AOT 适配的难度会更大——但也不是完全不可能,只是需要更深的拦截和替换。

以后想做一个 Windows 小工具,又不想让用户装运行时、也不想用 C++ 或其他依赖重新造一遍 UI 轮子——熟悉的WinForm + AOT 这个组合值得放进你的工具箱里。配置改三行,代码改几处,出来的就是一个孤零零的 exe,跑在用户机器上,又快又省心。

七、最后