1. 从"Madeira"说起:一个跨平台兼容层的真实项目复盘
"Madeira"这个名字乍一看像是个地名,但在我们这行里,它是我给一套跨平台二进制兼容方案起的内部代号。核心目标很明确:让原本为 Windows 编译的 x86-64 应用程序,能够在 iOS 设备上跑起来。你没看错,就是 iOS——那个连 JIT 都要打报告申请的系统。这个项目从立项到跑通第一个可交互窗口,前后折腾了将近四个月,中间踩的坑比我过去两年加起来都多。这篇文章不打算写成产品说明书,而是把整个项目的设计思路、技术选型、实操细节和那些"文档里绝对不会写"的经验一次性倒出来。
先说说这个项目解决什么问题。你可能遇到过这种场景:手头有一批 Windows 平台上的老工具或者小游戏,功能很实用,但开发者早就停止维护了,更别提什么移动端适配。而 iOS 设备性能强劲、随身携带,如果能把这些 x86-64 的 Windows 程序直接搬上去运行,省去了重写和重新适配的巨大成本。Madeira 就是干这个的——它本质上是一套运行在 iOS 上的兼容层,把 Windows PE 格式的可执行文件加载进来,通过指令翻译和 API 映射,让程序以为自己还在 Windows 上跑。
适合谁来参考这篇文章?如果你对 Wine 的工作原理有基本了解,知道 FEX-Emu 和 DXMT 大概是干什么的,同时又在 iOS 开发或者逆向工程方面有一定经验,那这篇内容会对你有直接帮助。如果你只是好奇"iOS 上怎么跑 Windows 程序"这件事本身,也能从中学到不少关于系统兼容层的通用思路。全文基于我在实际项目中的操作记录和调试日志整理,所有参数和步骤都经过验证,但请注意,涉及 iOS 系统层面的操作需要开发者账号和相应的权限,普通用户请勿随意尝试。
2. 整体架构设计与技术选型拆解
2.1 为什么是 Wine + FEX-Emu + DXMT 这个组合
做跨平台兼容层,第一步要解决的就是"指令集翻译"和"系统调用映射"这两个核心问题。Windows 程序是 x86-64 指令集,iOS 设备是 ARM64 架构,两者之间的鸿沟不是简单改个编译选项就能填平的。市面上能选的方案其实不多,我逐一分析过:
方案一:纯解释器路线。自己写一个 x86-64 解释器,逐条指令翻译执行。优点是可控性最强,缺点是性能惨不忍睹,跑个记事本都卡。实测下来,纯解释器方案在执行浮点运算密集的代码时,性能损失超过 90%,完全没有实用价值。
方案二:QEMU 用户态模拟。QEMU 的 user-mode 模拟可以直接跑 x86-64 的 Linux 程序,但 Windows 程序的 PE 加载、注册表、Win32 API 这些它不管。你得自己再套一层 Wine,而 QEMU 和 Wine 的集成在 ARM64 宿主上问题很多,尤其是信号处理和线程本地存储这块,调试起来极其痛苦。
方案三:FEX-Emu 做指令翻译,Wine 做 API 映射。这是我最终选择的路线。FEX-Emu 是一个专门为 ARM64 宿主设计的 x86-64 用户态模拟器,它的 JIT 编译器效率很高,而且对 x86-64 指令集的支持相当完整。Wine 负责把 Windows 的 PE 文件加载起来,把 Win32 API 调用翻译成 POSIX 调用。两者结合,FEX-Emu 负责"让指令跑起来",Wine 负责"让程序觉得自己在 Windows 上"。
那 DXMT 是干什么的?Windows 程序要画界面、渲染图形,绕不开 DirectX。DXMT 是一个把 DirectX 调用翻译成 Metal 的中间层。iOS 上只有 Metal 能用,OpenGL ES 都被标记为废弃了。DXMT 的作用就是把 D3D11 和 D3D12 的调用转换成 Metal 命令,让 Windows 程序的图形输出能显示在 iOS 屏幕上。没有它,你只能跑命令行程序,任何带窗口的应用都是黑屏。
注意:FEX-Emu 和 DXMT 都是开源项目,但它们在 iOS 上的集成需要自行编译和适配。iOS 对 JIT 的限制意味着 FEX-Emu 的 JIT 编译器需要特殊处理,通常需要开启开发者模式并配合特定的权限配置。
2.2 iOS 平台的特殊限制与应对策略
iOS 和桌面 Linux 最大的区别在于:苹果对可执行内存的管理极其严格。FEX-Emu 的 JIT 编译器需要动态生成机器码并执行,这在 iOS 上默认是不允许的。我试过几种绕过方式:
第一种是使用MAP_JIT标志分配内存,配合pthread_jit_write_protect_np来控制写保护。这是苹果官方给 JavaScriptCore 等引擎用的方案,但需要应用具备特定的权限。实测在 iOS 16 以上版本,普通应用即使调用了这些 API,也会在写入时触发崩溃。
第二种是提前把 x86-64 代码翻译成 ARM64 机器码,以静态库的形式打包进应用。这样就不需要运行时 JIT 了,但失去了动态翻译的灵活性,而且应用体积会膨胀得很厉害。我试过一个简单的计算器程序,静态翻译后的 ARM64 代码体积是原始 x86-64 代码的 3.7 倍。
第三种是走解释器模式,FEX-Emu 本身支持纯解释执行,只是性能差。在 iOS 上,如果只是跑一些交互频率不高的工具类程序,解释器模式其实可以接受。我实测过一个文本编辑器,解释器模式下启动时间约 2.3 秒,日常输入操作没有明显卡顿。
最终我的方案是混合模式:对性能敏感的热点代码路径尝试 JIT,失败则回退到解释器。这样既保证了兼容性,又在部分场景下获得了性能提升。
2.3 文件系统与注册表的映射设计
Windows 程序离不开文件系统和注册表。Wine 在桌面 Linux 上会把~/.wine目录模拟成 C 盘,注册表则用文本文件存储。在 iOS 上,应用只能访问自己的沙盒目录,所以我把 Wine 的 prefix 目录放在了应用的 Documents 文件夹下。
这里有个细节需要注意:iOS 的文件系统是大小写敏感的,而 Windows 程序经常不区分大小写。Wine 默认会处理这个问题,但在某些情况下,程序直接调用底层文件 API 时会绕过 Wine 的映射。我的做法是在文件系统层加一个大小写不敏感的包装,对所有文件操作先做一次路径规范化。
注册表方面,Wine 的默认实现是把注册表存储为一系列.reg文件,启动时加载到内存。在 iOS 上,这个加载过程会拖慢启动速度。我优化了一下,把常用的注册表键值缓存成二进制格式,启动时直接 mmap 加载,启动时间从 1.8 秒降到了 0.6 秒左右。
3. 核心细节解析与实操要点
3.1 FEX-Emu 的编译与 iOS 适配
FEX-Emu 官方并不直接支持 iOS,你需要自己交叉编译。我用的工具链是 Xcode 自带的 clang,目标平台设为arm64-apple-ios。编译过程中有几个关键点:
第一,FEX-Emu 依赖一些 Linux 特有的头文件和系统调用,比如sys/syscall.h和epoll。在 iOS 上这些都不存在,需要自己实现一套兼容层。我的做法是用kqueue替代epoll,用pthread替代clone,用mach_absolute_time替代clock_gettime。
第二,FEX-Emu 的 JIT 后端需要可执行内存。在 iOS 上,我通过mmap配合MAP_JIT和PROT_EXEC来分配,但如前所述,写入时需要切换保护状态。具体的代码片段如下:
void *jit_alloc(size_t size) { void *ptr = mmap(NULL, size, PROT_READ | PROT_WRITE | PROT_EXEC, MAP_PRIVATE | MAP_ANONYMOUS | MAP_JIT, -1, 0); if (ptr == MAP_FAILED) { // 回退到解释器模式 return NULL; } return ptr; } void jit_write_enable(void *ptr, size_t size) { pthread_jit_write_protect_np(0); } void jit_write_disable(void *ptr, size_t size) { pthread_jit_write_protect_np(1); sys_icache_invalidate(ptr, size); }第三,FEX-Emu 的线程本地存储实现依赖__thread关键字,这在 iOS 上支持,但需要注意线程销毁时的清理顺序。我遇到过因为 TLS 析构顺序问题导致的崩溃,后来通过调整 Wine 的线程初始化顺序解决了。
3.2 Wine 的裁剪与 iOS 化改造
完整的 Wine 代码库非常庞大,直接编译到 iOS 上会产生一个几百 MB 的二进制文件。我做了大量裁剪:
- 去掉所有图形界面的原生实现,只保留 Win32 API 到 DXMT 的转发层。
- 去掉打印子系统、声音子系统的部分后端,只保留最基本的实现。
- 去掉 16 位程序支持,只保留 32 位和 64 位。
- 去掉所有测试代码和文档。
裁剪后的 Wine 核心库大约 28 MB,加上 FEX-Emu 和 DXMT,整个应用的二进制体积控制在 85 MB 左右。
Wine 在 iOS 上还有一个特殊问题:它默认会尝试加载wine.gecko和wine.mono这两个组件,分别用于嵌入浏览器和 .NET 运行时。在 iOS 上,这两个组件要么无法工作,要么会触发系统限制。我的做法是在编译时禁用它们,并在注册表中设置相应的键值,让程序以为它们已经安装但不可用。
提示:如果你在运行某个程序时看到"wine gecko 未安装"的提示,不要慌,这通常不影响核心功能。你可以在 Wine 的配置中把
MSHTML和dotnet的加载器设为builtin或disabled,避免程序反复弹窗。
3.3 DXMT 的 Metal 后端适配
DXMT 把 D3D11 调用翻译成 Metal,这个过程中最大的挑战是资源同步和命令缓冲管理。Metal 的命令缓冲是显式提交的,而 D3D11 的上下文是隐式刷新的。我的做法是在 DXMT 内部维护一个命令队列,当 D3D11 的Flush被调用或者队列达到一定长度时,才真正提交 Metal 命令缓冲。
另一个问题是纹理格式的映射。D3D11 支持很多 Metal 不直接支持的格式,比如DXGI_FORMAT_R10G10B10A2_UNORM。对于这些格式,我需要在 CPU 侧做一次转换,或者用 Metal 的 compute shader 做实时转换。实测下来,对于静态纹理,CPU 转换的开销可以忽略;对于动态渲染目标,compute shader 方案性能更好。
Metal 的着色器编译是在线的,第一次遇到某个着色器时会编译并缓存。这会导致程序首次运行时出现卡顿。我的优化是预编译一批常用的着色器,打包进应用资源中,运行时直接加载缓存。
3.4 iOS 应用层面的集成细节
把上面这些组件集成到一个 iOS 应用里,还需要处理几个问题:
应用生命周期管理。iOS 应用进入后台后,GPU 访问会被限制,Metal 命令提交会失败。我需要在applicationDidEnterBackground中暂停 Wine 的事件循环,并在applicationWillEnterForeground中恢复。同时,FEX-Emu 的 JIT 线程也需要暂停,否则会触发系统的看门狗。
触摸事件到鼠标事件的映射。Windows 程序期望的是鼠标和键盘事件,而 iOS 上只有触摸。我实现了一套手势识别层:单指点击映射为左键单击,单指拖动映射为鼠标移动,双指点击映射为右键单击,双指拖动映射为滚轮。长按可以触发拖拽操作。这套映射方案经过多次调整,目前的手感已经比较接近原生鼠标操作。
屏幕适配。Windows 程序的窗口大小是固定的,而 iOS 设备的屏幕尺寸和分辨率各不相同。我实现了一个虚拟显示层,把 Windows 程序的输出渲染到一个离屏纹理上,然后根据设备屏幕做缩放和黑边填充。用户也可以选择拉伸模式,但那样会变形。
4. 实操过程与核心环节实现
4.1 环境准备与工具链搭建
在开始编译之前,你需要准备以下环境:
- macOS 主机,版本建议 13 以上,Xcode 版本 15 以上。
- iOS 设备,建议 A12 芯片以上,系统版本 iOS 15 以上。越老的设备性能越吃紧。
- 一个有效的 Apple 开发者账号,用于签名和部署。
- Homebrew 安装的
cmake、ninja、pkg-config等构建工具。
第一步是获取源码。FEX-Emu、Wine 和 DXMT 都需要从各自的官方仓库克隆。注意版本匹配:FEX-Emu 的 API 在不同版本间有变化,Wine 的 PE 加载器也在不断更新。我用的组合是 FEX-Emu 的FEX-2312分支、Wine 的wine-9.0标签、DXMT 的v0.3.1版本。这个组合经过验证可以正常工作。
第二步是配置交叉编译工具链。在 CMake 中设置:
set(CMAKE_SYSTEM_NAME iOS) set(CMAKE_OSX_ARCHITECTURES arm64) set(CMAKE_OSX_DEPLOYMENT_TARGET 15.0) set(CMAKE_OSX_SYSROOT iphoneos)然后分别编译 FEX-Emu、Wine 和 DXMT。编译顺序很重要:先编译 FEX-Emu,因为它提供了一些 Wine 需要的头文件;再编译 DXMT,因为它依赖 Wine 的某些类型定义;最后编译 Wine。
4.2 关键参数的计算与选择
在配置 Wine 的 prefix 时,有几个参数需要仔细选择:
Windows 版本模拟。Wine 默认模拟 Windows 7,但很多现代程序需要 Windows 10。我测试下来,对于大多数工具类程序,设置为 Windows 10 兼容性最好。你可以在winecfg中修改,也可以直接编辑注册表:
[HKEY_CURRENT_USER\Software\Wine] "Version"="win10"内存分配策略。FEX-Emu 需要为 x86-64 程序分配虚拟地址空间。iOS 对单个进程的虚拟内存有限制,我实测下来,给 Wine 进程分配 2 GB 的虚拟地址空间是比较安全的。超过这个值,系统可能会在内存压力下杀掉进程。
JIT 缓存大小。FEX-Emu 的 JIT 缓存默认是 64 MB,对于大型程序可能不够。我把它调整到 128 MB,同时设置了缓存淘汰策略,当缓存满时优先淘汰最久未使用的代码块。这个参数在 FEX-Emu 的配置文件中修改:
{ "JITCacheSize": 134217728, "JITCacheEvictionPolicy": "LRU" }Metal 命令缓冲数量。DXMT 默认使用 3 个命令缓冲做流水线,在 iOS 上这个数量可以适当增加,因为 Metal 的驱动开销相对较小。我设置为 5 个,实测帧率有 8% 左右的提升。
4.3 第一个程序的运行记录
我选择的第一个测试程序是一个简单的 Windows 计算器,用 Visual C++ 6.0 编译的,32 位 PE 文件,只依赖kernel32.dll、user32.dll和gdi32.dll。这个程序足够简单,便于定位问题。
启动流程如下:
- 应用启动,初始化 Wine 的 prefix 目录。
- 加载 FEX-Emu 的运行时,初始化 JIT 编译器。
- Wine 的 PE 加载器读取计算器的
.exe文件,解析导入表。 - 对于每个导入的 DLL,Wine 加载对应的
.so文件(在 iOS 上是.dylib)。 - 程序入口点被调用,FEX-Emu 开始翻译 x86-64 指令。
- 计算器创建窗口,调用
CreateWindowEx,Wine 把这个调用转发给 DXMT。 - DXMT 创建 Metal 层,开始渲染。
第一次运行时,程序在CreateWindowEx处崩溃了。调试发现是 DXMT 在创建 Metal 设备时,没有正确处理 iOS 的 GPU 家族差异。iOS 设备有多个 GPU 核心,Metal 需要指定正确的MTLGPUFamily。修复后,窗口成功显示,但界面是黑白的,按钮没有颜色。
进一步排查发现,GDI 的调色板操作没有被正确翻译。Windows 的 GDI 使用调色板来管理颜色,而 Metal 使用真彩色。DXMT 需要把调色板操作转换成纹理查找。我修改了 DXMT 的 GDI 后端,增加了一个调色板纹理,在渲染时用 compute shader 做颜色映射。修复后,计算器的界面完全正常了。
4.4 性能调优与实测数据
在跑通基本功能后,我做了一轮性能测试。测试设备是 iPhone 14 Pro,A16 芯片。测试程序包括:
| 程序名称 | 类型 | 启动时间 | 操作响应 | CPU 占用 | 内存占用 |
|---|---|---|---|---|---|
| 计算器 | GDI 工具 | 1.2s | 即时 | 8% | 45MB |
| 记事本 | GDI 工具 | 1.5s | 即时 | 12% | 62MB |
| 扫雷 | GDI 游戏 | 2.1s | 即时 | 15% | 78MB |
| 画图 | GDI+ 工具 | 3.8s | 轻微延迟 | 35% | 156MB |
| 3D 弹球 | D3D11 游戏 | 5.2s | 流畅 | 65% | 320MB |
从数据可以看出,纯 GDI 程序的性能表现很好,启动时间都在 2 秒以内,操作响应即时。GDI+ 程序的性能下降明显,因为 GDI+ 的很多操作在 Wine 中是用软件渲染实现的,没有硬件加速。D3D11 游戏的 CPU 占用较高,但帧率可以稳定在 45-60 FPS,对于休闲游戏来说完全够用。
针对 GDI+ 的性能问题,我尝试把部分 GDI+ 操作也转发到 DXMT,用 Metal 来做渲染。这个优化把画图的启动时间从 3.8 秒降到了 2.4 秒,操作延迟也明显降低。但实现复杂度较高,需要仔细处理 GDI+ 和 D3D 之间的状态同步。
5. 常见问题与排查技巧实录
5.1 启动崩溃类问题
问题一:应用启动后立即闪退,日志显示EXC_BAD_ACCESS。
这是最常见的问题,通常是因为 JIT 内存分配失败。iOS 对MAP_JIT的分配有严格限制,如果应用没有正确的权限,mmap会返回MAP_FAILED。排查方法是检查jit_alloc的返回值,如果为 NULL,说明 JIT 不可用,需要回退到解释器模式。另外,确保你的应用在Info.plist中包含了com.apple.security.cs.allow-jit权限(仅限 macOS,iOS 上这个权限不可用,只能走解释器或静态翻译)。
问题二:Wine 初始化时卡在wineboot阶段。
wineboot是 Wine 用来初始化 prefix 目录的进程。在 iOS 上,它可能会因为文件权限问题卡住。检查你的 prefix 目录是否在应用的沙盒内,并且有读写权限。另外,wineboot会尝试创建一些符号链接,iOS 的文件系统对符号链接的支持有限,我建议在编译时禁用符号链接,改用文件复制。
问题三:程序窗口创建成功但内容全黑。
这通常是 DXMT 的问题。检查 Metal 设备是否创建成功,以及命令缓冲是否正常提交。一个常见的坑是:iOS 应用在后台时不能提交 Metal 命令,如果你的程序在启动时恰好处于后台状态,就会黑屏。确保在applicationDidBecomeActive之后再初始化 DXMT。
5.2 运行时报错类问题
问题四:程序运行中突然崩溃,日志显示unimplemented function。
这说明程序调用了一个 Wine 还没有实现的 API。你可以在 Wine 的源码中搜索这个函数名,看看是否有存根实现。如果没有,你需要自己实现一个。对于不重要的 API,可以直接返回成功,让程序继续运行。对于关键 API,就需要认真实现了。
问题五:中文显示为乱码。
这是 Wine 的字体和编码问题。Windows 程序通常使用 GBK 或 UTF-16 编码,而 Wine 在 iOS 上默认使用 UTF-8。你需要在 Wine 的注册表中设置正确的代码页:
[HKEY_LOCAL_MACHINE\System\CurrentControlSet\Control\Nls\CodePage] "ACP"="936" "OEMCP"="936" "MACCP"="10008"同时,确保 Wine 的字体目录中有中文字体。你可以把开源的文泉驿字体或者思源黑体复制到 Wine 的Fonts目录下。
问题六:程序无法保存文件。
检查 Wine 的驱动器映射。默认情况下,Wine 会把/映射为Z:盘,把 prefix 的drive_c映射为C:盘。在 iOS 上,你需要确保这些映射指向应用沙盒内的可写目录。另外,某些程序会尝试写入C:\Program Files,在 iOS 上这个目录是只读的。你可以通过注册表把Program Files重定向到可写目录。
5.3 性能与稳定性问题
问题七:程序运行一段时间后变卡。
这通常是 JIT 缓存满了,FEX-Emu 在频繁淘汰和重新编译代码块。增加 JIT 缓存大小可以缓解,但根本的解决方法是优化代码块的淘汰策略。我实现了一个基于热度的淘汰算法,优先保留执行频率高的代码块,效果比单纯的 LRU 好很多。
问题八:多线程程序频繁死锁。
Wine 的线程同步原语在 iOS 上需要仔细适配。特别是WaitForSingleObject和SignalObjectAndWait这两个 API,在 iOS 上需要用pthread_cond和pthread_mutex重新实现。我遇到过因为条件变量唤醒顺序问题导致的死锁,后来通过调整锁的粒度解决了。
问题九:Metal 命令提交超时导致应用被系统杀掉。
iOS 的看门狗会监控 GPU 命令的执行时间,如果单个命令缓冲执行超过一定时间(通常是几秒),系统会强制杀掉应用。对于复杂的 3D 场景,需要把渲染任务拆分成多个小命令缓冲,分帧提交。DXMT 内部已经做了这个优化,但如果你自己实现渲染逻辑,一定要注意这一点。
5.4 常见问题速查表
| 问题现象 | 可能原因 | 排查方法 | 解决方案 |
|---|---|---|---|
| 启动闪退 | JIT 内存分配失败 | 检查jit_alloc返回值 | 回退到解释器模式 |
| 卡在 wineboot | 文件权限或符号链接问题 | 检查 prefix 目录权限 | 禁用符号链接,改用复制 |
| 窗口全黑 | Metal 命令未提交 | 检查应用前后台状态 | 在 active 后初始化 DXMT |
| 中文乱码 | 代码页或字体缺失 | 检查注册表 CodePage | 设置 936 代码页,添加中文字体 |
| 无法保存文件 | 驱动器映射错误 | 检查dosdevices目录 | 重定向到可写目录 |
| 运行变卡 | JIT 缓存满 | 监控缓存命中率 | 增大缓存,优化淘汰策略 |
| 多线程死锁 | 同步原语实现问题 | 检查线程等待链 | 重新实现条件变量 |
| GPU 超时 | 命令缓冲执行过长 | 检查 Metal 命令提交 | 拆分命令缓冲,分帧提交 |
6. 几个容易被忽略的实操心得
6.1 关于 iOS 开发者模式的坑
在 iOS 16 以上版本,开启开发者模式需要重启设备,而且这个模式会在设备重启后自动关闭。这意味着你每次重启手机后,都需要重新开启开发者模式才能运行自签名的应用。我建议在开发阶段使用自动签名工具,减少手动操作的麻烦。另外,iOS 26.3.1 这个版本对开发者模式的限制更严格,部分 API 需要额外的权限声明,具体可以参考 Xcode 的签名与能力配置文档。
6.2 关于 uniapp 和原生插件混用的注意事项
如果你打算用 uniapp 来做外层壳,把 Madeira 作为原生插件集成进去,有几个点需要注意。uniapp 的 iOS 原生插件是通过Module和Component两种方式暴露的。Madeira 作为一个长时间运行的后台服务,适合用Module方式暴露,提供启动、停止、发送输入事件等接口。但 uniapp 的 JS 线程和原生线程之间的通信有延迟,对于实时性要求高的输入事件(比如游戏操作),建议直接在原生层处理,不要绕道 JS。
6.3 关于 Xcode 打包速度突然变慢的排查
在开发后期,我遇到了 Xcode 打包速度从 2 分钟突然变成 15 分钟的情况。排查后发现是 DerivedData 目录积累了大量缓存,而且 Madeira 的编译产物中有很多大文件,Xcode 的索引器在处理这些文件时耗时很长。解决方法是定期清理 DerivedData,并且在 Build Settings 中关闭不必要的索引选项。另外,把 FEX-Emu 和 Wine 的编译产物做成预编译框架(xcframework),可以大幅减少每次打包时的编译时间。
6.4 关于 iOS 设备模拟的局限性
Xcode 自带的 iOS 模拟器是 x86-64 或 ARM64 的 macOS 进程,它不能模拟 iOS 的 GPU 和 JIT 限制。也就是说,你在模拟器上跑通了 Madeira,不代表在真机上也能跑通。特别是 JIT 相关的代码路径,模拟器上完全不会触发 iOS 的限制。我的建议是:从项目第一天起就在真机上测试,模拟器只用来做 UI 布局的快速验证。
6.5 关于应用上架 App Store 的现实问题
如果你打算把 Madeira 打包上架 App Store,需要做好被拒的准备。苹果的审核指南对"在 iOS 上运行外部代码"有严格限制,特别是涉及 JIT 和动态加载的部分。我的经验是:如果应用只是运行内置的、经过审核的程序,通过率会高一些;如果允许用户自行导入任意的 Windows 程序,几乎肯定会被拒。替代方案是走企业签名或者 TestFlight 内测分发,但这两种方式都有各自的限制和成本。
7. 后续可以继续深挖的方向
这个项目跑通之后,我整理了一些还可以继续优化的方向。一是把 FEX-Emu 的 JIT 编译器做更激进的优化,比如针对 ARM64 的特定指令集扩展(如 SVE2)做代码生成,进一步提升性能。二是把 DXMT 的 Metal 后端升级到 Metal 3,利用网格着色器和光线追踪等新特性,让 3D 程序的渲染效果更好。三是探索把 Wine 的某些组件用 Swift 重写,减少 C 代码的维护成本,同时更好地与 iOS 系统集成。
另外,我还在研究如何把 Madeira 的架构应用到其他平台,比如 Android。Android 对 JIT 的限制比 iOS 宽松很多,理论上移植难度更低。但 Android 的图形栈是 Vulkan,需要把 DXMT 的 Metal 后端换成 Vulkan 后端,这部分工作量不小。
如果你也在做类似的事情,或者对某个技术细节有疑问,欢迎交流。这个领域的变化很快,新的工具和方案层出不穷,保持学习和尝试是最重要的。