Serialization 可以对包含C函数引用的lua字节码字符串调用loadstring吗?
我们使用的是Love2d Lua游戏引擎,它向Lua公开了一个图形api。我们正在尝试序列化一个巨大的哈希表,该哈希表包含游戏世界的所有保存游戏数据。这个散列包含一些函数,其中一些函数调用love2dc函数 为了序列化散列中的函数,我们使用string.dump,并使用loadstring将它们加载回。这对于纯Lua函数很有效,但当我们尝试序列化然后加载回调用包装C函数(如Love2d api中的函数)的函数时,loadstring返回nil 考虑以下通过Love2d图形引擎将“hello,world”绘制到屏幕的简单程序:Serialization 可以对包含C函数引用的lua字节码字符串调用loadstring吗?,serialization,lua,binary-data,love2d,Serialization,Lua,Binary Data,Love2d,我们使用的是Love2d Lua游戏引擎,它向Lua公开了一个图形api。我们正在尝试序列化一个巨大的哈希表,该哈希表包含游戏世界的所有保存游戏数据。这个散列包含一些函数,其中一些函数调用love2dc函数 为了序列化散列中的函数,我们使用string.dump,并使用loadstring将它们加载回。这对于纯Lua函数很有效,但当我们尝试序列化然后加载回调用包装C函数(如Love2d api中的函数)的函数时,loadstring返回nil 考虑以下通过Love2d图形引擎将“hello,wo
function love.load()
draw = function()
love.graphics.print('hello, world', 10, 10)
end
end
function love.draw()
draw()
end
我们希望能够做到这一点:
function love.load()
draw_before_serialize = function()
love.graphics.print('hello, world', 10, 10)
end
out = io.open("serialized.lua", "wb")
out:write('draw = load([[' .. string.dump(draw_before_serialize) .. ']])')
out:close()
require "serialized"
end
function love.draw()
draw()
end
执行此操作将写入磁盘上的Lua文件,该文件包含未编译的Lua和Lua字节码的混合,如下所示:
draw = load([[^[LJ^A^@
@main.lua2^@^@^B^@^B^@^D^E^B^B4^@^@^@%^A^A^@>^@^B^AG^@^A^@^Qhello, world
print^A^A^A^B^@^@]])
此方法适用于不调用C模块的Lua函数。我们认为这就是问题所在,因为这个例子确实有效:
function love.load()
draw_before_serialize = function()
print('hello, world')
end
out = io.open("serialized.lua", "wb")
out:write('draw = load([[' .. string.dump(draw_before_serialize) .. ']])')
out:close()
require "serialized"
end
function love.draw()
draw()
end
function love.load()
draw_before_serialize = function()
love.graphics.print('hello, world', 10, 10)
end
draw = load(string.dump(draw_before_serialize))
end
function love.draw()
draw()
end
它不调用Love2d图形方法,而是向控制台进行打印
经过更多测试后,我们困惑地发现此示例确实有效:
function love.load()
draw_before_serialize = function()
print('hello, world')
end
out = io.open("serialized.lua", "wb")
out:write('draw = load([[' .. string.dump(draw_before_serialize) .. ']])')
out:close()
require "serialized"
end
function love.draw()
draw()
end
function love.load()
draw_before_serialize = function()
love.graphics.print('hello, world', 10, 10)
end
draw = load(string.dump(draw_before_serialize))
end
function love.draw()
draw()
end
在这里,我们实际上并没有将函数写入磁盘,而是转储它,然后立即将其加载回磁盘。我们认为可能是罪魁祸首没有使用二进制写入模式标志集(“wb”
)写入数据,但由于我们使用的是Linux,因此该标志没有任何效果
有什么想法吗?我想问题在于字符串的格式。Nicol Bolas关于字节码转储的[[]]引号可能是对的,但这指出了一个更大的问题;字节码实际上可以是任何东西,但您将其视为可以写入文本文件并从文本文件中读取的普通字符串。上一个演示演示了这个问题,您在加载转储字符串时从未将其写入文件 我认为,为包含函数的表实现序列化程序有点像你想要的,但我也认为它已经坏了(好吧,反正我无法让它正常工作…)。无论如何,这是正确的。您需要格式化字节码,然后将其写入文件 我相信有更好的方法,但这是可行的:
1. binary = string.dump(some_function)
2. formatted_binary = ""
3. for i = 1, string.len(binary) do
4. dec, _ = ("\\%3d"):format(binary:sub(i, i):byte()):gsub(' ', '0')
5. formatted_binary = formatted_binary .. dec
6. end
这将循环遍历字节码中的每个字符,并将其格式化为转义字节(每个字节都是一个字符串,包含类似“\097”的代码,插值后将转义为“a”)
这个样本的第4行有点密集,所以我将把它分解。首先,
binary:sub(i, i)
从字符串中拉出第i个字符。然后
binary:sub(i, i):byte()
返回第i个字符的ascii整数表示形式。然后我们用
("\\%3d"):format(binary:sub(i, i):byte())
这会给我们一个类似“\97”的字符串,例如,如果字符是“a”。但这无法正确转义,因为我们需要“\097”,所以我们使用gsub将“”替换为“0”。gsub返回结果字符串和执行的替换次数,因此我们只取第一个返回值并将其放入“dec”。我不确定为什么默认情况下“%3d”格式不将空格替换为“0”。。。哦,好吧
然后,为了执行格式化的二进制字符串,我们需要对其进行转义,并将结果传递给“load”。Lua中古怪的[[]]引号不会像“。。。事实上,我不确定他们有没有逃跑。因此,为了使一个可执行的Lua字符串返回一个函数,该函数将执行“some_function”中的任何操作,我们执行以下操作:
executable_string = 'load("' .. formatted_binary .. '")'
好的-把所有这些放在一起,我认为我们可以让您的测试用例像这样工作:
1 function love.load()
2 draw_before_serialize = function()
3 love.graphics.print('hello, world', 10, 10)
4 end
5
6 binary = string.dump(draw_before_serialize)
7 formatted_binary = ""
8 for i = 1, string.len(binary) do
9 dec, _ = ("\\%3d"):format(binary:sub(i, i):byte()):gsub(' ', '0')
10 formatted_binary = formatted_binary .. dec
11 end
12
13 out = io.open("serialized.lua", "wb")
14 out:write('draw = load("' .. formatted_binary .. '")')
15 out:close()
16
17 require "serialized"
18 end
19 function love.draw()
20 draw()
21 end
当我带着爱运行这个程序时,我会看到一个OpenGL屏幕的角落里印着“hello world”。结果文件“serialized.lua”包含以下内容:
draw = load("\027\076\074\001\000\009\064\109\097\105\110\046\108\117\097\084\000\000\004\000\004\000\008\009\002\002\052\000\000\000\055\000\001\000\055\000\002\000\037\001\003\000\039\002\010\000\039\003\010\000\062\000\004\001\071\000\001\000\017\104\101\108\108\111\044\032\119\111\114\108\100\010\112\114\105\110\116\013\103\114\097\112\104\105\099\115\009\108\111\118\101\001\001\001\001\001\001\001\002\000\000")
“打印到控制台”打印什么?此外,您是否确定上述代码使用的全局环境与
require
使用的全局环境相同?[由于您依赖于在全局环境中定义的draw
”,因此应该建议您:[['..string.dump(在序列化之前绘制)]
不一定会起作用。您获得的转储可以包含任何内容,包括]
字符。这将提前终止字符串,从而破坏一切。@Nicolas我曾经看到一个简单而聪明的解决方案,它只是检查字符串的](=*)]
,然后用一个=
框定转储,比匹配中找到的=
的最大数量多。这样做难吗?我有一个类似于您发布的这个字符串的序列化字符串,我想将其更改为基于人工的lanquage,以便对该字符串生成的脚本进行一些小的更改。