SA-MP Forums

Go Back   SA-MP Forums > SA-MP Scripting and Plugins > Scripting Help > Help Archive

 
 
Thread Tools Display Modes
Old 24/05/2011, 06:39 AM   #1
leong124
High-roller
 
leong124's Avatar
 
Join Date: Jun 2008
Location: Hong Kong, China
Posts: 1,738
Reputation: 134
Default Transparent Hooking Method with States

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 Y_Less) 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 Y_Less' 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 Y_Less released recently as most of the code here is developed by him.

Code
pawn Code:
#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 Code:
#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 Code:
#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 Code:
#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 Code:
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 Code:
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 Code:
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 Code:
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 Code:
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 Code:
chain prefix_CallbackName(parameters);
calls the hooked callback after loading your library. It is mentioned at above.

pawn Code:
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 Code:
#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 Code:
#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 Y_Less, 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 Code:
#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 Code:
#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 Code:
#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:
Code:
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
  • Y_Less - 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.
__________________
[KDT_MS]hk_shade

Sorry for my bad English and my weakness in expressing myself.

Last edited by leong124; 27/03/2013 at 09:27 PM. Reason: recommend a better method
leong124 is offline  
Old 24/05/2011, 06:54 AM   #2
[KO]KillerThriller
Big Clucker
 
Join Date: May 2011
Posts: 64
Reputation: 0
Default Re: S-ALS Hooking Method

You're point?
__________________
[IMG]http://*************/files/images/misc/Zombie.gif[/IMG]
[KO]KillerThriller is offline  
Old 24/05/2011, 07:31 AM   #3
Retardedwolf
High-roller
 
Retardedwolf's Avatar
 
Join Date: Jun 2009
Posts: 1,598
Reputation: 60
Default Re: S-ALS Hooking Method

Quote:
Originally Posted by [KO]KillerThriller View Post
You're point?
What about YOUR point? It is not meant to be said that way. You're means 'You are', you are using it as You are point? Try getting some sense into your words before trying to criticize someone else's work.
Retardedwolf is offline  
Old 24/05/2011, 09:12 AM   #4
Y_Less
Beta Tester
 
Y_Less's Avatar
 
Join Date: Jun 2008
Location: 629 - git.io/Y
Posts: 18,443
Reputation: 2601
Default Re: S-ALS Hooking Method

Have you tried this when a user DOESN'T have the callback? I'm pretty sure you'll get a run-time error for calling a function with an undefined state! It MAY be doable, but not easy however:

pawn Code:
// Define the states, a fall-back option and another option to set the automata.
// Note you need to be careful with OnPlayerCommandText as the default return is different.
// This is a generic one-time define.
#define redirect%0_%1(%2)%3; stock%0@%1()<%0:y>{}stock%0@%1()<%0:n>{}%0_%1(%2);public%0_%1(%2)<%0:n>return 1;public%0_%1(%2)<>return 1;

#define chain%0_%1(%2)%3; {state%0:y;%0_%1(%2);}

#define S_ALS(%0_%1) %0_%1<%0:y>

#include <a_samp>

public OnFilterScriptInit()
{
    // Your initialisation code.
    chain OnFilterScriptInit();
    return 1;
}

// Note the lack of state here, only the automata.
redirect OnFilterScriptInit();

#if defined _ALS_OnFilterScriptInit
    #undef OnFilterScriptInit
#else
    #define _ALS_OnFilterScriptInit
#endif
#define OnFilterScriptInit() S_ALS(AnyStateName_OnFilterScriptInit())

That code has an explicit state on the second version which is called if the function exists, and the fall-back is used if it doesn't. The macro allows you to define all the possible states and use one of them (that's never set) to define "prefix_OnFilterScriptInit" as using the "AnyStateName" automata.

Last edited by Y_Less; 24/05/2011 at 03:06 PM.
Y_Less is offline  
Old 24/05/2011, 01:24 PM   #5
leong124
High-roller
 
leong124's Avatar
 
Join Date: Jun 2008
Location: Hong Kong, China
Posts: 1,738
Reputation: 134
Default Re: S-ALS Hooking Method

I've tested that and both my method and yours work.
I can't get any compile-time or run-time error at all.
I guess it will not get an error, because the function only check if the state matches.
__________________
[KDT_MS]hk_shade

Sorry for my bad English and my weakness in expressing myself.
leong124 is offline  
Old 24/05/2011, 03:08 PM   #6
Y_Less
Beta Tester
 
Y_Less's Avatar
 
Join Date: Jun 2008
Location: 629 - git.io/Y
Posts: 18,443
Reputation: 2601
Default Re: S-ALS Hooking Method

Hmm, I thought it gave a run-time error. It doesn't seem to but does still fail. Compare this:

pawn Code:
public OnFilterScriptInit()
{
    //Your initialisation code
    printf("1");
    prefix_OnFilterScriptInit();
    printf("4");
    return 1;
}

forward prefix_OnFilterScriptInit();

public prefix_OnFilterScriptInit() <AnyStateName:AnyStateValue>
{
    printf("3");
    return 1;
}

#if defined _ALS_OnFilterScriptInit
    #undef OnFilterScriptInit
#else
    #define _ALS_OnFilterScriptInit
#endif
#define OnFilterScriptInit() prefix_OnFilterScriptInit() <>

public OnFilterScriptInit()
{
    //Your initialisation code
    printf("2");
    return 1;
}

And this:

pawn Code:
public OnFilterScriptInit()
{
    //Your initialisation code
    printf("1");
    prefix_OnFilterScriptInit();
    printf("4");
    return 1;
}

forward prefix_OnFilterScriptInit();

public prefix_OnFilterScriptInit() <AnyStateName:AnyStateValue>
{
    printf("3");
    return 1;
}

#if defined _ALS_OnFilterScriptInit
    #undef OnFilterScriptInit
#else
    #define _ALS_OnFilterScriptInit
#endif
#define OnFilterScriptInit() prefix_OnFilterScriptInit() <>

The second version is one in which the user hasn't included the callback, in that case "4" is never printed - it is crashing silently.

As a contrast, based on yours (with a few more macros to make it (IMHO) look nicer), try running both of these:

pawn Code:
#include <a_samp>

// Define the states, a fall-back option and another option to set the automata.
// Note you need to be careful with OnPlayerCommandText as the default return is different.
// This is a generic one-time define.
#define redirect%0_%1(%2)%3; stock%0@%1()<%0:y>{}stock%0@%1()<%0:n>{}%0_%1(%2);public%0_%1(%2)<%0:n>return 1;public%0_%1(%2)<>return 1;

#define chain%0_%1(%2)%3; {state%0:y;%0_%1(%2);}

#define S_ALS(%0_%1) %0_%1<%0:y>

#include <a_samp>

public OnFilterScriptInit()
{
    // Your initialisation code.
    printf("1");
    chain AnyStateName_OnFilterScriptInit();
    printf("4");
    return 1;
}

// Note the lack of state here, only the automata.
redirect AnyStateName_OnFilterScriptInit();

#if defined _ALS_OnFilterScriptInit
    #undef OnFilterScriptInit
#else
    #define _ALS_OnFilterScriptInit
#endif
#define OnFilterScriptInit() S_ALS(AnyStateName_OnFilterScriptInit())

public OnFilterScriptInit()
{
    //Your initialisation code
    printf("2");
    return 1;
}

pawn Code:
#include <a_samp>

// Define the states, a fall-back option and another option to set the automata.
// Note you need to be careful with OnPlayerCommandText as the default return is different.
// This is a generic one-time define.
#define redirect%0_%1(%2)%3; stock%0@%1()<%0:y>{}stock%0@%1()<%0:n>{}%0_%1(%2);public%0_%1(%2)<%0:n>return 1;public%0_%1(%2)<>return 1;

#define chain%0_%1(%2)%3; {state%0:y;%0_%1(%2);}

#define S_ALS(%0_%1) %0_%1<%0:y>

#include <a_samp>

public OnFilterScriptInit()
{
    // Your initialisation code.
    printf("1");
    chain AnyStateName_OnFilterScriptInit();
    printf("4");
    return 1;
}

// Note the lack of state here, only the automata.
redirect AnyStateName_OnFilterScriptInit();

#if defined _ALS_OnFilterScriptInit
    #undef OnFilterScriptInit
#else
    #define _ALS_OnFilterScriptInit
#endif
#define OnFilterScriptInit() S_ALS(AnyStateName_OnFilterScriptInit())
Y_Less is offline  
Old 25/05/2011, 03:40 AM   #7
leong124
High-roller
 
leong124's Avatar
 
Join Date: Jun 2008
Location: Hong Kong, China
Posts: 1,738
Reputation: 134
Default Re: S-ALS Hooking Method

I've thought of that last night. You are right then.
I'll have a look on it, though the libraries are still loaded.

Edit:
Alright I've edited it and it should work now. Thanks for your help
__________________
[KDT_MS]hk_shade

Sorry for my bad English and my weakness in expressing myself.

Last edited by leong124; 25/05/2011 at 05:41 AM.
leong124 is offline  
 

Thread Tools
Display Modes

Posting Rules
You may not post new threads
You may not post replies
You may not post attachments
You may not edit your posts

BB code is On
Smilies are On
[IMG] code is On
HTML code is Off


Similar Threads
Thread Thread Starter Forum Replies Last Post
[HELP] What is the best method to do this. [BKR]LUCAGRABACR Help Archive 1 10/03/2011 10:36 AM
What is a better method of saving? RealCop228 Help Archive 16 12/02/2011 06:51 PM
No run method matdav Help Archive 2 10/02/2011 11:31 PM
Will the Steam version run with this method? Asop3 Client Support 1 01/11/2009 03:35 AM


All times are GMT. The time now is 08:22 PM.


Powered by vBulletin® Version 3.8.6
Copyright ©2000 - 2014, Jelsoft Enterprises Ltd.