Hello!
We are planning some SourceHook changes for SourceMM 1.5.
First of all, all hooks now have an id. Consequently, the SH_ADD_HOOK macros return a non-zero integral hook id. A return value of 0 means failure.
This change of the return value (int->bool) may lead to warnings on some platforms if you are saving the return value in a bool variable.
Secondly, the macros have changed:
Now we have:
- SH_ADD_HOOK(class name, function name, instance pointer, handler, post or not?)
- SH_ADD_VPHOOK(class name, function name, representative instance pointer, handler, post or not?)
- SH_ADD_DVPHOOK(class name, function name, vtable pointer, handler, post or not?)
- SH_ADD_MANUALHOOK(manual hook name, instance pointer, handler, post or not?)
- SH_ADD_MANUALVPHOOK(manual hook name, representative instance pointer, handler, post or not?)
- SH_ADD_MANUALDVPHOOK(manual hook name, vtable pointer, handler, post or not?)
handler can be either SH_STATIC(non-member function / static member function) or SH_MEMBER(instance, &Class::Function)
Of course, the old macros with _STATICFUNC / _MEMFUNC will continue working. I just wanted to get rid of that because otherwise we'd have 16 ADD_HOOK_ macros now. I like the new way much more.
There is also
SH_REMOVE_HOOK_ID which removes a hook by id. This means that you can store the return value of the SH_ADD_*HOOK macro and then pass this to SH_REMOVE_HOOK_ID. The old SH_REMOVE_HOOK macros continue working as well - however, SH_REMOVE_HOOK_ID is the only way to remove VP hooks and manual VP hooks.
Also note that now it is safe to remove hooks even on instances which have been already deleted.
VP hooks behave like normal hooks - except for the fact that they are not bound to a specific instance of the class but are called everytime the Virtual function Pointer (that's why they are called VP) gets executed. The "representative instance pointer" is a pointer to an instance of the class on whose vtable you want to install the hook. I think it's easiest to see this on a code example:
Code:
class Base
{
public:
virtual void Function() = 0;
};
class Derived1 : public Base
{
public:
void Function()
{
// Function 1
}
};
class Derived2 : public Base
{
public:
void Function()
{
// Function 2
}
};
void HookFunctionNormal()
{
printf("Normal hook Called!\n");
}
void HookFunctionVP()
{
printf("VP hook Called!\n");
}
void Test()
{
// Both Derived1 and Derived2 implement Function()
Derived1 d1_instance1;
Derived1 d1_instance2;
Derived2 d2_instance1;
Base *pd1_instance1 = &d1_instance1;
Base *pd1_instance2 = &d1_instance2;
Base *pd2_instance1 = &d2_instance1;
// Now add a normal hook on d1_instance1
int hook1 = SH_ADD_HOOK(Base, Function, pd1_instance1, SH_STATIC(HookFunctionNormal), false);
// And add a VP hook.
int hook2 = SH_ADD_VPHOOK(Base, Function, pd1_instance1, SH_STATIC(HookFunctionVP), false);
// Try to call some functions.
pd1_instance1->Function(); // Prints "VP hook Called", then "Normal hook called"
pd1_instance2->Function(); // Only prints "VP hook Called"
pd2_instance1->Function(); // Doesn't print anything.
SH_REMOVE_HOOK_ID(hook1);
SH_REMOVE_HOOK_ID(hook2);
pd1_instance1->Function(); // Prints nothing
pd1_instance2->Function(); // Prints nothing
pd2_instance1->Function(); // Prints nothing
}
(no, I haven't even compiled this, but it should work)
So, you see, normal hooks are only executed on one specific instance; VP hooks are executed on all instances of a class (attention; if you have a magic pointer from somewhere, and this pointer is in fact a DERIVED class of what you think it is, it has its own vtable and thus VP hooks on what you think it is aren't called on it!).
Alternatively, you can use "direct vp hooks" (SH_ADD_DVPHOOK or SH_ADD_MANUALDVPHOOK): here, you give sourcemod a pointer to the vtable instead of a representative instance pointer. This lets you compute the vtable address using an offset without having a real instance. Note that SH expects a pointer to the beginning of the vtable - it then still adds vtable index * sizeof(void*) to it.
It's also pretty important to note that vp hooks are called in the order they were installed (which is not necessarily the order of their hook ids - ids get reused), but ALWAYS before SH starts calling normal hooks. This is also the reason why the above example first prints "VP hook called" even though the VP hook was the second declared hook.
There's also one very important change: The concept of SH_CALL and callclasses was bound to interfaces. This means that it did not work anymore for VP hooks.
As of today, CallClasses are NOT used anymore. SH_CALL's first parameter is now the this pointer. You do NOT need to request and release a call class amymore. I've done some template and macro magic in order to make the new system source compatible (so the old code with SH_GET_CALLCLASS etc. will compile, but it is completely unnecessary and I'd encourage you to convert the code to the new method). Also, the ISourceHook interface continues to provide support for CallClasses so that old plugins should still work (however, if a new plugin uses a VP hook, old plugins might cause the hook to be called even if using SH_CALL).
In order to use this, you will need the newest
sourcehook.h from SVN. Plugins compiled with this file need a new version of sourcemm, which you can get in my testing post in this forum. Before we release this, we need to be sure that it's backwards compatible with older plugins.
So, have fun with it
__________________