有位开发者花了三年,把亲手写的C++库全部改写成C89标准。他的目标不是性能优化,而是让同一套代码能在Windows 95和Windows 11上同时编译运行。

这听起来像技术考古,但背后藏着一条被忽视的产品逻辑:当所有人都在追逐最新硬件时,有人发现"向后兼容"本身就是稀缺能力。

打开网易新闻 查看精彩图片

从C++98退到C89:一场逆向技术选型

这个叫LDL(Little Directmedia Layer)的项目,最初用C++98编写。作者坦言,C++98在当时已经提供了"不错的可移植性"。

但三年开发中,他逐渐推翻了自己的技术栈:

「我彻底转向C89(ANSI C)——这为旧编译器和平台提供了最大兼容性,包括DOS、Windows 95、Solaris,甚至PlayStation 1。」

C89是1989年发布的C语言标准,比C++98早了近十年。选择它意味着放弃模板、异常处理、面向对象等现代特性,换取的是几乎所有C编译器都能理解的语法子集。

这个决策的代价和收益都很具体。代价是代码组织更繁琐,没有标准容器库,字符串处理回到手工时代。收益是一张惊人的兼容清单:从MS-DOS的Turbo C到嵌入式系统的专用编译器,C89几乎是唯一通吃的方言。

作者没有解释为什么PlayStation 1值得关注,但这款1994年的主机至今有活跃的自制软件社区。对特定场景——教育演示、复古游戏移植、工业设备维护——"能跑"比"跑得快"更有商业价值。

放弃大版本发布:用迭代对抗完美主义

LDL的发布策略同样反常规。作者原本计划憋一个功能完整的1.0版本,后来彻底改弦更张:

「我放弃了一次性发布完整1.0的想法——现在改为迭代发布。」

当前0.1版本只包含三件事:窗口管理、事件处理、图形渲染。接下来的路线图是二维渲染器、音频、字体支持。

这种"先跑起来再长全"的思路,本质上是对开源项目死亡模式的回应。太多个人项目卡在"还差一个功能就能发布"的无限延期中,作者选择用可见的进度换取社区耐心。

具体的时间承诺是:窗口和事件已经可用(✅),2D渲染器即将推出(),音频和字体随后跟进()。表情符号的使用暗示这不是严肃的企业级规划,而是个人开发者的诚实沟通。

不做替代者,做转换层:LDL的架构设计

LDL最有趣的设计决策是后端策略。它明确声明:「不试图替代SDL、SFML或GLFW——而是成为它们之上的一层。」

计划支持的后端列表包括:SDL 1.2、SDL 2.x、SDL 3.x、SFML、GLFW。这意味着开发者可以在不改动业务代码的情况下,切换底层实现。

作者列举了三种典型场景:

第一,原生支持尚未就绪的平台,可以临时通过现有库的后端过渡。比如想支持某个小众系统,但还没写完原生驱动,先接SDL 1.2顶着。

第二,熟悉SDL或GLFW的开发者可以尝试LDL,无需完全替换工具链。学习成本被压缩到API差异层面,而非重新理解事件循环和上下文管理。

第三,可以更早使用这些库的特性。比如SDL的音频和字体功能已经成熟,而LDL的原生实现还在排队,直接走SDL后端就能先用起来。

这种"桥接"定位回避了与成熟项目的正面竞争,同时把它们的生态变成自己的护城河。对开发者而言,LDL提供的是"未来可迁移"的期权:今天用SDL后端快速启动,明天原生支持到位后无缝切换。

代码解剖:一个最小窗口的完整实现

作者提供的示例代码展示了LDL的API风格。这是一个800×600窗口的完整实现,包含初始化、事件循环、资源清理和错误处理。

代码结构遵循经典的C语言模式:显式分配(LDL_ResultNew、LDL_ContextNew、LDL_WindowNew)、状态检查(LDL_ResultIsOk)、轮询事件(LDL_WindowGetEvent)、延迟渲染(LDL_Delay(16)约等于60fps)、逆序释放(LDL_WindowFree...)。

几个设计细节值得注意:

上下文类型显式声明为LDL_ContextOpenGL1,暗示未来可能有OpenGL 3.x、Vulkan等其他后端选项。坐标和尺寸用LDL_GetVec2i封装,避免直接暴露整数对,为未来的向量运算扩展留门。

事件处理采用嵌套循环结构:外层检测窗口是否运行(LDL_WindowIsRunning),内层排空事件队列(LDL_WindowGetEvent)。退出条件同时响应窗口关闭事件和ESC键按下,这是跨平台GUI的保守做法。

错误处理没有使用异常或返回码,而是通过结果对象(LDL_Result)聚合状态。失败时调用LDL_ResultGetMessage获取可读错误信息,这种设计在C89的限制下保持了一定的调试友好性。

整个示例没有隐藏宏魔法,没有生成代码,没有隐式的全局状态。每一行资源操作都有对应的释放调用,符合嵌入式和长期运行系统的可靠性要求。

30年兼容性的商业隐喻

LDL的slogan是"One API for 30 Years of Computer History"。这个数字不是夸张:Windows 95发布于1995年,到今年正好30年。

但"30年兼容"的真正含义不是技术怀旧,而是一种被低估的产品能力——时间套利。

在消费电子领域,硬件迭代周期以月计算,软件框架的生命周期以年计算。这种速度创造了繁荣,也制造了废墟:无数项目困在废弃的依赖版本中,无数设备因为操作系统升级变成电子垃圾。

LDL指向的是一个反直觉的细分市场:那些不能、不愿或不需要跟随升级节奏的场景。

工业控制系统的维护周期可能长达20年,医疗设备认证与特定软件版本绑定,教育机构的机房预算按十年规划,复古游戏社区持续为30年前的硬件生产内容。这些场景的共同点是对"稳定"的定价远高于对"新功能"的定价。

作者没有明确声明目标用户,但从兼容清单可以反推:DOS指向遗产系统和模拟器开发者,Windows 95指向复古软件 preservation,Solaris指向企业Unix环境,PlayStation 1指向家用机自制软件社区。

这些市场的支付意愿和能力往往被忽视,因为它们不符合"增长"叙事。但它们的竞争强度也远低于消费级市场,单个开发者的项目可能就能建立壁垒。

技术债还是技术资产?

LDL的C89选择会引发典型的技术争论。批评者会认为这是在累积技术债:现代C++的类型安全、RAII资源管理、标准库算法都能减少bug,放弃这些等于主动制造维护负担。

作者的回应隐含在架构设计中:LDL本身不提供复杂功能,复杂功能交给后端(SDL等)或留给未来版本。C89的"简陋"被限制在薄封装层,而非贯穿整个技术栈。

这是一种分层防御策略:用最低公共 denominator 保证最大兼容性,用后端插件接入现代能力,用迭代发布控制风险暴露。

对比SDL的发展轨迹更能理解这种定位。SDL 1.2发布于1998年,至今仍有项目依赖;SDL 2.0在2013年推出,API不兼容;SDL 3.0于2024年发布,再次打破兼容。每次大版本升级都是社区的分裂事件。

LDL的"One API"承诺试图冻结这个动荡的接口层。如果成功,它将成为比SDL更持久的抽象——不是因为它技术更先进,而是因为它变更更缓慢。

这种"以不变应万变"的策略在基础设施软件中有成功先例。OpenGL的固定功能管线被批评多年,但正是它的稳定性支撑了跨平台图形开发;C语言本身被预言死亡数十年,仍是系统编程的通用语。

个人项目的产品化边界

LDL目前的状态是0.1版本,作者一人开发,功能残缺,文档有限。但它已经做出了几个关键的产品决策:明确的版本策略、清晰的后端架构、公开的路线图、宽松的许可(CC0示例代码)。

这些决策的成熟度超过了多数同类项目。很多开源库停留在"能用但不知道怎么用"或"功能完整但不再维护"的两极,LDL试图找到可持续的中间状态。

最大的不确定性是作者能否维持迭代节奏。从C++98到C89的重写已经消耗三年,0.1到1.0的功能清单(2D渲染、音频、字体)如果按同等密度开发,可能需要 another 数年。

但作者似乎接受了这种时间尺度。「项目不会在线下停滞多年,而是逐渐在社区面前成长」——这句话既是承诺,也是自我约束。公开进度意味着公开失败的可能,但也意味着公开获得帮助的机会。

对于观察者来说,LDL的价值可能不在于直接使用,而在于它提出的问题:当行业默认"最新最好"时,兼容性的定价是否被系统性低估?当框架追逐功能广度时,接口的持久性是否被忽视?

这些问题没有标准答案,但LDL提供了一个具体的实验场。它的成败将检验一个假设:在快速迭代的主流之外,存在对"慢"的需求,且这种需求可以被单个开发者满足。

毕竟,能让同一套代码在Windows 95和Windows 11上同时运行,这件事本身就像个技术冷笑话——而笑完之后,你可能会想起某个还在用XP的亲戚,某台还在跑DOS的机床,某个还在维护PS1模拟器的周末。