WoW:Hooking functions: Difference between revisions

→‎Hooking a function: SetItemRef -> ChatFrame_OnHyperlinkShow
No edit summary
(→‎Hooking a function: SetItemRef -> ChatFrame_OnHyperlinkShow)
Line 20: Line 20:


== Hooking a function ==
== Hooking a function ==
To illustrate the concept, let's create a pre-hook. Pre-hooks are commonly used to override user interface functions that perform an (unprotected) action, potentially adding behavior to existing UI. In this example, hook [[API_SetItemRef|SetItemRef]] (a function called when the user clicks on a chat link) to display a faux tooltip for the {{item|Tinfoil Hat}}, an item currently not in the game.
To illustrate the concept, let's create a pre-hook. Pre-hooks are commonly used to override user interface functions that perform an (unprotected) action, potentially adding behavior to existing UI. In this example, hook {{api|ChatFrame_OnHyperlinkShow}} (a function called when the user clicks on a chat link) to display a faux tooltip for the {{item|Tinfoil Hat}}, an item currently not in the game.
<div style="max-height: 200px; overflow: auto; border: 2px solid black; padding: 0.5em; margin-left: 0.5em">
<div style="max-height: 200px; overflow: auto; border: 2px solid black; padding: 0.5em; margin-left: 0.5em">
<pre style="margin: 0; border: 0"><nowiki>local origSetItemRef = SetItemRef; -- (1)
<pre style="margin: 0; border: 0"><nowiki>local origChatFrame_OnHyperlinkShow = ChatFrame_OnHyperlinkShow; -- (1)
SetItemRef = function(...) -- (2)
ChatFrame_OnHyperlinkShow = function(...) -- (2)
  local link, text, button = ...; -- (3)
  local chatFrame, link, text, button = ...; -- (3)
  if type(text) == "string" and text:match("%[Tinfoil Hat%]") and not IsModifiedClick() then --(4)
  if type(text) == "string" and text:match("%[Tinfoil Hat%]") and not IsModifiedClick() then --(4)
   return ShowTinfoilHat(); -- (5)
   return ShowTinfoilHat(); -- (5)
  end
  end
  return origSetItemRef(...); -- (6)
  return origChatFrame_OnHyperlinkShow(...); -- (6)
end
end
print("Click me: \124cff0070dd\124Hitem:8191:0:0:0:0:0:0:0:0:1\124h[Tinfoil Hat]\124h\124r"); -- (7)
print("Click me: \124cff0070dd\124Hitem:8191:0:0:0:0:0:0:0:0:1\124h[Tinfoil Hat]\124h\124r"); -- (7)
Line 55: Line 55:
[[File:Hooking functions demo.png|thumb|right|The resulting tooltip.]]
[[File:Hooking functions demo.png|thumb|right|The resulting tooltip.]]
Explained in detail, the preceding code does:
Explained in detail, the preceding code does:
# Stores the old SetItemRef value (original function) into a local variable
# Stores the old ChatFrame_OnHyperlinkShow value (original function) into a local variable
# Changes the value of the SetItemRef variable to a new function (the pre-hook). Note the vararg expression (...) in the function signature.
# Changes the value of the ChatFrame_OnHyperlinkShow variable to a new function (the pre-hook). Note the vararg expression (...) in the function signature.
# Extract known arguments from the vararg expression for local use.
# Extract known arguments from the vararg expression for local use.
# Check whether link text is a string, whether it matches [Tinfoil Hat], and whether we should display a tooltip
# Check whether link text is a string, whether it matches [Tinfoil Hat], and whether we should display a tooltip
Line 103: Line 103:
; Insecure hooking : If you want to alter behavior significantly, it may be easier to call widget:[[API_Frame_GetScript|GetScript]] to get the original handler, and widget:[[API_Frame_SetScript|SetScript]] to set a hooked verson.
; Insecure hooking : If you want to alter behavior significantly, it may be easier to call widget:[[API_Frame_GetScript|GetScript]] to get the original handler, and widget:[[API_Frame_SetScript|SetScript]] to set a hooked verson.
; Secure hooking: widget:[[API_Frame_HookScript|HookScript]] allows you to set up a secure post-hook that will be called after the original handler, with the same arguments as the original handler.
; Secure hooking: widget:[[API_Frame_HookScript|HookScript]] allows you to set up a secure post-hook that will be called after the original handler, with the same arguments as the original handler.
== Hooking object methods ==
Functions called using the object calling syntax, <code>object:method(...)</code>, can also be hooked; one simply has to remember that the construct is merely syntactic sugar for <code>object.method(object, ...);</code>. To hook a function that was originally declared as:
function AnAddon.Module:FuncName(arg1)
  -- Things happen here...
end
We store the original function and replace it with a hook:
local original_AnAddon_Module_FuncName = AnAddon.Module.FuncName;
function AnAddon.Module:FuncName(...)
  local arg1 = ...; -- Use the vararg expression in case the function signature changes
  if type(arg1) == "number" and arg1 > 0 then
    -- Call the original function; because of object calling syntax,
    --  pass self as the first argument.
    return original_AnAddon_Module_FuncName(self, ...);
  end
end
The example hook above will only allow the original function to be called if the first argument is a strictly positive number.


== Removing existing hooks ==
== Removing existing hooks ==
Removing hooks is generally problematic: there's no way to remove a hooksecurefunc-based post-hook, and insecure hooks may only be removed in some situations. The difficulty arises from the fact that multiple addons may want to hook the same function -- and if any addon but the top-most in the hooking chain wants to unhook, there's no way to tell the hook on top of your function to call the function below. It is therefore recommended to simply make your existing hook pass arguments and return values directly through if you don't want to deal with the data. For example:
Removing hooks is generally problematic: there's no way to remove a hooksecurefunc-based post-hook, and insecure hooks may only be removed in some situations. The difficulty arises from the fact that multiple addons may want to hook the same function -- and if any addon but the top-most in the hooking chain wants to unhook, there's no way to tell the hook on top of your function to call the function below. It is therefore recommended to simply make your existing hook pass arguments and return values directly through if you don't want to deal with the data. For example:
  local oldFunction = someGlobalFunc;
  local oldFunction, noLongerRelevant = someGlobalFunc, false;
  function someGlobalFunc(...)
  function someGlobalFunc(...)
   if noLongerRelevant then
   if noLongerRelevant then
Line 114: Line 131:
   end
   end
  end
  end
Libraries may contain functions that allow you to "unhook" your method -- those operate primarily by moving the ignore switch further up the execution tree: if you want to remove your hook, it won't get called, but the library's hook function stays in place.
Libraries may contain functions that allow you to "unhook" your method -- those operate primarily by moving the ignore switch further up the execution tree: if you remove your hook, it won't get called, but the library's hook function stays in place.
 
== Hooking functions with ":" in their name ==
You can hook a function like "AddonName:Function()" by replaceing the ":" with a ".".
For example if the below code is from someone's addon:
function AnAddon.Module:FuncName(per1,per2)
  -- Things happen here...
end;
 
You can use the following to pre-hook it:
local original_AnAddon_Module_FuncName = AnAddon.Module.FuncName; -- Store the original function encase you want to use it.
function AnAddon.Module:FuncName(per1,per2) -- This is the new function to overwrite the original one
  if (per1 > 10) then
    ChatFrame1:AddMessage(tostring(per1));
  else
    original_AnAddon_Module_FuncName(self,...); -- Calls original function like nothing happened
  end;
end;
 
Assuming per1 is a number greater than 10 then per2 will be printed and if per1 isn't then the original function will run.


== Notes ==
== Notes ==
* Warning! Blizzard has moved some pieces of the UI to be loaded on demand. The functions in those pieces cannot be hooked until they are loaded.
* Warning! Blizzard has moved some pieces of the UI to be loaded on demand. The functions in those pieces cannot be hooked until they are loaded.
* Some libraries, like [[Sea]], [[Ace]], or [[Ace2]] offer functions to assist in hooking.
* Some libraries, like [[Sea]], [[Ace]], or [[Ace2]] offer functions to assist in hooking.