Hooking and You!
Hooks: What are they?
|This article or section contains information that is out-of-date.|
People have been hooking blizzard's UI functions for a long time. The traditional 'manual' way to hook is usually 1- 4 lines long where you either replace a function with one of your own or you store the function, replace it with your own and call the orig from inside your function. The following example demonstrates a manual hook:
function AddonName_SendChatMessage(msg, system, language, channel) --Your 'before' code here SavedSendChatMessage(msg, system, language, channel); --Your 'after' code here end local SavedSendChatMessage = SendChatMessage; SendChatMessage = AddonName_SendChatMessage;
NOTE: The hooking must be done AFTER you have declared your function or AddonName_SendChatMessage will be nil at the time you hook stuff. -Sarf
Example of a returning hook:
function AddonName_UnitIsConnected(unit) --Your 'before' code here local connected = SavedUnitIsConnected(unit); --Your 'after' code here return connected; end local SavedUnitIsConnected = UnitIsConnected; UnitIsConnected = AddonName_UnitIsConnected;
Example of a more brusque hook:
local Orig_UnitIsConnected = UnitIsConnected; function AddonName_UnitIsConnected(unit) -- Your code here return Orig_UnitIsConnected(unit); end
For the most part, that's all that the new developer knows or ever learns. However, what you may not know is that there are problems about hooks that have plagued seasoned programmers for a long time, especially ones that program compilations of dozens if not scores of addons.
Hooking Pitfalls and Limitations
Multiple addons hooking the same function
First off, if you or another programmer ever tries to hook the same function you start running into problems. Short replacement hooks are the most destructive in this manner. They completely break one of the two hooks and oftentimes cause additional havok by changing the internal mechanics of the function that may be required by another addons. For example, if function A is called within function B and function B is replaced by C which uses function D and not A, then any thing that hooks A will not be called when B is called.
There is no easy/efficient ways around this, however if you are programming for compatibility, you may have a number of choices. You could simply call you function before or after calling the orig function. This usually only requires storing the orig function under a different name, a simple fix, and under most circumstances this method is the most compatible with other hooks. Sometimes the effects of your 'before' hook may be cancelled by another 'after' hook. But fixing this would almost always require you to know of the other hook while programming so you can account for its effects. This usually only happens if you wrote the other addon or if someone reports the bug.
Unfortunately using manual hooks this is still very hard to debug. First of all you have to know what functions conflict. Then you need to guarantee that your hook code gets called after the offending hook. If both hooks are called OnLoad this can sometime be done by adding an optional dependency to your addon, causing the other addon to load first. However, if a hook is called from an event, or sometime later than OnLoad it can become very difficult to make your code load afterwards, and in some cases you may even want to hook the function before the other addon does, but then it conflicts with yours when it finally hooks.
Another common problem involves both conflicts and efficiency: unhooking. With manual hooks unhooking is almost always a bad idea. Unhooking involves replacing your hook with the orig function, with the effect of negating the effects and cpu processing of your code, and in some cases memory space. The simplest unhook is an assignment of the function to the saved orig function. It is possible to manually unhook, but it requires checking the current function definition against your stored definition to make sure the function has not been modified since you hooked it. This is require or else any hook assigned after your saved orig function was defined will be destroyed as well. This can also be more safely circumvented using a flag and an if statement inside your hook function. Thus to unhook your code you aren't actually unhooking, but your code is not processed and then the orig function is. This is almost always the safest way to unhook, but requires a global or addon local flag and doesn't actually remove your code from memory, always processing the if statement.
Example of a safe manual unhook:
local SavedPickupContainerItem; local PickupContainerItem_IsHooked; function AddonName_OnLoad() PickupContainerItem_IsHooked = 1; SavedPickupContainerItem = PickupContainerItem; PickupContainerItem = AddonName_PickupContainerItem; end function AddonName_Unhook() if (PickupContainerItem == AddonName_PickupContainerItem) then PickupContainerItem = SavedPickupContainerItem; end PickupContainerItem_IsHooked = nil; end function AddonName_PickupContainerItem(index,slot) if (PickupContainerItem_IsHooked) then --Your 'before' code here end SavedPickupContainerItem(index,slot) if (PickupContainerItem_IsHooked) then --Your 'after' code here end end
Hooks modifying input values
Another common practice for hooking safely is to modify the input of the orig function. For example by hooking HealthBar_OnValueChanged and changing the second argument from (smooth) to (not smooth) you can enable health bar color gradient changing without modifying the orig function. This means that other hooks that hooked the same function before your hook was defined will be using the modified parameters defined by your hook and that hooks called after your hook will use the orig parameters. This can be problematic, but is hard to avoid.
Hooks modifying return values
|This article or section contains information that is out-of-date.|
The other similar hooking practice, not quite as well known and possibly less useful is the method of modifying the return values of a function by calling the orig, then using it's return values as arguments to determine new replacement return values. This has the advantage that you have both the function input arguments as well as the return values and then may not have to duplicate logic already done by the orig function. This can mean more efficiency as well as compatibility, but falls under the same problematic constraints of the prior method in that whether additional hook functions conflict or not often depends on the loading order of the hook assignment.
Hooking functions that do too much
Another common problem with hooks is that the orig function does too much, so when replacing it for a small change in the middle it may then conflict with other hooks because the orig is never called. Most of the time this is the result of poor coding either in the case of the orig function (cant be avoided, gg Blizzard) or the hooker and can be avoided by hooking the functions internal to the orig function. Sometimes this is not very practical, as in the case of ChatFrame_OnEvent or FCF_OnUpdate, two of WoW's biggest troublemaker functions. In these cases you would often want to be able to call your function in place of the orig function while still calling other 'before' and 'after' hooks. Unfortunately this is nearly impossible without having the saved orig function defined before any hooks are done and then have the subsequent hooks known about or register with a hooking mechanism for management.
Hooking a function that changes in the next patch
Thankless. Your hook works fine in the current patch, and then in the next patch, Blizzard adds a third parameter. Or the function suddenly starts returning multiple values. This is somewhat of a different category of problem than this article deals with though; read more about it in HOWTO: Hook functions in a safer way.
Manual hooks vs. Hook Management Libraries
Manual Hooks: Pros
- Efficiency: Tailored code is almost always more efficient by itself. If only one adddon ever tries to hook that particular function, then manual hooking is often faster.
- No Dependency: Many users dislike additional downloads and installing 'excess' code they aren't using.
- Full Control: If you make the saved function and flags local you can guarantee that only your addon can modify the status of your hook.
Managed Hooks: Pros
- Efficiency: If multiple addons try to hook the same function it can be more efficient to only ever hook the function once and run loops on a database to execute other hook code.
- Debugging Conflicts: One of the largest draw of managed hooks is that the data is all stored in a database where you can check what addons use what functions to hook what. This makes debugging hook conflicts dramaticly simpler. It also means that if you know two hooks will conflict you can unhook as necissary and account for some of the non-conflicting code in your own hook.
- Simple to Code: Hooking and unhooking is usually abstracted to a single line function call.
- Flexible: As long as your management lib is robust enough you can implement any number of different methods of hooking without worrying about how they interact. Some of the commonly supported methos are 'after', 'before' and 'replace'. Each management lib has its own bells and whistles to try and make the coder's life simpler.
Hook Management Libraries
Even knowing all this, generating a suitably flexible, low overhead, compatibility minded hook management system is a definite challenge. Here are some of the libraries that attempt to make hooking simpler, safer, easier to debug and more compatible with multiple hooks.
Cosmos has been using a hook management system since beta and it is in a continual state of fluctuation and improvement. In the early days it was added into the Sea library. New things have been added since then, such as the ability to hook to and from functions within tables and the ability to replace the return arguments. Around the dawning of WoW 1.9, SeaHooks was created as an embeddable mini-library that was reverse compatible with the old Sea style hooks and also added new features such as modifying the input arguments from 'before' hooks, intercepting and modifying the return arguments from 'after' hooks and compatibility debugging to predict hook conflicts.
AceHooks is a Object Oriented framework for building AddOns, and it provides an abstraction for hooking methods and functions. This method is literally just an implementation of the standard hook method, and as such you are expected to act nicely (call your parent function passing the arguments) or purposely disrupt the hook chain, which is a bad thing. There is built-in prevention of double-hooks naturally, In addition the latest version of AceHook has some improvement on the hooking methods to provide proper integration with non-ace hooks.
AceHooks is also an embeddable hooking library that can be used without the entire Ace framework. For more information on the AceHooks package and its uses, please visit http://wiki.wowace.com/index.php/AceHooks