我一直试图创建一个函数,一旦调用某个函数,该函数便会运行事件/函数。我希望用法如下:
local a = nil
a = hookfunction(print, function() -- When print() is called...
warn("print(...) called.")
return nil
end)
我知道有一个debug.sethook()函数,但我不太了解如何使用它。
有两种方法,但是每种方法都有一些陷阱需要处理。
一种方法是使用monkeypatching。这会将打印功能更改为将执行所需功能的其他功能,然后调用原始功能。
类似这样的东西:
local origprint = print;
(_G or _ENV).print = function(...)
io.write("print is called\n")
return origprint(...)
end
print("foo", 1)
此选项的主要陷阱是,如果某些组件在进行猴子修补之前保存了print
的值,则其后续使用不会受到补丁的影响。
另一个选择是使用调试钩子:
debug.sethook(function()
local info = debug.getinfo(2)
if info.name then
io.write(info.name, " is called\n")
end
end, "c")
function foo()
print("do something")
end
foo()
这应该打印:
foo is called
print is called
do something
此方法的主要缺陷是,它不保证将按您的期望输出函数名称,因为values可能与它们的不同名称/容器关联。
例如,将打印a is called
:
local a = print
a("do something")
这将打印? is called
:
local a = {print}
a[1]("do something")
最简单的方法是包装原始函数:
do
local _print = print
function print(...)
_print("print(...) called.")
_print(...)
end
end
print("hello world")
输出:
打印(...)被调用。
你好世界
[我们听到的是将原始print
存储在局部变量_print
中,这是我们在存储原始字符后创建的新全局打印函数的上值。
这个问题缺少一个非常重要的细节:为什么?
如果要调试时使用此选项,则debug.sethook
可能是更合适的选择。正如Paul Kulchenko指出的那样,debug.getinfo
可能返回错误的函数名称,但它还会返回函数对象本身,您可以使用该对象在表中查找规范名称。这也可以用作监视哪些功能的过滤器:
local watch = {}
debug.sethook(function()
local info = debug.getinfo(2)
if watch[info.func] then
print(watch[info.func], "was called")
-- This *should* not cause a recursion, as
-- the hook *should* be disabled inside the hook
-- callback.
end
end, "c")
-- Somewhere else in your code
watch[print] = "print"
print("foobar")
但是,如果打算将其用于调试以外的其他用途,则应尽可能避免使用debug.sethook
,因为它可能会与依赖调试钩子的其他库(例如luacov
举例)混为一谈;在这种情况下,最好替换函数:
do local p = print
function print(...)
p("Print was called!")
return p(...)
end
end
但请记住,您不应更改ahy函数的行为,否则可能会破坏其他地方的代码。这还包括运行时;如果您要执行一个非常高效的功能而做很多繁重的工作,则可能会以意想不到的方式完全破坏性能。
还请记住,某些库会本地化它们希望使用的全局函数。尽管这通常是非常糟糕的做法,但似乎有一个神话,它神奇地使Lua代码更快(这是有这个神话的原因,但在大多数情况下是不正确的);这意味着您的替换必须在before
加载任何这些库之前进行。出于同样的原因,顺便说一句,除非您知道需要持久地引用原始函数,否则通常不应该对全局变量进行本地化,就像上面示例中的p
变量一样。