如何Hook一个函数

如题所述

从LUA的文档来看: The statement function f () ... end translates to f = function () ... end 那意味着任意一个函数能被其它的任意一个函数通过一个简单的分配所替代。把这个记在心里,它将变得很容易去“Hook”,或者是添加你自己的函数到一个预先定义好的函数中。 # Hook 有钩住,钩子的意思。 警告:这个部分包含过时的信息。比如说我们想显示那些我们级别高很多的玩家和怪的级别来替代骷髅。隐藏级别的函数是TargetFrame_CheckLevel(),因此我们需要hook那个函数来让它不要隐藏级别。 让我们假设我们的插件被命名为"MyAddOn",并且有一个OnLoad处理器被它的XML 事件所调用。在Lua的文档中我们应该有: local MyAddOn_Orig_TargetFrame_CheckLevel; function MyAddOn_OnLoad() MyAddOn_Orig_TargetFrame_CheckLevel = TargetFrame_CheckLevel; -- 存储原始的函数 TargetFrame_CheckLevel = MyAddOn_TargetFrame_CheckLevel; -- Hook进我们的 end 因此上面所做的就是它存储原始"TargetFrame_CheckLevel" 的参量到"MyAddOn_Orig_TargetFrame_CheckLevel"。接着它用我们的函数替换了原始的,因此现在任何人调用TargetFrame_CheckLevel()时实际上得到的是被MyAddOn_TargetFrame_CheckLevel()所替代的。 下一步就是创建我们的MyAddOn_TargetFrame_CheckLevel()。让我们假设我们想要它显示目标的级别。 function MyAddOn_TargetFrame_CheckLevel() local retval = MyAddOn_Orig_TargetFrame_CheckLevel(); -- 调用原始的函数 TargetLevelText:Show(); TargetHighLevelTexture:Hide(); return retval; end 因此在这个函数中,我们首先调用老的函数让它做它必需做的。接着,显示级别并且隐藏骷髅。非常简单不是吗? 这只是一个怎么样hook的例子,它不是真的为你显示级别。 --影子而矣 很容易就能Hook一个函数吗? 如你有Sea库,那么你能hook一个函数用Sea.util.hook。 Sea.util.hook("OldFunctionName", "NewFunctionName", "before|after|hide|replace"); 如果你指明代替,那么老的函数将只在新的函数返回真时被调用。 如果你使用Sea.util.hook,那么你也能在此过后用Sea.util.unhook移去hook。 Sea.util.unhook("OldFunctionName, "NewFunctionName"); 使用Sea.util.hook 要小心参数的传递,优先权,链接并且你能确保在此过后你能清掉这些。 有选择的使用Sea 如果你不想你的插件依赖Sea,但是你又想当它可用时获益,你可以检测它的存在: local MyAddOn_Old_FunctionToHook = function() end; if Sea then Sea.util.hook("FunctionToHook", "ReplacementFunction", "after"); else MyAddOn_Old_FunctionToHook = FunctionToHook; FunctionToHook = ReplacementFunction; end function ReplacementFunction() MyAddOn_Old_FunctionToHook(); ... end 这些编码允许你在你的TOC中列出Sea做为一个OptionalDep。这能帮你防止将来用户安装Sea时其它的插件完全替换老的函数所引起的冲突。 其它Hooking库 Stubby 用Stubby你能hook函数如Stubby.HookFunctionStubby.RegisterFunctionHook("FunctionToHook", position, replacementFunction [, hook1, hook2, .., hookN]) “FunctionToHook”是一个字符串用做你想hook进的hook名字。即:"GameTooltip.SetOwner" (#游戏工具提示.设置所有者) “position”是一个负数或正数定义着插件的真实调用次序。更小或负数,它被你的hookFunction调用的越早,大的数字则反之。实际上初始的(hooked)函数被调用是在position 0,因此如果你的插件被hook在一个负的position,你返回的值表(看下面)将包括在Stubby调用链中以前的函数的值。 你传递(用参量)你的函数一个你想做为替代的函数被调用。这个函数将被下面的参数调用:hookFunction(hookParams, returnValue, hook1, hook2 .. hookN) “hookParams”是一个包括附加参数表被传递到RegisterFunctionHook function (the "hook1, hook2, .., hookN" params) “returnValue”是一个在你进入Stubby调用链或如果没有为空值时被调用函数返回值的数组。 “hook1..hookN”是在初始次序被hook函数的初始参数。 有特殊处理的情况时你能有选择的返回下列字符串到Stubby。 "abort" 将中止调用链(将不会调用任何在你自己所有之后的其它函数计划表)。 "killorig"将不再调用初始的函数,但是将继续调用链。 "setparams"将用你所指定一个列表中的你函数的第二返回值来替换函数的调用参数。 "setreturn" 将用你所指定一个列表中的你函数的第二返回值来改变在Stubby调用链中下一个函数的返回值。 Ace Ace2 到什么地方去调用老的函数 这是很有技巧的,并且真的依赖于你现在正在写的。缺省的情况下你应该有可能首先调用到老的函数,接着做你任何你需要做的。然而,有的时候,你想最后调用老的函数,有条件地调用它,或者根本就不调用它。 注意:不要调用有热门效果的老函数假设另一个插件在你之前hook它。接着什么是你将做的呢,就是不要让插件看见函数调用。这个是hook库真的帮的上忙的地方。 函数带参数 如果你的一个函数带参数,确信你的新函数也带同样的参数,并且它能传递它。 因此举个例子假设我们想hook ActionButton_GetPagedID( id )我们的函数定义应该象: function New_ActionButton_GetPagedID( id ) Old_ActionButton_GetPagedID( id ); ... end 函数使用全局变量 一些函数使用全局变量,并且能在执行过程中改变这些全局变量。例如,GameTooltip_OnEvent()就是这样的函数,在其中它使用全局事件变量,并且在执行过程中改变它。在这种情况中,重要的是在调用老函数前做一个变量的拷贝。好象: function New_GameTooltip_OnEvent() local myEvent =event; Old_GameTooltip_OnEvent(); if ( myEvent = ... ) then ... end end Hook 链 警告:这个部分包含过时的信息。这有点技巧,但是起作用。我们说这有两个单独的的插件,一个附加玩家工具提示到鼠标,以及另一个被用来替换?的玩家级别。一个插件被称做“AnchorToolTip--锚定工具提示”,另一个被称为“ShowLevel--显示级别”。 如果的AnchorToolTip编码是: local Pre_AnchorToolTip_GameTooltip_OnEvent; function AnchorToolTip_OnLoad() Pre_AnchorToolTip_GameTooltip_OnEvent = GameTooltip_OnEvent; GameTooltip_OnEvent = AnchorToolTip_GameTooltip_OnEvent; end function AnchorToolTip_GameTooltip_OnEvent() Pre_AnchorToolTip_GameTooltip_OnEvent(); ... end 以及ShowLevel的编码是: local Pre_ShowLevel_GameTooltip_OnEvent; function ShowLevel_OnLoad() Pre_ShowLevel_GameTooltip_OnEvent = GameTooltip_OnEvent; GameTooltip_OnEvent = ShowLevel_GameTooltip_OnEvent; end function ShowLevel_GameTooltip_OnEvent() Pre_ShowLevel_GameTooltip_OnEvent(); ... end 这真的将起作用。然而,我们说你不仅想Anchor(锚定)tooltip到鼠标当shift 键被按下时。一个小的简单改变好象: function AnchorToolTip_GameTooltip_OnEvent() if ( isShiftKeyDown() ) then Pre_AnchorToolTip_GameTooltip_OnEvent(); ... end end 有可能打断hook链取决于哪个插件先加裁。因而,千万千万小心当你决定什么时候在什么地方调用老函数。
温馨提示:答案为网友推荐,仅供参考
第1个回答  推荐于2016-10-14
This HOWTO deals with pre-hooks. For details on post-hooks, see 如何安全的Post-Hook一个函数.
  For more information on the actual hooking of functions, see 如何Hook一个函数.
  你通常这样使用么
  Meet Joe Average Hook:
  local orig_foo = foo
  function foo(a1, a2)
  -- some code that looks at a1
  return orig_foo(a1, a2)
  end

  问题在于这个方法只能处理固定数目的参数, 如果方法的API改变了, 将导致无法使用. 幸运的是我们有办法使他继续工作.
  Blizzard's APIs do change from time to time!
  使用安全的方式
  local orig_foo = foo
  function foo(a1, ...)
  --do something with a1
  return orig_foo(a1, ...)
  end

  这样确保了所有的参数会传递到原始方法中, 即便你不知道具体有多少个参数. 同样确保了所有返回值都能正确返回. 另一个好处是, 我们使用了局部变量来保存原始方法并做了一个适当的尾调用可以带来更好的性能, 从而为我们的hook做了最小化的付出.
  会带来巨大的性能影响么?
  在WoW-2.0以前的设计中, 使用unpack(), 在每次hook被调用时创建一个垃圾回收表. 在新的设计中改进了, 使用'...'变量, 去掉了垃圾回收这部分源码. 在Lua5.1中, 在每次hook调用时包括传参和返回值都不会浪费表的内存.