AutoHotkey Homepage AutoHotkey Community
Let's help each other out
 
 FAQFAQ   SearchSearch   MemberlistMemberlist   RegisterRegister 
 ProfileProfile   Log in to check your private messagesLog in to check your private messages   Log inLog in 

function hooks
Goto page 1, 2  Next
 
Reply to topic    AutoHotkey Community Forum Index -> Wish List
View previous topic :: View next topic  

advice is best for end user programming
yes
66%
 66%  [ 2 ]
no
33%
 33%  [ 1 ]
Total Votes : 3

Author Message
tinku99



Joined: 03 Aug 2007
Posts: 399
Location: Houston, TX

PostPosted: Wed Jan 14, 2009 6:13 am    Post subject: function hooks Reply with quote

http://www.gnu.org/software/emacs/elisp/html_node/Advising-Functions.html

function hooks like the ability to advise functions in emacs elisp would be a nice feature to have in ahk.

It would help with debugging as well as metaprogramming.

Edit: Thanks for Lexikos
DebugBIF

Edit: adviseUDF for ahkv1.48 thanks to Lexikos.
Code:

; requires ahkv1.48+
; huge thanks to Lexikos.
adviseUDF(advice, type, advisee)  ; type = before or after
{
    static ACT_EXPRESSION:=3, _sizeof_FuncParam:=16, _Var_Name:=24
        , _JumpToLine:=4, _Param:=8, _ParamCount:=12, _MinParams:=16, _IsBuiltIn:=49
        , pn:="1,2,3,4,5,6,7,8,9,10", SYM_INVALID:=56, SYM_VAR:=3, SYM_FUNC:=55, SYM_OPERAND:=4, VAR_ALIAS:=0
        , count = 0

    ErrorLevel := ErrorLevel  ; As of v1.0.48.02, this line is omitted from ListLines. Its
    ListLines Off             ; presence allows the line which called adviseUDF() to be seen.   
    LowLevel_init()

    ; Resolve functions and validate.
    If !(advice_func := __findFunc(advice))
        || !(advisee_func := __findFunc(advisee))
        || NumGet(advisee_func+_IsBuiltIn,0,"char")
    {
        ListLines On
        return false
    }
    param_count := NumGet(advisee_func+_ParamCount)

    ; Begin generating code.
    cg := code_gen()

    ; Begin a standalone expression. Each standalone expression has only one arg.
    eline := code_line(cg, ACT_EXPRESSION )
    code_arg(cg, 0, 1)
    code_line_end(cg)
    arg := NumGet(eline+0, 4)

    ; postfix%count% must fit one ExprTokenType for each param, followed by a SYM_FUNC token
    ; representing a function call, a SYM_INVALID token terminating the expression and
    ; a DerefType holding a pointer to the function to call.
    count += 1
    __static(postfix%count%), __static(text%count%:="[advise " advice " " type " " advisee "]")
    VarSetCapacity(postfix%count%, 16*(param_count+2)+12, 0) ; 16*(MAX_PARAMS+2) + 12*1
    this_postfix := &postfix%count%
    param := NumGet(advisee_func+_Param)
    pc := 0
    Loop, % param_count
    {
        ; Ignore any parameters beyond the function's capacity.
        If (pc >= NumGet(advice_func+12))
            break

        var := NumGet(param+0)
        ; Resolve ByRef/alias.
        If (NumGet(var+23,0,"char") = VAR_ALIAS)
            var := NumGet(var+12)

        ; Put param var into postfix%count% array.
        NumPut(var, this_postfix+0), NumPut(SYM_VAR, this_postfix+8)

        ; Move to next token in postfix%count% array, increment param count.
        this_postfix += 16
        pc++
        param += _sizeof_FuncParam

    }

    ; Set up the DerefType at the end of the postfix%count% array.
    func_deref := this_postfix+32
    NumPut(advice_func, func_deref+4)
    NumPut(pc, func_deref+9, 0, "char")

    ; Set up SYM_FUNC token.
    NumPut(func_deref, this_postfix+0)
    NumPut(SYM_FUNC, this_postfix+8)
    this_postfix += 16

    ; Terminate postfix%count% array.
    NumPut(SYM_INVALID, this_postfix+8)

    ; Set postfix%count% array of the first arg of 'return' below.
    NumPut(&postfix%count%, arg+12)
    NumPut(&text%count%, arg+4)

    If (type == "after")
    {
        line1 := Numget(advisee_func+_JumpToLine, 0, "UInt")
        Loop
        {
            lineAct := NumGet(line1+0, 0, "UChar")
            If (lineAct != 102)
                line1 := NumGet(line1+0, 20, "UInt")
            Else
                break
        }

        If (ch := code_finalize(cg))
        {
            ; Insert the code before the first return statement
            code_insert_before(ch, line1)
            code_delete_handle(ch)
        }
    }
    Else
    {
        If (ch := code_finalize(cg))
        {
            ; Insert the code before the jump-to line of the function.
            code_insert_before(ch, NumGet(advisee_func+_JumpToLine))
            ; Re-target the function at the new line.
            NumPut(eline, advisee_func+_JumpToLine)
            code_delete_handle(ch)
        }
    }
    code_gen_delete(cg)
    ListLines On
    return !!ch
}


myFunc("Apple","banana")
adviseUDF("SwapParameters","before","myFunc")
myFunc("Apple","banana")
adviseUDF("EXCLAIM","before","myFunc")
adviseUDF("LogParams","before","myFunc")
myFunc("Apple","banana")
adviseUDF("LogParams","after","threeparams")
threeparams("alpha","bravo","charlie")
ListLines
Pause

threeparams(a,b,c) {
    a=%a%
    return
}
myFunc(A,B) {
    MsgBox 0, myFunc, %A%, meet %B%.
    return
}
Exclaim(ByRef A, ByRef B) {
    StringUpper, A, A
    StringUpper, B, B
}
SwapParameters(ByRef A, ByRef B) {
    C := A
    A := RegExReplace(B,"^\w","$U0")
    B := RegExReplace(C,"^\w","$l0")
}
LogParams(a="", b="", c="") {
    MsgBox 0, LogParams, %a%,%b%,%c%
}


Edit: code for ahk v1.47.06
Code:

advise(advice, type, advisee)
{
    ; adapted from lexikos code, see posts below.
    static ACT_EXPRESSION:=3, _sizeof_FuncParam:=16, _Var_Name:=16
        , _JumpToLine:=4, _Param:=8, _ParamCount:=12, _MinParams:=16, _IsBuiltIn:=49
    LowLevel_init()
    ; Resolve functions and validate.
    if !(advice_func := __findFunc(advice))
        || !(advisee_func := __findFunc(advisee))
        || NumGet(advisee_func+_IsBuiltIn,0,"char")
        || (param_count := NumGet(advisee_func+_ParamCount)) < NumGet(advice_func+_MinParams)
        return false
    ; Begin generating code.
    cg := code_gen()
    ; Begin a standalone expression. Each standalone expression has only one arg.
    line := code_line(cg, ACT_EXPRESSION), code_arg(cg, 0, true)
    ; Begin function call by adding a function deref.
    code_arg_deref(cg, advice, advice_func, true, param_count)
    ; Write required text for a function call:
    code_arg_write(cg, "(")
    ; Get address of advisee's first parameter (FuncParam).
    param := NumGet(advisee_func+_Param)
    ; For each available parameter,
    Loop %param_count%
    {
        if A_Index > 1
            code_arg_write(cg, ",")
        ; Get Var of this FuncParam.
        param_var := NumGet(param+0)
        ; Add variable deref.
        code_arg_deref(cg, __str(NumGet(param_var+_Var_Name)), param_var, false)
        ; Move to next parameter. (OK if no next parameter; loop will terminate).
        param += _sizeof_FuncParam
    }
    code_arg_write(cg, ")")
    ; Finish generating code.


if (type = "after")
{
  line1 := Numget(advisee_func+_JumpToLine, 0, "UInt")
  loop
  {
  lineAct := NumGet(line1+0, 0, "UChar")
    if (lineAct != 102)
      line1 := Numget(line1+0, 20, "UInt")
    else
      break
  }
 
  {
    if (ch := code_finalize(cg))
    {
      ; Insert the code before the first return statement
      code_insert_before(ch, line1)
      code_delete_handle(ch)
    }
    code_gen_delete(cg)
    return !!ch
  }
}
else
{
    if (ch := code_finalize(cg))
    {
        ; Insert the code before the jump-to line of the function.
        code_insert_before(ch, NumGet(advisee_func+_JumpToLine))
        ; Re-target the function at the new line.
        NumPut(line, advisee_func+_JumpToLine)
        code_delete_handle(ch)
    }
    code_gen_delete(cg)
    return !!ch
  }
 
}


Code:

AdviseBIF(advisee, type, advice)
{
    ; adapted from lexikos code, see posts below.
    ; type := "before" or "after"
adviceInstruction := ""
. "B9" mcodeptr(RegisterCallback(advice,"Cdecl",4,1))    ; mov   ecx, ...
. "FFD1"          ; call  ecx
%type% := adviceInstruction
    __mcode(advisee, ""
    . "8B442404"      ; mov   eax,dword ptr [esp+4]
    . "8B08"          ; mov   ecx,dword ptr [eax]
    . "51"            ; push  ecx
    . "FF742410"      ; push  dword ptr [esp+10h]
    . "FF742410"      ; push  dword ptr [esp+10h]
    . "50"            ; push  eax
    . before
    . "B9" mcodeptr(NumGet(__findFunc(advisee)+4))    ; mov   ecx, ...
    . "FFD1"          ; call  ecx
    . after
    . "83C410"        ; add   esp,10h
    . "C3")           ; ret
}


Last edited by tinku99 on Thu May 28, 2009 3:42 am; edited 7 times in total
Back to top
View user's profile Send private message Send e-mail Visit poster's website
majkinetor



Joined: 24 May 2006
Posts: 4250
Location: Belgrade

PostPosted: Wed Jan 14, 2009 2:59 pm    Post subject: Reply with quote

This is called Aspect Oriented Programming and its way beyond AHK.

You could change your programming style to do something similar (although much less sophisticated) , using dynamic function calls.

The other thing is that you talk about something from Lisp and that is completely different paradigm. Lisp supports that kind of extreme but in lets say pure Object languages its usually implemented via postprocessr (see for instance PostSharp or AspectJ). This way it could be done in AHK too, although preprocessor would be more appropriate in this case.

AOP is fantastic and interesting thing, but it kills the simplicity of the language like AHK and there are not that many people there that would use that feature. It requires very experienced programmers.
_________________
Back to top
View user's profile Send private message
tinku99



Joined: 03 Aug 2007
Posts: 399
Location: Houston, TX

PostPosted: Thu Jan 15, 2009 6:42 am    Post subject: ahk is the write place for aop Reply with quote

AOP can be thought of as a debugging tool or as a user level tool. Advice should be reserved for the cases where you cannot get the function changed (user level)http://www.gnu.org/software/emacs/elisp/html_node/Advising-Functions.html (from Emacs Documentation)] or do not want to change the function in production code (debugging).

Also, AOP is essentially a new class of hooks. AHK is all about hooking: user interfaces to 3rd party applications, windows api hooks.
One implementation would be for functions to automatically check for
A_ThisFunction%advice% and run it first.

I tried to hook dynamic function calls to start, but i am doing something wrong, would you mind looking at the following:
Thanks.
at line 702 in (Lexikos' custom build) script_expression.cpp after
Code:

      if (*op_end == '(') // i.e. dynamic function call
      {
         if (infix_count > MAX_TOKENS - 2) // No room for the following symbol to be added (plus the ++infix done that will be done by the outer loop).
            goto abnormal_end;
         ++infix_count;
         // As a result of a prior loop, deref_start = the null-marker deref which terminates the deref list.
         deref_start->is_function = true;
         // param_count was set when the derefs were parsed.
         deref_start->param_count = deref_alloca->param_count;
      //  aspect oriented programming attempt 1.2
char *FuncName = deref_start->func->mName;
char AdviceFuncName[50];
AdviceFuncName[0] = '\0';
strcpy( AdviceFuncName, FuncName );
strncat( AdviceFuncName, "advice", strlen("advice"));

if (g_script.FindFunc(AdviceFuncName) != NULL)
         {   
deref_start->func = g_script.FindFunc(AdviceFuncName);
            }   

         infix[infix_count].deref = deref_start;
         infix[infix_count].symbol = SYM_FUNC;
         
         // postfix processing of SYM_DYNAMIC will update deref->func before SYM_FUNC is processed.
      }
      else
         deref_start->is_function = false;
      *target++ = '\0'; // Terminate the name, which looks something like "Array%i%".
      cp = op_end; // Must be done only after above is done using cp: Set things up for the next iteration.
      // The outer loop will now do ++infix for us.
   } // For each deref in this expression, and also for the final literal/raw text to the right of the last deref.

         
[/url]
Back to top
View user's profile Send private message Send e-mail Visit poster's website
Lexikos



Joined: 17 Oct 2006
Posts: 4726
Location: Australia

PostPosted: Thu Jan 15, 2009 8:49 am    Post subject: Reply with quote

That particular piece of code is part of "infix processing", where the expression text is converted into tokens. deref_start->func should always be NULL at that point, as the function name is not yet known. It is filled in later, when the SYM_DYNAMIC (i.e. %func_name% preceding the parameter list) is processed.

This approach is not ideal, anyway. You would need to handle it where SYM_FUNC is processed in order to call both functions, or to not require the function be called dynamically. You would also need to loop to call both the advice function and original function, unless the advice function explicitly calls the original function. Additional work would be needed to support multiple advice functions. Lastly, it is not intuitive, hurts performance and does not allow the advice functions to have meaningful names.

I think majkinetor suggestion was to use dynamic function calls, not to modify them.
Code:
myFunc = myFunc
;...
myFunc = myFuncAdvice
;...
%myFunc%()
;...
myFuncAdvice() {
    ;...
    return myFunc()
}
Got the idea?
Quote:
AHK is all about hooking: user interfaces to 3rd party applications, windows api hooks.
Neither point relates to hooking AutoHotkey script functions.
Back to top
View user's profile Send private message Visit poster's website
Lexikos



Joined: 17 Oct 2006
Posts: 4726
Location: Australia

PostPosted: Thu Jan 15, 2009 10:04 am    Post subject: Reply with quote

Code:
myFunc("Apple","banana")
advise("SwapParameters","before","myFunc")
myFunc("Apple","banana")
ListLines   ; Note lines numbered "000" are dynamic lines.
Pause

myFunc(A,B) {
    MsgBox %A%, meet %B%.
}

SwapParameters(ByRef A, ByRef B) {
    C := A
    A := RegExReplace(B,"^\w","$U0")
    B := RegExReplace(C,"^\w","$l0")
}

advise(advice, type, advisee)
{
    static ACT_EXPRESSION:=3, _sizeof_FuncParam:=16, _Var_Name:=16
        , _JumpToLine:=4, _Param:=8, _ParamCount:=12, _MinParams:=16, _IsBuiltIn:=49
    if type != before
        return false ; not supported.
    LowLevel_init()
    ; Resolve functions and validate.
    if !(advice_func := __findFunc(advice))
        || !(advisee_func := __findFunc(advisee))
        || NumGet(advisee_func+_IsBuiltIn,0,"char")
        || (param_count := NumGet(advisee_func+_ParamCount)) < NumGet(advice_func+_MinParams)
        return false
    ; Begin generating code.
    cg := code_gen()
    ; Begin a standalone expression. Each standalone expression has only one arg.
    line := code_line(cg, ACT_EXPRESSION), code_arg(cg, 0, true)
    ; Begin function call by adding a function deref.
    code_arg_deref(cg, advice, advice_func, true, param_count)
    ; Write required text for a function call:
    code_arg_write(cg, "(")
    ; Get address of advisee's first parameter (FuncParam).
    param := NumGet(advisee_func+_Param)
    ; For each available parameter,
    Loop %param_count%
    {
        if A_Index > 1
            code_arg_write(cg, ",")
        ; Get Var of this FuncParam.
        param_var := NumGet(param+0)
        ; Add variable deref.
        code_arg_deref(cg, __str(NumGet(param_var+_Var_Name)), param_var, false)
        ; Move to next parameter. (OK if no next parameter; loop will terminate).
        param += _sizeof_FuncParam
    }
    code_arg_write(cg, ")")
    ; Finish generating code.
    if (ch := code_finalize(cg))
    {
        ; Insert the code before the jump-to line of the function.
        code_insert_before(ch, NumGet(advisee_func+_JumpToLine))
        ; Re-target the function at the new line.
        NumPut(line, advisee_func+_JumpToLine)
        code_delete_handle(ch)
    }
    code_gen_delete(cg)
    return !!ch
}
Requires LowLevel and AutoHotkey v1.0.47.06 (specifically).

Note that if you duplicated the advise() line, it would seemingly swap the parameters back to normal. However, it is actually inserting a second call to SwapParameters.
Back to top
View user's profile Send private message Visit poster's website
tinku99



Joined: 03 Aug 2007
Posts: 399
Location: Houston, TX

PostPosted: Thu Jan 15, 2009 7:59 pm    Post subject: advising builtin functions Reply with quote

Thanks Lexikos.
That works nicely for user defined functions.
However, its not advisable to advise user defined functions, but to change the function call in the style recommended by majkinetor.

Could your approach work for builtin functions?

It would be even nicer if it worked for commands or script lines in general, particularly for things like gosub and return.

As for even lowerlevel stuff.
What do you think of hooking the cpp function in ahk that is reponsible for listing recently executed lines in the ListLines gui?


Last edited by tinku99 on Thu Jan 15, 2009 8:31 pm; edited 1 time in total
Back to top
View user's profile Send private message Send e-mail Visit poster's website
tinku99



Joined: 03 Aug 2007
Posts: 399
Location: Houston, TX

PostPosted: Thu Jan 15, 2009 8:25 pm    Post subject: advising builtin functions Reply with quote

It works for builtins if you override the builtin first:

Code:

Add(A,B) {
return % A - B
}

Msgbox % Add(5,6) 
advise("SwapParameters","before","Add")
Msgbox % Add(5,6)
ListLines   ; Note lines numbered "000" are dynamic lines.
Pause

SwapParameters(ByRef A, ByRef B) {
Global
    C := A
    A := B
    B := C
}


advising functions like "InStr" that are used by the advise function do give problems though.
Back to top
View user's profile Send private message Send e-mail Visit poster's website
Lexikos



Joined: 17 Oct 2006
Posts: 4726
Location: Australia

PostPosted: Thu Jan 15, 2009 11:04 pm    Post subject: Re: advising builtin functions Reply with quote

tinku99 wrote:
It works for builtins if you override the builtin first:
No, it works for user-defined functions which are obscuring built-in functions. Wink

"Advising" built-in functions would require a bit of machine code. See DllCallDebugger. The best way may be to write some code that has a list of advice callbacks, and to somehow detect built-in functions which already have advice. (Edit: The Func structure has a number of fields which aren't used for built-ins.) Otherwise it will be necessary to recurse once for each advice. Pseudocode for the machine code:
Code:
InStr = AdviceMachineCode2
;...
%InStr%(A,B,C)
;...
AdviceMachineCode2(A,B,C) {
    AdviceCallback2(A,B,C)
    return AdviceMachineCode1(A,B,C)
}
AdviceMachineCode1(A,B,C) {
    AdviceCallback1(A,B,C)
    return InStr(A,B,C)
}
On the other hand, it could use tail recursion/jumps (i.e. jmp instruction rather than call.) Pseudo-assembly:
Code:
AdviceMachineCode2:
    ; Note parameters would be off by 4 bytes because of our "return" address.
    call AdviceCallback2
    jmp AdviceMachineCode1

AdviceMachineCode1:
    call AdviceCallback1
    jmp InStr
This way a virtually unlimited number of advice functions could be implemented, but could not be removed at a later point.

Edit: Noticed your previous post. Rolling Eyes
tinku99 wrote:
However, its not advisable to advise user defined functions, but to change the function call in the style recommended by majkinetor.
How so? I'd guess that dynamic function calls are slower, and would not be as "modular."
Quote:
It would be even nicer if it worked for commands or script lines in general, particularly for things like gosub and return.
code.ahk can generate basically any (script) code, and insert it anywhere (in script). You would need to search for each gosub or return line, though. Beyond debugging, what are its uses?
Quote:
What do you think of hooking the cpp function in ahk that is reponsible for listing recently executed lines in the ListLines gui?
Why? Actually, only a few low-overhead lines of C++ (in Line::ExecUntil, iirc) are used as the backbone for ListLines. A pointer to each "semi-compiled" Line structure is stored in an array, which wraps around when it gets to the end. The GUI is separate - it merely generates human-readable text from the array of pointers. I can't think what this would be useful for other than step-through debugging, which AutoHotkey_L provides.
Back to top
View user's profile Send private message Visit poster's website
tinku99



Joined: 03 Aug 2007
Posts: 399
Location: Houston, TX

PostPosted: Fri Jan 16, 2009 6:10 pm    Post subject: previous post Reply with quote

Quote:
How so? I'd guess that dynamic function calls are slower, and would not be as "modular."
.
This is said without explanation in the emacs manual.
http://www.gnu.org/software/emacs/elisp/html_node/Advising-Functions.html . I agree with your two reasons + wanting to avoid action at a distance if you can avoid it. Same reason as wanting fewer global variables. Some globals (and advice) are useful, too many are hard to maintain.
Quote:

I can't think what this would be useful for other than step-through debugging, which AutoHotkey_L provides.

Never mind about hooking the ListLines function.
I will play with the AutoHotkey_L step debugging.

Quote:
code.ahk can generate basically any (script) code, and insert it anywhere (in script).

I will try to create a function using code.ahk as follows:
Code:

extend(func, line)
{
; insert line before at the beginning or end of the {}
; this is advising a function with a command, rather than a function call
}

However, I would prefer to just have the builtin commands have analog builtin functions. Is it too hard to translate ahk source for this conversion?


Quote:

You would need to search for each gosub or return line, though.

I would rather not do too much compile time processing and turn ahk in to a preprocessor for itself... Its much cleaner to just hook functions which is runtime and much more useful.
The same reasons for hooking functions apply to hooking commands.
By the way, I'd rather hook commands other than gosub and return... Such low level control flow hooks are probably best left to an outside debugger.

Quote:

Beyond debugging, what are its uses?

It would allow more modularity and easier extension of existing scripts without modifying the existing code.
It is easy to change existing functions, because you can rewrite them in one place. It is impossible to change behavior of program based on commands, without changing code everywhere, without advice.

One example is for logging or user monitoring (if you accept it is different from debugging).
If I wanted to show my customer, how many manual clicks I have saved them, It would be easier to hook - click, send, mousemove etc. - then to modify all my existing code.

Another example is profiling (again if you accept it is different from debugging).

This could be used to limit functionality of scripts.
ex1. certain commands (static, ControlGetPos) can only be used so many times in the demo version of a script.
ex2. to reduce runaway resource consumption from scripts, limit frequence of high overhead command calls like fileappend, looping constructs. This is slightly different from debugging. Think of it as giving training wheels to some of your scripts. They can't get too out of control because you accidently put in an infinite loop, if you just advise a deref of A_Index for example ( I know this is totally different from hooking commands, and likely must be done at the cpp level).

This can allow graceful exit rather than crashes on your client computers, before the os does its version of a graceful exit, or worse your script does some harm...
Back to top
View user's profile Send private message Send e-mail Visit poster's website
Lexikos



Joined: 17 Oct 2006
Posts: 4726
Location: Australia

PostPosted: Fri Jan 16, 2009 7:04 pm    Post subject: Re: previous post Reply with quote

tinku99 wrote:
This is said without explanation in the emacs manual.
You're making very little sense. Surely emacs does not support the style recommended by majkinetor. I'd guess it probably doesn't make a distinction between user-defined or built-in functions, either.
Quote:
However, I would prefer to just have the builtin commands have analog builtin functions. Is it too hard to translate ahk source for this conversion?
Absolutely.
Quote:
Its much cleaner to just hook functions which is runtime and much more useful.
That is not possible with commands, hence my suggestion.
Quote:
It would allow more modularity and easier extension of existing scripts without modifying the existing code.
I was referring specifically to gosub/return. Never mind.
Quote:
It is easy to change existing functions, because you can rewrite them in one place. It is impossible to change behavior of program based on commands, without changing code everywhere, without advice.
Titan long ago wrote a script wrapping each command in a function. Using those in place of the actual commands, it would be possible.
Quote:
Another example is profiling
No, that is the same example, worded differently. Razz
Quote:
This can allow graceful exit rather than crashes on your client computers,
How so?
Back to top
View user's profile Send private message Visit poster's website
tinku99



Joined: 03 Aug 2007
Posts: 399
Location: Houston, TX

PostPosted: Sat Jan 17, 2009 1:28 am    Post subject: failing registercallback call to command from ahk cpp Reply with quote

I tried hooking a funciton, and it fails:
Code:

x := RegisterCallback("f")
MouseMove , 4, 5
return

f()
{
msgbox, test
}

keyboard_mouse.cpp
Code:

extern UINT __stdcall RegisterCallbackCStub(UINT *params, char *address);
MouseMove(..){
UINT temp = 3876798686;
RegisterCallbackCStub(&temp, "test");
...}


crashes in RegisterCallbackCStub on the following line:
Code:

if (func.mInstances > 0) // Backup is needed.
      if (!Var::BackupFunctionVars(func, var_backup, var_backup_count)) // Out of memory.
         return DEFAULT_CB_RETURN_VALUE; // Since out-of-memory is so rare, it seems justifiable not to have any error reporting and instead just avoid calling the function.


Edit:
I am basically trying to call a callback from the executable. Is this possible?
Back to top
View user's profile Send private message Send e-mail Visit poster's website
Lexikos



Joined: 17 Oct 2006
Posts: 4726
Location: Australia

PostPosted: Sat Jan 17, 2009 2:30 am    Post subject: Reply with quote

You misunderstand how RegisterCallbackCStub works:
  • params is a pointer to the first parameter for the callback, ordinarily a location on the stack. If the callback has no parameters, "params" is not used. This will not necessarily be at the same address each time you run the script.
  • address is not a string, but a pointer near the callback. Essentially, it is RegisterCallback(...)+5. Callbacks are dynamically allocated, so also will not necessarily be at the same address each time you run the script.
You could create an RCCallbackFunc structure, fill in the func field, then pass ((char*)cb)+5, but I would instead write code based on the contents of RegisterCallbackCStub or OnMessage to call the script function directly. This gives you control over how values are assigned to the parameters. A script function can be resolved by calling g_script.FindFunc.

Rather than doing this from each command (e.g. MouseMove), it would be more effective to hook Line::ExecUntil or Line::Perform. I believe g_act[line->mActionType].Name will retrieve a pointer to the command name in most cases, where line is instead this in Line::Perform. Control flow statements are handled directly by Line::ExecUntil, so hooking Line::Perform would not work for them.
Back to top
View user's profile Send private message Visit poster's website
tinku99



Joined: 03 Aug 2007
Posts: 399
Location: Houston, TX

PostPosted: Sun Jan 18, 2009 8:47 am    Post subject: solved: hooking commands Reply with quote

// adapted from IPC.ahk by Majkinetor
Code:

// in script.cpp
// added
LRESULT sendAhk(char *msg, char *target)
{
   COPYDATASTRUCT cd;
   int port = 10000;
    cd.dwData = port;
         cd.cbData = sizeof(msg) + 1; 
         cd.lpData = msg;
        WPARAM id = 951753;  // for security
       HWND hHost = FindWindow(NULL, target);
   return SendMessage(hHost, WM_COPYDATA, id, (LPARAM)&cd); 
}

static int NavHook;

__forceinline ResultType Line::Perform()
{...
...
// added
char *Navmsg = g_act[mActionType].Name;

// seems to work without this test anyways, but i'm worried about loops because of advice functions
if (NavHook != 1)
{
NavHook = 1;
sendAhk(Navmsg, g_script.mFileName);  //Naveen IPC between ahkcpp and ahk
NavHook = 0;
}
....case ACT_...
}


now, I can call whatever function i want dynamically after onmessage() on the .ahk side. Very Happy
Back to top
View user's profile Send private message Send e-mail Visit poster's website
Lexikos



Joined: 17 Oct 2006
Posts: 4726
Location: Australia

PostPosted: Sun Jan 18, 2009 12:47 pm    Post subject: Reply with quote

Confused

As I mentioned, you can call the script function directly, with code based on the contents of OnMessage. You may call any function with any number of parameters, passing numeric or non-numeric values. Using OnMessage and SendMessage to call a script function from within AutoHotkey itself is rediculous.

Yesterday I was writing an example of __mcode and __mcodeptr to put in the LowLevel documentation (for the next release), and realised I'd accidentally created something relevant to this thread. I decided not to use the example, but thought I'd polish it up and post it here:
Code:
LowLevel_init()
AdviseBIF("VarSetCapacity","Debugger")
VarSetCapacity( v:="foo", -1 )

Debugger(aResultToken, aParam, aParamCount, aName)
{
    v := 1 + 1
    ; TODO: More reliable way to determine if we are running on the current beta
    ;       of AutoHotkey. The following works by checking if the integer we set
    ;       was cached as binary; it won't work if SetFormat is used anywhere.
    OFFSET_VAR_NAME := NumGet(__getVar(v)+0) == 2 ? 24 : 16
   
    s := __str(aName)
    Loop % aParamCount
    {
        t := NumGet(aParam+A_Index*4-4)
        sym := NumGet(t+8)
        s .= "`n   [" A_Index "]: "
        if sym = 3 ; SYM_VAR
            s .= __str(NumGet(NumGet(t+0)+OFFSET_VAR_NAME)) " := "
        v := getTokenValue(t+0)
        if (sym=0 || sym=3 || (sym=4 && !NumGet(t+4)))
        {
            StringReplace, v, v, ", "", All
            v = "%v%"
        }
        s .= v
    }
    if A_EventInfo ; Show return value.
        s .= "`n   ret: " getTokenValue(aResultToken+0)
    MsgBox % s
}

AdviseBIF(advisee, advice)
{
    __mcode(advisee, ""
    . "8B442404"      ; mov   eax,dword ptr [esp+4]
    . "8B08"          ; mov   ecx,dword ptr [eax]
    . "51"            ; push  ecx
    . "FF742410"      ; push  dword ptr [esp+10h]
    . "FF742410"      ; push  dword ptr [esp+10h]
    . "50"            ; push  eax

    . "B9" mcodeptr(RegisterCallback(advice,"Cdecl",4,0))    ; mov   ecx, ...
    . "FFD1"          ; call  ecx
    . "B9" mcodeptr(NumGet(__findFunc(advisee)+4))    ; mov   ecx, ...
    . "FFD1"          ; call  ecx
    . "B9" mcodeptr(RegisterCallback(advice,"Cdecl",4,1))    ; mov   ecx, ...
    . "FFD1"          ; call  ecx

    . "83C410"        ; add   esp,10h
    . "C3")           ; ret
}

; The following section is necessary to simultaneously support the current release
; of LowLevel and a pending release, which is compatible only with the current beta
; of AutoHotkey. __getTokenValue and __mcodeptr are included in the pending release.
getTokenValue(token) {
    if _gtv:=__findFunc("__getTokenValue") {
        ; Copy that pseudo-built-in func to avoid going through this process again.
        this:=__getFuncUDF(A_ThisFunc)
        NumPut(NumGet(_gtv+4),this+4)
        NumPut(1, this+49, 0, "char")
    } else {    ; That function doesn't exist, so we must create it:
        __mcode(A_ThisFunc ; COMPILED FOR v1.0.47.06:
        , "8B4424088B008378080175298B008B50088B4C240483FA03750E8"
        . "B00C74108040000008B008901C38951088B1089118B4004894104C3")
    }
    return getTokenValue(token+0)
}
mcodeptr(p) {
    VarSetCapacity(buf, 20), DllCall("msvcrt\sprintf", "str", buf, "str", "%08X"
        , "int", p>>24&255 | (p>>16&255)<<8 | (p>>8&255)<<16 | (p&255)<<24, "cdecl")
    return buf
}
Debugger() is called before and after the built-in function; in this example, VarSetCapacity. It has access to the parameters of the built-in function, including the "result token" (return value). Supports advising only once per built-in function.

Edit: fixed typo, DebugBIF -> Debugger. See also the real DebugBIF.


Last edited by Lexikos on Sun Jan 25, 2009 6:35 pm; edited 1 time in total
Back to top
View user's profile Send private message Visit poster's website
majkinetor



Joined: 24 May 2006
Posts: 4250
Location: Belgrade

PostPosted: Mon Jan 19, 2009 3:14 pm    Post subject: Reply with quote

Great for logging and troubleshooting. Thx.
_________________
Back to top
View user's profile Send private message
Display posts from previous:   
Reply to topic    AutoHotkey Community Forum Index -> Wish List All times are GMT
Goto page 1, 2  Next
Page 1 of 2

 
Jump to:  
You can post new topics in this forum
You can reply to topics in this forum


Powered by phpBB © 2001, 2005 phpBB Group