技术说明 4

原文:https://www.lua.org/notes/ltn004.html

Technical Note 4 技术说明 4

Last update: Mon Aug 12 15:49:10 EST 2002 by lhf.

A thin API for interlanguage working, or Lua in Four Easy Calls 用于语言间工作的简单 API,或 Lua 中的四个简单调用

by Reuben Thomas 作者:Reuben Thomas

Abstract 摘要

The obvious way to make Lua interwork with language L is to implement the Lua API in L, but this is daunting for the implementor, and burdens the L programmer with a verbose syntax. A simpler solution is to implement just lua_open, lua_close, lua_dobuffer and lua_register in L, expanding lua_register to do inter-language marshalling. Additional functionality can then be provided in Lua.

​ 使 Lua 与语言 L 相互操作的明显方法是在 L 中实现 Lua API,但这对实现者来说是艰巨的,并且会给 L 程序员带来冗长的语法负担。一个更简单的解决方案是在 L 中仅实现 lua_openlua_closelua_dobufferlua_register ,扩展 lua_register 以执行语言间封送。然后可以在 Lua 中提供其他功能。

The problem 问题

The Lua API allows a Lua state to be completely controlled from C. But what if you want to interwork with another language, L? The obvious approach, assuming that L can interwork with C, is to reflect the C API into L. However, this is a rather daunting prospect, as the Lua API is quite large and has some hidden subtleties. Also, while it is a good medium for writing extension libraries and tools for Lua, it does not lead naturally to a convenient syntax for L programmers; the Lua manual shows (see section 5.12 on p. 26 in the 4.0 edition) how the Lua statement

​ 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)

becomes ten calls to the Lua API.

​ 变为对 Lua API 的十次调用。

The solution 解决方案

There is an easier way: using the first rule of Lua (“Do it in Lua”):

​ 有一种更简单的方法:使用 Lua 的第一条规则(“在 Lua 中执行”):

lua_dostring(S, "a,b = f(\"how\", t.x, 4)");

where S is the state in which the code is to be executed. In fact, the only things you can’t do with lua_dostring are get values back from Lua, and allow Lua to call L. These can both be achieved with lua_register.

​ 其中 S 是要执行代码的状态。事实上,您无法使用 lua_dostring 完成的唯一操作是从 Lua 获取值,并允许 Lua 调用 L。这两者都可以通过 lua_register 实现。

Hence all that is needed for language interworking is lua_dostring and lua_register, plus lua_open and lua_close to allow Lua states to be created and destroyed. Also, it’s better to use lua_dobuffer than lua_dostring, as it can handle pre-compiled code too.

​ 因此,语言互操作所需的一切是 lua_dostringlua_register ,外加 lua_openlua_close 以允许创建和销毁 Lua 状态。此外,最好使用 lua_dobuffer 而不是 lua_dostring ,因为它也可以处理预编译代码。

But wait! In the C API, lua_register says nothing about the argument or result types of the function being registered; these have to be dealt with by inspection and manipulation of the Lua stack. A brutal but simple solution to this is to make lua_register specify the type and number of arguments and return values, and allow only such types as map naturally into L.

​ 但是等等!在 C API 中, lua_register 并未提及要注册的函数的参数或结果类型;这些必须通过检查和操作 Lua ​栈来处理。对此的一个简单但残酷的解决方案是让 lua_register 指定参数和返回值的类型和数量,并且只允许自然映射到 L 的类型。

The final list of functions for the thin API is:

​ 精简 API 的最终函数列表如下:

  • lua_open and lua_close, to allow Lua states to be created and destroyed lua_openlua_close ,允许创建和销毁 Lua 状态
  • lua_dobuffer, to allow Lua to be called from L lua_dobuffer ,允许从 L 调用 Lua
  • lua_register (suitably specialised), to allow L to be called from Lua lua_register (经过适当专门化),允许从 Lua 调用 L

Case study: Lua to OPL 案例研究:Lua 到 OPL

When porting Lua to EPOC, Symbian’s OS for mobile devices such as PDAs, I wanted to provide hooks to OS features such as the Eikon GUI. EPOC is C++-based, which looks promising, but for space reasons its libraries contain no symbol information, so run-time dynamic linking by name is impossible. Not wanting to resort to tolua, I decided instead to bind Lua to OPL, EPOC’s interpreted BASIC-like RAD language, which has both good support for EPOC, including a wide range of OPXs (OPL libraries implemented in C++), and allows procedures to be called dynamically by name.

​ 在将 Lua 移植到 EPOC(Symbian 为 PDA 等移动设备提供的操作系统)时,我想提供一些挂钩到操作系统功能(如 Eikon GUI)的方法。EPOC 基于 C++,看起来很有希望,但出于空间原因,其库不包含符号信息,因此不可能按名称进行运行时动态链接。我不想求助于 tolua,而是决定将 Lua 绑定到 OPL,即 EPOC 的解释型 BASIC 类 RAD 语言,它既很好地支持 EPOC(包括各种在 C++ 中实现的 OPX(OPL 库)),又允许按名称动态调用过程。

OPL has four basic types: 16-bit and 32-bit integers, 64-bit floats, and strings. 16-bit integers are denoted %, 32-bit integers &, strings $, and floats by nothing. OPL supports C-like function prototypes, for example:

​ OPL 有四种基本类型:16 位和 32 位整数、64 位浮点数和字符串。16 位整数表示为 % ,32 位整数表示为 & ,字符串表示为 $ ,浮点数不表示。OPL 支持类似 C 的函数原型,例如:

foo&:(a,b%,c$)

foo is the name of the function. The & indicates that it returns a 32-bit integer (all OPL functions return a value, which defaults to zero or the empty string if there is no explicit RETURN statement). The colon indicates that foo is a function. Next comes the optional argument list; in this case, there are three arguments: a float a, a 16-bit integer b%, and a string c$. (Strings may be at most 255 characters long; in this API, longer strings may not be exchanged with Lua directly.)

foo 是函数的名称。 & 表示它返回一个 32 位整数(所有 OPL 函数都返回一个值,如果没有显式的 RETURN 语句,则默认为零或空字符串)。冒号表示 foo 是一个函数。接下来是可选的参数列表;在本例中,有三个参数:一个浮点数 a 、一个 16 位整数 b% 和一个字符串 c$ 。(字符串最长可达 255 个字符;在此 API 中,更长的字符串可能无法直接与 Lua 交换。)

Hence, I created a small OPX which provided the following OPL functions:

​ 因此,我创建了一个提供以下 OPL 函数的小型 OPX:

  • LuaOpen&: returns a pointer to the new state LuaOpen&: 返回指向新状态的指针
  • LuaClose:(state&) closes the given state LuaClose:(state&) 关闭给定状态
  • Lua&:(state&,chunk$) executes the given chunk (which may be precompiled, but this is unlikely to be useful as it can be at most 255 bytes long) in the given state Lua&:(state&,chunk$) 在给定状态中执行给定的块(可以是预编译的,但这不太可能有用,因为它最多只能有 255 个字节长)
  • LuaRegister:(state&,func$,name$) registers the OPL function whose prototype is given by func$ in the given Lua state with Lua name name$ LuaRegister:(state&,func$,name$) 使用 Lua 名称 name$ 在给定的 Lua 状态中注册 OPL 函数,其原型由 func$ 给出

Lua&: seemed a better name than LuaDoBuffer&: as it is both apt (Lua&: is the function that does some Lua) and a nice short name for what is likely to be the most widely used procedure by far out of the four. When an OPL function registered by LuaRegister: is called from Lua, the arguments are automatically translated to the OPL types, and the result type translated back. It is the programmer’s responsibility to check that integer arguments are in range.

Lua&: 似乎比 LuaDoBuffer&: 更合适,因为它既恰当( Lua&: 是执行某些 Lua 的函数),又是一个简洁的名称,很可能成为四者中使用最广泛的函数。当从 Lua 调用由 LuaRegister: 注册的 OPL 函数时,参数会自动转换为 OPL 类型,结果类型会转换回来。检查整数参数是否在范围内是程序员的责任。

Is a thin API enough? API 是否足够精简?

At first sight, this interface may seem very limited. For example, there’s no simple way to evaluate a Lua expression and return its result to OPL, nor is it possible to traverse Lua tables in OPL. This is intentional: adding these facilities would complicate the API, and omitting them encourages programmers to use OPL only to provide library routines to Lua. After all, the main motivation for linking Lua to OPL was to be able to access EPOC without needing to write lots of C++ libraries for Lua first.

​ 乍一看,此界面似乎非常有限。例如,没有简单的方法来计算 Lua 表达式并将其结果返回给 OPL,也不可能在 OPL 中遍历 Lua 表。这是有意的:添加这些功能会使 API 复杂化,而省略这些功能会鼓励程序员仅使用 OPL 向 Lua 提供库例程。毕竟,将 Lua 链接到 OPL 的主要动机是能够访问 EPOC,而无需首先为 Lua 编写大量 C++ 库。

However, in some cases I might want to write much of the application in the other language, because of its application domain properties (for example, SQL or Prolog). Also, I seem to be promoting Lua from its intended use as application extension language to the main language in which the application is written.

​ 然而,在某些情况下,我可能希望用另一种语言编写大部分应用程序,因为它的应用程序域属性(例如,SQL 或 Prolog)。此外,我似乎正在推广 Lua,使其从作为应用程序扩展语言的预期用途转变为编写应用程序的主要语言。

Actually, there is no conflict here. Think of Lua not so much as an application extension language as a glue language, binding bits of programs written in other languages together. The core of the application’s functionality will often be implemented in some other language L, perhaps C for speed, or some domain-specific language. By structuring this core as a library, the L programmer is free to concentrate on providing application primitives in L, without worrying about tying them together; L may well not be suitable for this. The application can then be implemented as a layer of Lua on top of a series of libraries; this separates the different concerns of programming the domain-specific primitives in L from configuring the particular application, which makes the application easier to write, and promotes reuse of both Lua and L code.

​ 实际上,这里不存在冲突。不要将 Lua 视为应用程序扩展语言,而应将其视为胶水语言,将用其他语言编写的程序片段粘合在一起。应用程序功能的核心通常会用其他语言 L 实现,可能是为了速度而使用 C,或者使用某种特定领域语言。通过将此核心构建为库,L 程序员可以自由地专注于用 L 提供应用程序原语,而无需担心将它们捆绑在一起;L 可能并不适合这样做。然后,应用程序可以作为 Lua 层实现,该层位于一系列库之上;这将用 L 编程特定领域原语的不同关注点与配置特定应用程序分开,这使得应用程序更易于编写,并促进了 Lua 和 L 代码的重用。

If it is really necessary to implement other parts of the Lua API in L, then, provided it is not for performance reasons, the requisite functionality can still be implemented in Lua with L callbacks. Indeed, it would be possible to write a Lua implementation of the full Lua API which would then work with any language to which Lua was interfaced by the thin API.

​ 如果真的有必要在 L 中实现 Lua API 的其他部分,那么,只要不是出于性能原因,仍然可以在 Lua 中使用 L 回调实现所需的功能。事实上,可以编写一个 Lua 实现的完整 Lua API,然后该 API 可以与 Lua 通过精简 API 与之交互的任何语言一起使用。

Conclusion 结论

Lua can be connected to other languages with a very simple API, which is mostly a subset of the standard C API. It is quick to implement, provided that the target language can interwork with C, and provides all the necessary functionality for writing applications in a mixture of Lua and the target language. Some seeming restrictions in the thin API actually help to write more reusable code.

​ Lua 可以通过一个非常简单的 API 连接到其他语言,该 API 基本上是标准 C API 的一个子集。只要目标语言可以与 C 互操作,就可以快速实现,并提供编写 Lua 和目标语言混合应用程序所需的所有必要功能。精简 API 中的一些看似限制实际上有助于编写更可重用的代码。

最后修改 January 31, 2024: 更新 (e0896df)