Wowpedia

We have moved to Warcraft Wiki. Click here for information and the new URL.

READ MORE

Wowpedia
(limitations of all hooks, not just manual ones.)
(Updated Hook Documentation with examples and a little less bias.)
Line 1: Line 1:
== Hooking History (The Limitations of Hooks) ==
+
== Hooking and You! ==
So, as we all know you can easily hook functions in wow lua. Addon devs tend to prefer to manual hooks so as not to require a library. Manual hooks are 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.
 
   
  +
===Hooks: What are they?===
For the most part, that's all that the new deveoloper knows or ever learns. However, what you may not know is that there are problems about manual hooks that have plagued seasoned programmers for a long time, especially ones that program compilations of dozens if not scores of addons.
 
  +
*Hooking a function is the procedure of modifying a function after it has been defined to carry out another set of instructions every time the original function is called.
 
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:
  +
local SavedSendChatMessage = SendChatMessage;
  +
SendChatMessage = AddonName_SendChatMessage;
  +
  +
function AddonName_SendChatMessage(msg, system, language, channel)
  +
--Your 'before' code here
  +
SavedSendChatMessage(msg, system, language, channel);
  +
--Your 'after' code here
  +
end
   
  +
Example of a returning hook:
  +
local SavedUnitIsConnected = UnitIsConnected;
  +
UnitIsConnected = AddonName_UnitIsConnected;
  +
  +
function AddonName_UnitIsConnected(unit)
  +
--Your 'before' code here
  +
local connected = SavedUnitIsConnected(unit);
  +
--Your 'after' code here
  +
return connected;
  +
end
  +
 
For the most part, that's all that the new deveoloper 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===
 
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 manor. They completely break one of the two hooks and often times 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.
 
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 manor. They completely break one of the two hooks and often times 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 canciled 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 it's effects. This usually only happens if you wrote the other addon or if someone reports the bug.
 
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 canciled 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 it's 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. Fist 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 dependancy 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.
+
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 dependancy 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:
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. This is as terrible for compatibility as a one line assignment hook in that any hook assigned after your saved orig function was defined will be destroyed as well. This can be 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.
 
  +
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 hooked then
  +
--Your 'before' code here
  +
end
  +
SavedPickupContainerItem(index,slot)
  +
if hooked then
  +
--Your 'after' code here
  +
end
  +
end
   
  +
----
 
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.
 
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.
   
  +
----
 
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.
 
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.
   
  +
----
 
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.
 
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.
  +
  +
===Manual hooks vs. Hook Management Libraries===
  +
Addon devs tend to prefer to manual hooks so as not to require a library or to make thier code as streamlined as possible.
  +
More Comming Soon.
   
 
== Hook Management Libraries ==
 
== Hook Management Libraries ==
:Even knowing all this, generating a suitably flexible, low overhead, compatibility minded hook management system is a definite challenge.
+
: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/Sea ===
 
=== Cosmos/Sea ===

Revision as of 20:57, 28 November 2005

Hooking and You!

Hooks: What are they?

  • Hooking a function is the procedure of modifying a function after it has been defined to carry out another set of instructions every time the original function is called.

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:

local SavedSendChatMessage = SendChatMessage;
SendChatMessage = AddonName_SendChatMessage;

function AddonName_SendChatMessage(msg, system, language, channel)
	--Your 'before' code here
	SavedSendChatMessage(msg, system, language, channel);
	--Your 'after' code here
end

Example of a returning hook:

local SavedUnitIsConnected = UnitIsConnected;
UnitIsConnected = AddonName_UnitIsConnected;

function AddonName_UnitIsConnected(unit)
	--Your 'before' code here
	local connected = SavedUnitIsConnected(unit);
	--Your 'after' code here
	return connected;
end

For the most part, that's all that the new deveoloper 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

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 manor. They completely break one of the two hooks and often times 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 canciled 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 it's 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 dependancy 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 hooked then
		--Your 'before' code here
	end
	SavedPickupContainerItem(index,slot)
	if hooked then
		--Your 'after' code here
	end
end

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.


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.


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.

Manual hooks vs. Hook Management Libraries

Addon devs tend to prefer to manual hooks so as not to require a library or to make thier code as streamlined as possible. More Comming Soon.

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/Sea

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.

Ace

AceHooks has its own hooking abstraction. Please add info.