Serialization 可以对包含C函数引用的lua字节码字符串调用loadstring吗?

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

我们使用的是Love2d Lua游戏引擎,它向Lua公开了一个图形api。我们正在尝试序列化一个巨大的哈希表,该哈希表包含游戏世界的所有保存游戏数据。这个散列包含一些函数,其中一些函数调用love2dc函数

为了序列化散列中的函数,我们使用string.dump,并使用loadstring将它们加载回。这对于纯Lua函数很有效,但当我们尝试序列化然后加载回调用包装C函数(如Love2d api中的函数)的函数时,loadstring返回nil

考虑以下通过Love2d图形引擎将“hello,world”绘制到屏幕的简单程序:

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,以便对该字符串生成的脚本进行一些小的更改。