老旧Pascal程序在过去是很多企业内部系统的核心载体,但随着技术迭代,这些跑在传统Windows服务器上的程序,很难适配现在的容器化部署环境,本文就聊怎么让这些“老伙计”顺利住进容器,解决最核心的两个问题:资源句柄继承和Windows消息队列隔离的适配。

一、老旧Pascal程序搬去容器的核心痛点

旧Pascal程序大多是几十年前写的,写的时候都是直接绑定在宿主机的Windows系统上,依赖宿主机的文件、网络、消息队列等资源,一旦搬到容器里,这些资源就被隔离了,程序自然就“找不到北”了。最常见的两个痛点:一是资源句柄失效,旧程序打不开之前的配置文件、串口或网络端口;二是消息队列隔离,桌面版的旧程序收不到宿主机的自定义消息,导致定时任务、自定义功能失效。

二、资源句柄继承的适配技巧

2.1 什么是老旧Pascal程序的资源句柄

说白了,资源句柄就是系统给程序的“专属钥匙”,比如你写的旧Pascal程序打开了一个配置文件,系统就给它发一把这个文件的钥匙,程序用这把钥匙就能读到内容;如果是连接了串口、网络端口,对应的钥匙就是这些硬件或网络资源的。旧程序写的时候,默认拿的是宿主机的资源钥匙,跑到容器里,容器的资源是单独隔离的,宿主机的钥匙在容器里根本没用,所以程序会报错说“找不到资源”。

2.2 资源句柄继承的具体操作

其实不用改旧程序的核心代码,只要用Docker的“挂载”功能,把宿主机的资源映射到容器里,让旧程序以为还在宿主机上跑就行,下面举个具体的例子: 技术栈:Free Pascal + Windows Nano Server容器

program OldPascalConfigReader;
uses SysUtils, Classes;
var
  ConfigFile: TextFile;
  LineContent: string;
begin
  // 旧程序原来写死的是宿主机路径,比如"C:\企业系统\config.ini"
  // 这里改成容器内的对应路径,方便映射管理,不用改核心逻辑
  AssignFile(ConfigFile, 'C:\app\mounted_config.ini');
  try
    Reset(ConfigFile);
    Writeln('成功打开配置文件,开始读取内容:');
    while not Eof(ConfigFile) do
    begin
      ReadLn(ConfigFile, LineContent);
      Writeln('配置项:', LineContent);
    end;
    CloseFile(ConfigFile);
  except
    on E: Exception do
      Writeln('出错啦,原因是:', E.Message);
  end;
end.

然后是Docker配置文件,技术栈:Docker + Free Pascal 3.2.2 for Windows:

# 基于Windows Nano Server镜像,体积小,适合容器快速部署
FROM mcr.microsoft.com/windows/nanoserver:ltsc2019
# 安装Free Pascal运行时,确保容器内能运行编译好的程序
RUN curl -o fpc.zip https://downloads.freepascal.org/fpc/3.2.2/i386-win32/fpc-3.2.2.i386-win32.zip
RUN tar -xf fpc.zip -C C:\
# 创建程序运行的目录,和代码里的路径保持一致
RUN mkdir C:\app
# 把编译好的Pascal程序复制到容器的目标目录
COPY OldPascalConfigReader.exe C:\app\
# 设置挂载点,将宿主机的目录映射到容器内的C:\app,实现资源继承
VOLUME C:\app
# 设置工作目录,容器启动后默认进入这个目录
WORKDIR C:\app
# 容器启动时自动运行程序
ENTRYPOINT ["OldPascalConfigReader.exe"]

最后是容器启动命令,技术栈:Shell:

# 注意:D:\myconfig是宿主机存放配置文件的目录,需提前手动创建并放入config.ini
docker run -v D:\myconfig:C:\app old-pascal-config

这样容器内的C:\app就和宿主机的D:\myconfig共享,旧程序打开文件的资源句柄就继承过来了,不会出现资源找不到的错误。

三、Windows消息队列隔离的适配技巧

3.1 旧Pascal程序对消息队列的依赖

旧Pascal程序如果是桌面应用,肯定会用到Windows的消息队列,比如定时任务用WM_TIMER消息,自定义功能用WM_USER消息,或者窗口程序处理用户输入的消息。这些消息都是走宿主机的全局消息队列,容器默认会隔离这个队列,所以旧程序跑到容器里后,收不到这些消息,就会卡住、没反应。

3.2 消息队列隔离的适配方法

核心是把旧程序的消息处理从“全局”改成“本地”,不要依赖宿主机的全局消息队列,改成容器内的本地消息队列,避免和宿主机冲突,示例代码: 技术栈:Free Pascal + Windows API

program LocalMessageDemo;
uses Windows, SysUtils;
const
  WM_CUSTOM = WM_USER + 100; // 自定义消息ID,保持和旧程序一致
var
  Msg: TMsg;
  LocalHwnd: HWND;
// 自定义窗口过程,处理容器内的本地消息
function LocalWndProc(hwnd: HWND; uMsg: UINT; wParam: WPARAM; lParam: LPARAM): LRESULT; stdcall;
begin
  case uMsg of
    WM_CUSTOM:
      begin
        Writeln('收到本地自定义消息,参数:', wParam);
        Result := 0;
        Exit;
      end;
    else
      Result := DefWindowProc(hwnd, uMsg, wParam, lParam);
  end;
end;
begin
  // 创建本地窗口,绑定自定义窗口过程,用容器内的窗口句柄,不依赖宿主机全局窗口
  LocalHwnd := AllocateHWnd(@LocalWndProc);
  // 发送本地自定义消息,走容器内的独立消息队列
  PostMessage(LocalHwnd, WM_CUSTOM, 2024, 0);
  // 处理容器内的消息循环,不会和宿主机消息队列冲突
  while GetMessage(Msg, LocalHwnd, 0, 0) do
  begin
    TranslateMessage(Msg);
    DispatchMessage(Msg);
  end;
  // 释放窗口句柄,避免资源泄漏
  DeallocateHWnd(LocalHwnd);
end.

这段代码的关键是,不用全局消息钩子,也不用宿主机的窗口,完全用容器内的本地资源处理消息,既符合隔离要求,又能正常运行。

四、应用场景

这些适配技巧最适合传统企业的 legacy 系统迁移,比如很多制造、商贸企业,都有十几年前用Pascal写的内部系统,比如财务核算系统、库存管理系统,这些系统稳定,但跑在旧的Windows Server物理机上,运维成本高、硬件老化。把它们迁到容器里,既能减少物理服务器的数量,又能利用容器的资源调度功能,比如自动重启、按需扩容,而且不用重写代码,风险和成本都极低。比如某制造企业的旧ERP模块,之前跑在3台Windows Server 2008上,用这些技巧迁移后,只需要2个Windows Nano Server容器就能跑,运维工作量减少了70%。

五、技术优缺点

优点:第一,适配成本极低,旧程序几乎不用改,只要调整路径和消息处理的小部分逻辑,不需要重新开发;第二,容器化后的好处多,比如快速部署,只要有Docker环境就能启动容器,资源隔离也能避免多个旧程序互相干扰;第三,稳定性有保障,旧程序在熟悉的Windows环境里跑,不会因为新特性出问题。缺点:第一,对宿主机硬件资源依赖的旧程序,比如用了串口、打印机的,适配起来麻烦,因为容器默认隔离硬件;第二,Windows容器镜像体积比Linux大,Nano Server镜像约200MB,而Alpine Linux镜像只有几MB,占用的存储和网络资源更多;第三,如果旧程序用了Windows全局钩子,会和容器的隔离机制冲突,必须修改代码才能运行。

六、注意事项

这里要讲几个关键的踩坑点:第一,资源挂载的路径必须一致,宿主机和容器的目录要对应,比如代码里写的是C:\app,宿主机不能用其他名称,而且宿主机目录要给足够的读写权限,不然程序读不了文件;第二,旧程序如果是图形界面的,Windows Nano Server不支持,必须改成控制台程序,或者用Windows Server Core镜像,但Server Core镜像体积达5GB,尽量还是改成控制台;第三,消息处理一定不要用全局钩子,必须用本地窗口和消息队列,全局钩子会被容器隔离机制阻止,导致程序收不到消息;第四,测试必须在本地开发环境先测,比如用Docker Desktop的Windows容器,没问题再上生产,避免线上出问题;第五,旧程序用的第三方组件,要确认在Windows Nano Server里能运行,比如有些Pascal组件依赖特定DLL,要把DLL一起复制到容器的程序目录;第六,要调整容器的资源限制,旧程序如果打开很多句柄或端口,超过容器的句柄限制会报错,需要手动调整。

七、总结

让老旧Pascal程序跑在容器里,核心就是两个适配方向:资源句柄通过Docker挂载实现继承,消息队列改成容器内的本地队列,不用重写旧程序就能享受到容器的优势。这种方法非常适合传统企业的 legacy 系统迁移,既能保留旧系统的稳定性,又能降低运维成本、提升部署效率,是一种性价比极高的方案。