在 Windows 95 上跑 Fun

bash 复制代码
作者: 一了 <1liao3@funlang.org>
日期: 2026-11-13

一句话总结: 2026 年把 Fun 的运行时丢进 Windows 95 OSR2:语言核心、MessageBoxA 的 FFI 弹窗、以及运行时生成的机器码都跑通了。
  

在 Windows 95 上跑 Fun

2026 年,拿 Windows 95 测一门语言的兼容性,听起来像行为艺术。但我手边正好有一台 OSR2 虚拟机,版本号 4.00.1111,微软当年管它叫 Windows 95B。Fun 的运行时只有 300KB 左右,零外部依赖,官方文档里写的支持平台是 Windows 32/64、Linux、ARM 和 Windows CE,没有 Win95,也没有 Win98。官方没承诺的东西,跑一下才知道。

先跑 all.fun。这是 Fun 自带的全特性覆盖脚本,类、闭包、短路求值、case...is、loop/for/while、try/except/finally、字符串插值、正则、eval,基本语言核心能扫到的都在里面。在 Win95 上执行,输出顺序和语义全部正确。闭包里的计数器按预期自增,and/or 的短路没有跳步,finally 照常执行,1/0 抛出的异常被 except 接住,$@@() 还能取到出错位置。

然后是最直接的 FFI 测试:

bash 复制代码
use 'lib-os.fun';

var msgbox = 'user32'.getapi('MessageBox', 'issi:i');
var mb(s)  = msgbox(0, s, 'Title', 0x222);

?. mb('Test');

MessageBox 弹出来了,标题 "Title",正文 "Test",0x222 对应的 Abort / Retry / Ignore 三个按钮都在。返回值 3、4、5 和按钮对得上。

这件事能成立,需要几条同时不出错:

第一,FFI 层认 ANSI。 Win95 的 user32.dll 里只有 MessageBoxA,没有 MessageBoxW。如果 getapi 硬找宽字符版,函数地址就是空。Fun 的 'MessageBox' 能解析到 ANSI 导出,说明它对 Win9x 时代的 API 命名习惯有正确的 fallback。

第二,调用约定是 stdcall。 Win32 API 全部走 stdcall。如果 FFI 默认 cdecl,栈在调用返回时就会错位。弹窗能正常关闭、程序继续跑,说明约定对了。

第三,字符串封送走的是 ANSI 代码页。 Fun 的 string 传到 LPCSTR,没有经过 UTF-16 中转,也没有依赖只在 NT 上存在的转换函数。

第四,模块加载和动态符号解析可用。 use 'lib-os.fun' 先要读文件、解析、执行;getapi 背后是 LoadLibrary + GetProcAddress。这两条路在 Win95 上都是原生能力,Fun 没有在中间加 NT 独有的依赖。

这四条里任何一条踩坑,弹窗就不会出现。它出现了,说明 Fun 的 FFI 路径在 API 子集上是认真按 Win9x 做的保守实现,而不是碰巧能启动。


弹窗只证明"能调外部已有函数"。更硬的一条线是:Fun 能不能自己生成机器码并执行。能调别人的函数和能造新函数,是两种不同的能力。

lib-asm.fun 提供的是后者。下面这段在 Win95 上跑通了:

bash 复制代码
use 'lib-asm.fun';

#*
pop ebx
pop eax
push eax
push ebx
mov ebx,esp
inc eax // codes ...
ret $0004
#
var s = `
5b
58
50
53
89e3
40
`; #*
C20400
`; #

var asm = Assembly('i:i', s);
?. asm.Load().Run(1000);
asm.Delete();
?. 'Ok';

输出:

bash 复制代码
1001
Ok

逐条拆机器码:

bash 复制代码
5b      pop ebx        ; 弹出返回地址到 ebx
58      pop eax        ; 弹出参数 1000 到 eax
50      push eax       ; 把 1000 压回栈
53      push ebx       ; 把返回地址压回栈
89e3    mov ebx, esp   ; ebx = 当前栈指针
40      inc eax        ; eax = 1001
c20400  ret 4          ; 弹出返回地址,esp += 4 清理参数

Assembly('i:i', s) 声明签名是"一个 int 参数,返回 int",Run(1000) 把 1000 传进去。执行路径:入口栈顶是返回地址、下面是参数 1000;pop ebx / pop eax 把两者取出,push eax / push ebx 按原顺序压回,栈布局恢复;inc eax 把 1000 变成 1001;ret 4 弹出返回地址并跳过 4 字节参数,栈平衡。返回值在 eax 里,Run(1000) 返回 1001 。asm.Delete() 释放可执行内存,?. 'Ok' 输出 Ok。

这段代码能跑通,说明四件事同时成立:

  • 可执行内存可用 :Fun 能分配 PAGE_EXECUTE_READWRITE 内存并写入机器码,Win95 的 VirtualAlloc / VirtualFree 路径没踩坑;
  • 调用约定匹配 :Run(1000) 的封装和 ret 4 的 stdcall 风格清理是配套的,否则返回后栈会偏,Ok 就打印不出来;
  • 返回值封送正确 :eax 里的 1001 被 Fun 按 'i:i' 签名读回成 int;
  • 内存释放干净 :Delete() 之后程序继续执行,没有崩。

两个 ABI 细节要说明白,避免误导:

  • ebx 是 callee-saved 寄存器 ,这段代码 pop ebx 后又用 mov ebx, esp 改掉了它,返回时没有恢复原值。能跑,说明 Fun 的 Run 封装不依赖 ebx 的旧值。严格写法应在入口 push ebx、出口 pop ebx。
  • ret 4 是 stdcall 风格的参数清理。如果封装按 cdecl 走,这里会多偏移 4 字节。实际输出正常,说明两边约定是配套的。

这段例子比弹窗更能说明问题:弹窗证明 Fun 能调外部已有函数,机器码证明 Fun 能分配可执行内存、写入指令、按签名调用、拿回返回值、再释放。整条代码生成/执行链路在 Win95 上通了。大多数脚本语言止步于"解释执行 + FFI 调外部函数",能走到"运行时生成机器码并执行"的,本身就不多。


为什么 Win95 能跑,之前的推断反而错了?

我起初的判断是:Delphi 2006 编译,Win95 大概率没戏。理由是 Delphi 2006 的 VCL 某些路径会调用 MSGWaitForMultipleObjectsEx,这个函数在 Win95 的 user32.dll 里不存在,一调就崩。这个坑确实存在,但它只属于走 VCL 高级消息循环/同步封装的代码 。Fun 的运行时核心和 FFI 层如果直接调 kernel32 / user32 的基础导出,根本不经过那层封装,坑就绕过去了。

另一个被高估的风险是"Win95 缺 602 个 NT 侧 API"。这个数字本身没错,但 Fun 实际用到的 API 子集非常窄:堆内存、文件读写、LoadLibrary/GetProcAddress、VirtualAlloc、MessageBoxA、基础字符串处理。这些恰好全在 Win95 的支持范围里。API 缺失的数量吓人,但和 Fun 无关的那部分,不构成障碍。

所以结论可以修正为:Fun 在 Win95 上跑通语言核心、基础 FFI 和机器码执行,不是意外,是 API 子集保守到位的自然结果。


边界也要说清楚。

这次验证的是语言核心 + 动态 FFI + 基础 Win32 互操作 + 可执行内存里的机器码 。库层没有系统测试。Win95 默认只有 wsock32.dll,Winsock 2 的 ws2_32.dll 要另外安装;加密 API 比 Win98 还少一截;线程和同步原语的支持也更有限。如果某个库模块依赖这些,Win95 上大概率不可用。lib-os.fun 里的路径、文件属性、进程与窗口这类基础封装如果也按 ANSI/Win9x 子集写,可用面会比想象中大,但网络和现代加密那条线,不要期待。

这不是一门"为 Win95 设计"的语言。Fun 的支持列表里本来就没有 Win95,也没有 Win98。但一个运行时能在 2026 年,在一台版本号 4.00.1111 的机器上,把闭包、异常、正则、eval、MessageBoxA 和一段手写机器码一起跑通,它至少证明了一件事:它的 Win32 依赖足够老派,老派到跨越了三十年的系统版本。

实用价值很小。兼容性信号很强。后者不是噱头:弹窗不会说谎,1001 和 Ok 也不会。

相关推荐
ö Constancy5 小时前
一台电脑的硬件组成与作用:按功能分类详解(Windows 与 Linux 视角)
linux·windows·电脑
雪落漂泊1 天前
Liunux进程(七):程序控制篇(中)
linux·运维·windows
承渊政道2 天前
Fooocus本地AI绘图教程:Windows部署、低门槛生图与远程访问
人工智能·windows·cpolar·fooocus·生图
陈工大模型2 天前
收到乱码压缩包之后,我重新测了 7-Zip、Bandizip 和win解压缩
windows·电脑·软件工程·软件需求·7-zip
谙弆悕博士2 天前
在文件夹右键直接打开 Kali WSL:一条 `wsl --cd` 注册表脚本
windows·脚本·wsl·右击·ksli
陈工大模型2 天前
解压工具的三个技术分水岭:编码、分卷、容器格式
人工智能·windows·安全·解压工具·免费国内办公软件
Zucker n3 天前
文献发现、课题组织与证据可追溯的论文阅读。
论文阅读·windows·ubuntu
flyingonpine4 天前
PowerShell脚本数字签名实战:自签名证书、代码签名与信任链验证
windows·信任链
goehou4 天前
让 AI 替我操作这台电脑,先把手能伸多远定下来
windows·自动化·权限管理·ai agent