S-ALS Hooking Method
#1

Transparent Hooking Method with States(THMS)
Better Method
Better methods of hooking functions are now found. It makes use of the behaviour of macros to modify the name of the hooked functions. It's simpler and faster, so I recommend that method (by ipsBruno): http://forum.sa-mp.com/showpost.php?...2&postcount=14

What is it?
This is an improved method of the original transparent hooking method (named by ******) for the library scripters, so that it is more efficient than the original one, while they still do not need to tell the users to put the initialisation code into their scripts.

Inspiration
Yesterday I was inspired by ******' automata tutorial:(Click).

I wonder if we can use automata to change the states so that we don't need to add any prefix to the hooked callbacks when we write libraries, without duplication of the callback. However I failed to make that to become true. I still need a suffix. (Click)

Then I tried to mix that idea with the ALS method this morning and I succeeded to make this.
I think this should be the same as the one ****** released recently as most of the code here is developed by him.

Code
pawn Код:
#include <a_samp>

#define THMS(%0_%1(%2)) %0_%1(%2) <%0:y>
#define chain%0_%1(%2)%3; {state %0:y;%0_%1(%2);}
#define redirect%0_%1(%2)%3; forward%0_%1(%2);stock%0@%1(%2)<%0:y>{}public%0_%1(%2)<%0:n>%3;public%0_%1(%2)<>%3;

public CallbackName(parameters)
{
    //Your initialisation code.
    chain prefix_CallbackName(parameters);
    return 1;
}

redirect prefix_CallbackName(parameters) return 1;

#if defined _ALS_CallbackName
    #undef CallbackName
#else
    #define _ALS_CallbackName
#endif
#define CallbackName(parameters) THMS(prefix_CallbackName(parameters))
Explanation
I'll explain it a little if you don't understand why it works:
pawn Код:
#define THMS(%0_%1(%2)) %0_%1(%2) <%0:y>
This macro puts a state at the hooked callback, so that it can be correctly called when your library is loaded.

pawn Код:
#define chain%0_%1(%2)%3; {state %0:y;%0_%1(%2);}
This macro actually changes the state that is required for the hooked callback to run, and it also calls the hooked callback without using CallLocalFunction, which is the main bottleneck of the ALS hooking.

pawn Код:
#define redirect%0_%1(%2)%3; forward%0_%1(%2);stock%0@%1(%2)<%0:y>{}public%0_%1(%2)<%0:n>%3;public%0_%1(%2)<>%3;
This macro forwards the hooked function with your prefix, just like the ALS method, and also creates 3 functions with the same name but in different states.

The first function created,
pawn Код:
stock prefix@CallbackName(parameters)<prefix:y> {}
is created to use the state prefix:y, so it will not cause compile-time errors saying this automaton is unused, when there's no function to be hooked. Since the function name is different from the callback, it won't be called. The "{}" means that it has nothing to do.

The second one,
pawn Код:
public prefix_CallbackName(parameters)<prefix:n> return 1;
is created to escape from errors saying that no other states are declared for our callback when there's no callback can be hooked. Again, it will never be called because the state prefix:n is not used (but it will not give out warnings, since the automaton "prefix" is used above).

The last one,
pawn Код:
public prefix_CallbackName(parameters)<> return 1;
is the function that will be called when no callback can be hooked. In the chain macro mentioned above, the state is set into prefix:y. When the hooked callback does not exists, the callback with that state cannot be found. Therefore this one will be called as no other callback can be called(the way for automata to work). It has nothing to do, so it directly returns and terminates itself.
Note that the "return 1" can be changed to "return 0" or some other values in
pawn Код:
redirect prefix_CallbackName(parameters) return 1;
So that you can simply return the default values of the callback(for example OnPlayerCommandText should be "return 0") without terminating the chain call in other scripts.

pawn Код:
public CallbackName(parameters)
{
    //Your initialisation code.
    chain prefix_CallbackName(parameters);
    return 1;
}
This is the normal callback that you can put your initialisation code of your library here.
The
pawn Код:
chain prefix_CallbackName(parameters);
calls the hooked callback after loading your library. It is mentioned at above.

pawn Код:
redirect prefix_CallbackName(parameters) return 1;
Here this code escapes the errors that THMS hooking may have when no callback can be hooked.
Again, check it above for what it does.

pawn Код:
#if defined _ALS_CallbackName
    #undef CallbackName
#else
    #define _ALS_CallbackName
#endif
This is the same as the ALS hooking method. It check if any callback with the same name is defined so that we can undefine it. If it doesn't, we announce that the callback is created, so that other hooks can know about it.

pawn Код:
#define CallbackName(parameters) THMS(prefix_CallbackName(parameters))
This one adds our prefix and state to the name of the hooked callback, so that it is unique and we can call it correctly. For the THMS macro you can check it above.

Testing
I've tested this method that no errors were found and multiple hooking does work(i've tested it with triple hooking ). It also have backward compatibility that it supports includes with the original ALS method. Thanks to ******, the run-time errors are fixed. It should work now, but if you find any bugs/problems on this please feel free to tell it here.

Note
Due to the removal of CallLocalFunction, you don't need to use funcidx to check if the callback exists and call it IMHO. I've made a benchmark on it and it's as slow as the original ALS hooking.

Benchmark
I used Slice's benchmark macro to test it here, and I test the speed by calling the original OnFilterScriptInit callback:
Testing filterscript:
pawn Код:
#include <a_samp>
#include <testhook>

#pragma tabsize 0

#define START_BENCH(%0); {new __a=%0,__b=0,__c,__d=GetTickCount(),__e=1;do{}\
    while(__d==GetTickCount());__c=GetTickCount();__d=__c;while(__c-__d<__a||\
    __e){if(__e){if(__c-__d>=__a){__e=0;__c=GetTickCount();do{}while(__c==\
    GetTickCount());__c=GetTickCount();__d=__c;__b=0;}}{

#define FINISH_BENCH(%0); }__b++;__c=GetTickCount();}printf(" Bench for "\
    %0": executes, by average, %.2f times/ms.",floatdiv(__b,__a));}

public OnFilterScriptInit()
{
    return 1;
}

public OnRconCommand(cmd[])
{
    if(!strcmp(cmd,"test",false))
    {
        START_BENCH(10000);
        CallLocalFunction("OnFilterScriptInit","");
        FINISH_BENCH("ALS");
    }
    return 1;
}
testhook.inc(ALS version):
pawn Код:
#include <a_samp>

public OnFilterScriptInit()
{
    CallLocalFunction("testhook_OnFilterScriptInit","");
    return 1;
}

#if defined _ALS_OnFilterScriptInit
    #undef OnFilterScriptInit
#else
    #define _ALS_OnFilterScriptInit
#endif
#define OnFilterScriptInit testhook_OnFilterScriptInit

forward testhook_OnFilterScriptInit();
testhook.inc(THMS version):
pawn Код:
#include <a_samp>

#define THMS(%0_%1(%2)) %0_%1(%2) <%0:y>
#define chain%0_%1(%2)%3; {state %0:y;%0_%1(%2);}
#define redirect%0_%1(%2)%3; forward%0_%1(%2);stock%0@%1(%2)<%0:y>{}public%0_%1(%2)<%0:n>%3;public%0_%1(%2)<>%3;

public OnFilterScriptInit()
{
    chain testhook_OnFilterScriptInit();
    return 1;
}

redirect testhook_OnFilterScriptInit() return 1;

#if defined _ALS_OnFilterScriptInit
    #undef OnFilterScriptInit
#else
    #define _ALS_OnFilterScriptInit
#endif
#define OnFilterScriptInit() THMS(testhook_OnFilterScriptInit())
Here's the benchmarking result:
Код:
Bench for THMS: executes, by average, 516.88 times/ms.
Bench for ALS: executes, by average, 401.54 times/ms.
As you can see, THMS is faster than the original one by around 30%.
My server crashed when I try to test it with y_hooks so feel free to test it.

Issues
  1. You can't forward the hooked function after the THMS macro. You'll need to undefine the macro first if you want to. However, forwarding the function is unnecessary, as the function is forwarded in the redirect marco.
  2. You can't call the original function after the THMS macro. If you try to, you will notice that a state label is put there by the macro. You'll also need to undefine the macro before doing so.
  3. You can't call the hooked function by CallRemoteFunction. Seems that states cannot be recognised in other scripts (i.e. it is not global).
Credits
  • ****** - Inspired me by his great tutorial on automata, and even helped me to fix the bugs.
  • Slice - For his great benchmarking macro.
  • leong124 - For inventing this hooking method.
Reply


Messages In This Thread
Transparent Hooking Method with States - by leong124 - 24.05.2011, 06:39
Re: S-ALS Hooking Method - by [KO]KillerThriller - 24.05.2011, 06:54
Re: S-ALS Hooking Method - by Retardedwolf - 24.05.2011, 07:31
Re: S-ALS Hooking Method - by leong124 - 24.05.2011, 13:24
Re: S-ALS Hooking Method - by leong124 - 25.05.2011, 03:40

Forum Jump:


Users browsing this thread: 1 Guest(s)