Lua 的编译脚本几乎是在一个简短的标头之后转储出来的原始字节码。头文件记录了用于编译字节码的平台的一些属性,但加载器仅验证当前平台是否具有相同的属性。
不幸的是,这在加载在另一个平台上编译的字节码时会产生问题,即使是由相同版本的 Lua 编译。当然,不同版本的 Lua 编译的脚本不能正常工作,而且由于 Lua 的版本号包含在字节码头中,因此加载它们的尝试被内核捕获。
简单的答案是不编译脚本。如果 Lua 自己编译脚本,您只需要担心应用程序的各种构建中 Lua 内核之间可能存在的版本不匹配,这并不难处理。
实际上支持编译字节码的完全交叉兼容性并不容易。在该电子邮件中,Mike Pall 指出了以下问题:
Endianess:根据需要交换输出。
sizeof(size_t)
, 影响巨大的字符串常量:降级时检查溢出。
sizeof(int)
, 影响MAXARG_Bx
和MAXARG_sBx
: 降级时检查溢出。
typeof(lua_Number)
:在C语言中很容易,但只有当主机和目标遵循相同的FP标准时;升级时的精度损失(罕见情况);降级到时警告非整数数字 int32
。
从我在邮件列表上看到的关于这个问题的所有讨论中,我看到了两种可能的可行方法,假设您不愿意考虑只发送未编译的 Lua 脚本。
第一个是在加载编译脚本时修复字节顺序。事实证明,这比您预期的要容易,因为可以通过替换读取脚本文件的低级函数来完成,而无需重新编译内核本身。事实上,它甚至可以在纯 Lua 中完成,方法是向lua_load()提供你自己的块读取器函数。只要您的平台上唯一的兼容性问题是字节顺序,这应该可以工作。
第二个是修补核心本身,以便在所有平台上为编译的脚本使用通用表示。Luiz Henrique de Figueiredo尽可能地描述了这一点:
....我相信字节顺序或交叉编译的最佳途径是第三方转储/取消转储对。文件 ldump.c 和 lundump.c 是完全可替换的;它们导出一个单一的、定义明确的入口点。预编译块的格式一点也不神圣;您可以使用任何格式,只要 ldump.c 和 lundump.c 同意。(例如,Rici Lake 正在考虑为预编译块编写文本格式。) ....
就个人而言,我建议认真考虑不要预编译脚本,从而完全避免平台可移植性问题。
编辑:感谢 lhf 的评论,我更新了我对字节码标头的描述。我还没有阅读 Lua 源代码的这一部分,我可能应该在对标题中存在或不存在哪些信息如此自信之前检查它。
这是一个片段,lundump.c
它形成了与正在运行的平台匹配的标头副本,用于与正在加载的字节码进行比较。它只是与memcmp()
文件头的完全匹配进行比较,因此任何不匹配都会导致库存加载器(luaU_undump()
)拒绝文件。
/*
* make header
*/
void luaU_header (char* h)
{
int x=1;
memcpy(h,LUA_SIGNATURE,sizeof(LUA_SIGNATURE)-1);
h+=sizeof(LUA_SIGNATURE)-1;
*h++=(char)LUAC_VERSION;
*h++=(char)LUAC_FORMAT;
*h++=(char)*(char*)&x; /* endianness */
*h++=(char)sizeof(int);
*h++=(char)sizeof(size_t);
*h++=(char)sizeof(Instruction);
*h++=(char)sizeof(lua_Number);
*h++=(char)(((lua_Number)0.5)==0); /* is lua_Number integral? */
}
可以看出,标头有 12 个字节长,包含一个签名(4 个字节,“ <esc>Lua
”)、版本和格式代码、字节顺序的标志字节、类型的大小int
、size_t
、Instruction
和lua_Number
,以及指示是否lua_Number
为积分型。
这允许捕获大多数平台差异,但不会尝试捕获平台可能不同的所有方式。
我仍然坚持上面提出的建议:首先,发布可编译的源;或者第二,自定义ldump.c
并lundump.c
存储和加载通用格式,另外注意任何自定义格式都应重新定义标头的 LUAC_FORMAT 字节,以免与库存字节码格式混淆。