需求
LotusScript 最近的更新是版本 7/8 时增加的 WebService 调用者、提供者,这已经是快十年前的事情了。现在 LotusScript 已经明显落后,欠缺的大量基础能力:JSON 解析器、跨代理的数据缓存、HTTP Client、完善的面向对象机制、将函数作为参数传递……这严重制约了应用层面的开发。
IBM 对此问题的应对是推出 XPages,一种新的开发模式。XPages 确实是一个不错的东西,展现和数据分离、內置 Ajax 框架……但是,作为应用开发商我们无法抛弃历史资产一切推倒重来。所以 XPages 我们只能用来做一些新的业务、功能点,对于原有的业务还是要继续在 LotusScript 上发展。
所以我们必须自己来扩展 LotusScript 的能力。
方案
LotusScript 类、函数库
首先想到的就是直接用 LotusScript 来实现,但这只能解决一部分问题:比如 JSON 解析器。而类似于 HTTP Client 这种,LotusScript 就无能为力了。
我们也确实做了一版 LotusScript 的 JSON 解析器,但是性能实在是太差了:由于需要一个字符一个字符的解析,最终导致一个 15K 的 JSON 串解析出来要花 0.5 秒。结果我们又废弃了它,重新回到手工拆解字符串的老路上。
此路不通。
LSX(LotusScript eXtension)
这是最正统的扩展 LotusScript 的方式(看看名字就知道),实际上我们使用的 NotesDocument 等 Domino 后台对象都是用这种方式扩展出来的。实际的 C 代码编译成共享库,位于 nlsxbe.dll(n=windows平台前缀,lsx,be=Back-end)中,LotusScript 引擎会自动加载它,所以我们不用再通过 USELSX lsxbe 来手工加载。
LSX 的优点有很多:跨平台、与 LotusScript 无缝集成……
但是,LSX 现在已经处于一个半废弃的状态。为什么这么说呢,LSX 最新版 8.0 于 2009 年发布,再往前一版就是 1999 年了。所以即使是最新版也缺少了很多内容:xlinux 64 位的支持等。
此路不通。
LS2C(LotusScript to C API)
这是一个比较成熟、使用较多的方案。一般都用于在 LotusScript 中调用底层的 C API(例如隐藏设计、重新编译所有代码),因为这些 API 没有暴露到 LotusScript 这一层。
LS2C 与 LSX 相比会多一些限制,比如:
- 只能传递数值、字符串等基础类型,无法传递 NotesDocuemnt
- 不支持 LotusScript 扩展语法(doc.itemName 这种)
但这个方案至少在持续的更新,而且 C 这个层面不需要使用 Lotus 的开发工具包,用任何编译器生成标准的共享库即可。
我们最终采用了此方案。
经验总结
变长字符串
C 语言中不支持变长字符串,所以我们在返回变长字符串时,实际返回的是一个 handle 值;这个 handle 值再通过另一个 API 获取实际的字符串(类似于 NSFDbOpen 返回的 handle)。
handle 用完之后要注意释放(类似于 NSFDbClose)。
中文的处理
LS2C 的字符串参数传递有三种声明方式:
- arg As String
采用操作系统本地的编码方式,windows 一般为 GBK,linux 一般为 UTF-8。不同系统编码不同,不利于统一处理 - arg As LMBCS String
不管操作系统本地的编码方式,统一采用 LMBCS。这种编码方式适用范围太小,仅 Lotus 自己的产品线 - arg As Unicode String
不管操作系统本地的编码方式,统一采用 UTF-16。推荐使用此方式,对环境的适应性更好。
但要注意 UTF-16 英文也用双字节存储,C 语言中会把其中的 0x00 识别为字符串结束。所以进入到 C 代码内部后,可以转为 UTF-8 存储。
还要注意不同硬件下字节序的问题,x64 和 Power 架构下刚好是相反的。
Double 类型的传递
LS2C 在 64 位环境下的参数传递有 bug,浮点数无法正常处理。
bug 已经提交给 IBM 处理(更新:已确认不修复),在解决前可以先把参数改为按引用传递(即使是只读的参数,也不按值传递)。
资源的释放
有一部分内存在 C 代码中分配后,后续会被 LotusScript 端继续使用。所以需要在 LotusScript 端不再使用的时候,手工释放这部分资源。
推荐的做法是把这类资源包装成 LotusScript Class,在 Delete 析构函数中释放资源。这样相当于 LotusScript 引擎会帮忙自动跟踪变量的引用,比较方便。
调试共享库加载失败
Domino 给出的错误信息很模糊:“dll 加载失败”,没有太多有用的信息。而错误背后可能的原因有很多:文件找不到、32/64 位不匹配、缺少依赖的库……
可以先通过其他语言(比如 LuaJIT)调用共享库,会得到更详细的错误信息。成功之后再放到 Domino 环境下调试。
共享库的权限
Linux、Aix 环境下要注意上传的 so 库必须要有运行权限。
共享库的更新
Linux、Aix 环境下 so 库更新的行为有些奇怪,有时候覆盖 so 文件后会导致 Domino 崩溃。看上去像是内存中保留了一部分旧版的代码,与新版搭配在一起就不行了。
所以更新共享库时要停掉Domino。
总结
虽然 LotusScript 已经步入晚年,但我们希望通过 LS2C 这种方式给它注入新的活力,在既有业务资产中继续发光发热。