有一种方法可以做到,但让它发挥作用会有一些限制。
基本思想是在 Lua 函数和require
使用模块的脚本与您试图在幕后更改的实际 C 函数之间创建一个间接层。这最好在您的模块本身中完成。也就是说,不是让 Lua 代码替换函数等,而是让 C 函数自己替换。
这样,你就可以控制了。
这个想法很简单。你有两个模块:一个是 Lua 注册函数的存根模块,以及带有函数实现的实际模块。只有后一个模块发生了变化。
你的存根模块是一个 Lua 模块,它有两个向 Lua 注册的函数。第一个是存根函数,它会多次注册。它被注册为具有单个上值的闭包。
该上值是一个 C 函数。这个存根函数所做的唯一一件事就是从 Lua 状态中拉出一个 upvalue,并使用给定的相同参数调用它,并准确返回被调用函数返回的值。它需要一些堆栈技巧才能正确传递,但我假设您知道如何处理它。此外,这个函数应该检查 upvalue 的值;如果是nil
,则什么也不做,什么也不返回(这可以防止错误)。
第二个功能是我们稍后会谈到的。
您的存根模块初始化例程将加载您的实际模块,这实际上只是一个 DLL。此 DLL 需要有一个函数,该函数将提供实际模块导出的函数列表。因此,它可以查询要转发的各种 Lua 函数。
对于从实际模块中导出的每个函数,它会向 Lua 注册存根函数,并为其命名。存根的上值设置为导出的函数,加载时没有上值。
显然,DLL 的句柄需要保留,可能作为注册表项中的用户数据或其他内容。
存根模块导出的第二个函数是重新加载实际模块的函数。为此,您需要加载新的 DLL(而不是先卸载旧的)。为了使这个过程变得健壮(尽管你保证函数列表是相同的。我不喜欢这样假设),你需要遍历你的模块表。
对于不在新 DLL 中的每个已注册函数,将其上值设置为nil
(并将它们从模块表中删除)。对于新 DLL 中的每个注册函数,将 upvalue 更改为新函数。如果新的 DLL 有未注册的函数,现在通过使用新的上值注册存根函数将它们粘贴到表中。
完成所有这些之后,您就可以卸载旧的 DLL。
那里; 完毕。
注意:为了使其工作,您的模块功能必须表现良好。他们不能做诸如注册其他 C 函数之类的事情。他们不能使用 Lua 5.2 的机制来产生和恢复跨 C 边界的协程。等等。否则,您将面临调用已卸载 DLL 的风险。