Login

如何Hook一个函数

译者:rick777

在LUA中, 一个函数是一个简单的变量。从LUA的文档来看:

   1 The statement
   2  function f () ... end
   3 translates to
   4  f = function () ... end

那意味着任意一个函数能被其它的任意一个函数通过一个简单的分配所替代。把这个记在心里,它将变得很容易去“Hook”,或者是添加你自己的函数到一个预先定义好的函数中。 # Hook 有钩住,钩子的意思。

如何Hook一个函数

<<ImageLink(http://images.wikia.com/wowwiki/images/4/42/Icon-warning-48x48.png,width=24)>>警告:这个部分包含过时的信息。比如说我们想显示那些我们级别高很多的玩家和怪的级别来替代骷髅。隐藏级别的函数是TargetFrame_CheckLevel(),因此我们需要hook那个函数来让它不要隐藏级别。

让我们假设我们的插件被命名为"MyAddOn",并且有一个OnLoad处理器被它的XML <OnLoad>事件所调用。在Lua的文档中我们应该有:

   1 local MyAddOn_Orig_TargetFrame_CheckLevel;
   2  
   3 function MyAddOn_OnLoad()
   4   MyAddOn_Orig_TargetFrame_CheckLevel = TargetFrame_CheckLevel;  -- 存储原始的函数
   5   TargetFrame_CheckLevel = MyAddOn_TargetFrame_CheckLevel;       -- Hook进我们的
   6 end

因此上面所做的就是它存储原始"TargetFrame_CheckLevel" 的参量到"MyAddOn_Orig_TargetFrame_CheckLevel"。接着它用我们的函数替换了原始的,因此现在任何人调用TargetFrame_CheckLevel()时实际上得到的是被MyAddOn_TargetFrame_CheckLevel()所替代的。

下一步就是创建我们的MyAddOn_TargetFrame_CheckLevel()。让我们假设我们想要它显示目标的级别。

   1 function MyAddOn_TargetFrame_CheckLevel()
   2   local retval = MyAddOn_Orig_TargetFrame_CheckLevel();  -- 调用原始的函数
   3   TargetLevelText:Show();
   4   TargetHighLevelTexture:Hide();
   5   return retval;
   6 end

因此在这个函数中,我们首先调用老的函数让它做它必需做的。接着,显示级别并且隐藏骷髅。非常简单不是吗?

这只是一个怎么样hook的例子,它不是真的为你显示级别。 --影子而矣

很容易就能Hook一个函数吗?

如你有Sea库,那么你能hook一个函数用Sea.util.hook

   1 Sea.util.hook("OldFunctionName", "NewFunctionName", "before|after|hide|replace"); 

如果你指明代替,那么老的函数将只在新的函数返回真时被调用。

如果你使用Sea.util.hook,那么你也能在此过后用Sea.util.unhook移去hook。

   1 Sea.util.unhook("OldFunctionName, "NewFunctionName");

使用Sea.util.hook 要小心参数的传递,优先权,链接并且你能确保在此过后你能清掉这些。

有选择的使用Sea

如果你不想你的插件依赖Sea,但是你又想当它可用时获益,你可以检测它的存在:

   1 local MyAddOn_Old_FunctionToHook = function() end;
   2  if Sea then
   3    Sea.util.hook("FunctionToHook", "ReplacementFunction", "after");
   4  else
   5    MyAddOn_Old_FunctionToHook = FunctionToHook;
   6    FunctionToHook = ReplacementFunction;
   7  end
   8  
   9  function ReplacementFunction()
  10    MyAddOn_Old_FunctionToHook();
  11    ...
  12  end

这些编码允许你在你的TOC中列出Sea做为一个OptionalDep。这能帮你防止将来用户安装Sea时其它的插件完全替换老的函数所引起的冲突。

其它Hooking库

Stubby

用Stubby你能hook函数如Stubby.HookFunction

   1  Stubby.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 )我们的函数定义应该象:

   1 function New_ActionButton_GetPagedID( id )
   2     Old_ActionButton_GetPagedID( id );
   3     ...
   4 end

函数使用全局变量

一些函数使用全局变量,并且能在执行过程中改变这些全局变量。例如,GameTooltip_OnEvent()就是这样的函数,在其中它使用全局事件变量,并且在执行过程中改变它。在这种情况中,重要的是在调用老函数前做一个变量的拷贝。好象:

   1 function New_GameTooltip_OnEvent()
   2     local myEvent = event;
   3     Old_GameTooltip_OnEvent();
   4     if ( myEvent = ... ) then
   5         ...
   6     end
   7 end

Hook 链

<<ImageLink("http://images.wikia.com/wowwiki/images/4/42/Icon-warning-48x48.png",width=24)>>警告:这个部分包含过时的信息。这有点技巧,但是起作用。我们说这有两个单独的的插件,一个附加玩家工具提示到鼠标,以及另一个被用来替换??的玩家级别。一个插件被称做“AnchorToolTip--锚定工具提示”,另一个被称为“ShowLevel--显示级别”。

如果的AnchorToolTip编码是:

   1 local Pre_AnchorToolTip_GameTooltip_OnEvent;
   2 function AnchorToolTip_OnLoad()
   3     Pre_AnchorToolTip_GameTooltip_OnEvent = GameTooltip_OnEvent;
   4     GameTooltip_OnEvent = AnchorToolTip_GameTooltip_OnEvent;
   5 end

   1 function AnchorToolTip_GameTooltip_OnEvent()
   2     Pre_AnchorToolTip_GameTooltip_OnEvent();
   3     ...
   4 end

以及ShowLevel的编码是:

   1 local Pre_ShowLevel_GameTooltip_OnEvent;
   2 function ShowLevel_OnLoad()
   3     Pre_ShowLevel_GameTooltip_OnEvent = GameTooltip_OnEvent;
   4     GameTooltip_OnEvent = ShowLevel_GameTooltip_OnEvent;
   5 end

   1 function ShowLevel_GameTooltip_OnEvent()
   2     Pre_ShowLevel_GameTooltip_OnEvent();
   3     ...
   4 end

这真的将起作用。然而,我们说你不仅想Anchor(锚定)tooltip到鼠标当shift 键被按下时。一个小的简单改变好象:

   1 function AnchorToolTip_GameTooltip_OnEvent()
   2     if ( isShiftKeyDown() ) then
   3         Pre_AnchorToolTip_GameTooltip_OnEvent();
   4         ...
   5     end
   6 end

有可能打断hook链取决于哪个插件先加裁。因而,千万千万小心当你决定什么时候在什么地方调用老函数。你不仅在调用Blizzard的编码,你可能也调用了整个hook函数。

注意

  • 如果你使用一个局部变量去存储老函数,它的名字关系不大。但是通常在大的插件中,你可能需要从你其它几个插件中调用它,然而局部变量不工作。使用一个句法来唯一的标识你以前函数的参量,如:

{{{Pre_addon_functionname or addon_Orig_functionname. }}}

  • 一个变通办法是去存储老函数为一个全局变量的一个成员,a la MyAddOn.FunctionToBeHooked = FunctionToBeHooked ; FunctionToBeHooked = function() ...

  • 警告!Blizzard已经把一些UI移到需求加载中去了。那些函数不能被hook直到它们被加裁。

    警告!Blizzard确实加了新的参数并且返回值到他们的函数中。 See 如何安全的Hook一个函数. !

相关文档


HOWTOs类, rick777

如何Hook一个函数 (last edited 2008-06-22 14:19:57 by 月色狼影)