让 Lua 与语言 L 互操作的明显方法是在 L 中实现 Lua API,但这对实现者来说令人生畏,并且会给 L 程序员带来冗长的语法负担。一个更简单的解决方案是在 L 中仅实现 lua_open
、lua_close
、lua_dobuffer
和 lua_register
,将 lua_register
扩展为执行语言间封送。然后可以在 Lua 中提供其他功能。
Lua API 允许从 C 完全控制 Lua 状态。但是,如果你想与另一种语言 L 互操作,该怎么办?假设 L 可以与 C 互操作,那么显而易见的方法是将 C API 反射到 L 中。然而,这是一个相当令人生畏的前景,因为 Lua API 非常庞大,并且有一些隐藏的微妙之处。此外,虽然它是一个编写 Lua 扩展库和工具的良好媒介,但它并不会自然而然地为 L 程序员带来方便的语法;Lua 手册显示(请参阅 4.0 版第 26 页的 5.12 节),Lua 语句
a,b = f("how", t.x, 4)
变成了对 Lua API 的十次调用。
有一种更简单的方法:使用 Lua 的第一条规则(“用 Lua 来做”)
lua_dostring(S, "a,b = f(\"how\", t.x, 4)");
其中 S
是要执行代码的状态。事实上,你无法使用 lua_dostring
做的唯一事情是从 Lua 获取值,并允许 Lua 调用 L。这两者都可以通过 lua_register
实现。
因此,语言互操作所需的一切是 lua_dostring
和 lua_register
,加上 lua_open
和 lua_close
以允许创建和销毁 Lua 状态。此外,最好使用 lua_dobuffer
而不是 lua_dostring
,因为它也可以处理预编译代码。
但是等等!在 C API 中,lua_register
没有说明要注册的函数的参数或结果类型;这些必须通过检查和操作 Lua 堆栈来处理。对此的一个简单但残酷的解决方案是让 lua_register
指定参数和返回值的类型和数量,并且只允许自然映射到 L 中的类型。
精简 API 的最终函数列表是
lua_open
和 lua_close
,用于允许创建和销毁 Lua 状态
lua_dobuffer
,允许从 L 调用 Lua
lua_register
(经过适当专业化),允许从 Lua 调用 L
在将 Lua 移植到 EPOC(Symbian 为 PDA 等移动设备设计的操作系统)时,我想提供一些用于访问操作系统功能(如 Eikon GUI)的挂钩。EPOC 基于 C++,看起来很有希望,但由于空间原因,其库不包含符号信息,因此无法按名称进行运行时动态链接。我不想使用 tolua,而是决定将 Lua 绑定到 OPL,即 EPOC 的解释型 BASIC 类 RAD 语言,它对 EPOC 提供了良好的支持,包括各种 OPX(用 C++ 实现的 OPL 库),并且允许按名称动态调用过程。
OPL 有四种基本类型:16 位和 32 位整数、64 位浮点数和字符串。16 位整数表示为 %
,32 位整数表示为 &
,字符串表示为 $
,浮点数不表示。OPL 支持类似 C 的函数原型,例如
foo&:(a,b%,c$)
foo
是函数的名称。&
表示它返回一个 32 位整数(所有 OPL 函数都返回一个值,如果没有显式的 RETURN
语句,则默认为零或空字符串)。冒号表示 foo
是一个函数。接下来是可选的参数列表;在本例中,有三个参数:一个浮点数 a
、一个 16 位整数 b%
和一个字符串 c$
。(字符串最长可以有 255 个字符;在此 API 中,不能直接与 Lua 交换更长的字符串。)
因此,我创建了一个小 OPX,它提供了以下 OPL 函数
LuaOpen&:
返回新状态的指针
LuaClose:(state&)
关闭给定的状态 Lua&:(state&,chunk$)
在给定的状态中执行给定的块(可以预编译,但这不太可能有用,因为它最长只能有 255 个字节)
LuaRegister:(state&,func$,name$)
在给定的 Lua 状态中使用 Lua 名称 name$
注册 OPL 函数,其原型由 func$
给出
Lua&:
看起来比 LuaDoBuffer&:
更好,因为它既恰当(Lua&:
是执行某些 Lua 的函数),又是一个简洁的名称,很可能成为这四个过程中最广泛使用的过程。当从 Lua 调用由 LuaRegister:
注册的 OPL 函数时,参数会自动转换为 OPL 类型,结果类型会转换回来。检查整数参数是否在范围内是程序员的责任。
乍一看,此界面似乎非常有限。例如,没有简单的方法来计算 Lua 表达式并将其结果返回给 OPL,也不可能在 OPL 中遍历 Lua 表。这是故意的:添加这些功能会使 API 变得复杂,而省略这些功能会鼓励程序员仅使用 OPL 为 Lua 提供库例程。毕竟,将 Lua 链接到 OPL 的主要动机是能够访问 EPOC,而无需首先为 Lua 编写大量 C++ 库。
然而,在某些情况下,我可能希望使用其他语言编写大部分应用程序,因为其应用程序域属性(例如,SQL 或 Prolog)。此外,我似乎正在将 Lua 从其作为应用程序扩展语言的预期用途提升为编写应用程序的主语言。
实际上,这里不存在冲突。不要将 Lua 视为应用程序扩展语言,而应将其视为一种胶合语言,将用其他语言编写的程序片段绑定在一起。应用程序功能的核心通常会用其他语言 L 实现,可能是为了追求速度而使用 C,或者使用某种特定于域的语言。通过将此核心构建为一个库,L 程序员可以自由地专注于在 L 中提供应用程序基元,而不用担心将它们联系在一起;L 很有可能不适合此目的。然后,应用程序可以作为 Lua 的一层实现,位于一系列库之上;这将特定于域的基元在 L 中的编程的不同关注点与配置特定应用程序分离开来,这使得应用程序更易于编写,并促进了 Lua 和 L 代码的重用。
如果确实有必要在 L 中实现 Lua API 的其他部分,那么,只要不是出于性能原因,仍然可以使用 L 回调在 Lua 中实现所需功能。事实上,完全有可能编写 Lua API 的 Lua 实现,然后该实现将与 Lua 通过精简 API 与之交互的任何语言一起工作。
Lua 可以通过一个非常简单的 API 与其他语言连接,该 API 主要标准 C API 的一个子集。只要目标语言可以与 C 协同工作,就可以快速实现,并提供编写 Lua 和目标语言混合应用程序所需的所有必要功能。精简 API 中的一些看似限制实际上有助于编写更多可重用的代码。