Jump to content

Sky Slate Blueberry Blackcurrant Watermelon Strawberry Orange Banana Apple Emerald Chocolate
Photo

function hooks


  • Please log in to reply
18 replies to this topic

Poll: advice is best for end user programming (4 member(s) have cast votes)

advice is best for end user programming

  1. yes (3 votes [60.00%])

    Percentage of vote: 60.00%

  2. no (2 votes [40.00%])

    Percentage of vote: 40.00%

Vote Guests cannot vote
tinku99
  • Members
  • 560 posts
  • Last active: Feb 08 2015 12:54 AM
  • Joined: 03 Aug 2007
http://www.gnu.org/s...-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.
; 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
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
  }
 
}

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
}


majkinetor
  • Moderators
  • 4512 posts
  • Last active: May 20 2019 07:41 AM
  • Joined: 24 May 2006
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.
Posted Image

tinku99
  • Members
  • 560 posts
  • Last active: Feb 08 2015 12:54 AM
  • Joined: 03 Aug 2007
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/s...-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
		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]

Lexikos
  • Administrators
  • 9844 posts
  • AutoHotkey Foundation
  • Last active:
  • Joined: 17 Oct 2006
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.
myFunc = myFunc
;...
myFunc = myFuncAdvice
;...
%myFunc%()
;...
myFuncAdvice() {
    ;...
    return myFunc()
}
Got the idea?

AHK is all about hooking: user interfaces to 3rd party applications, windows api hooks.

Neither point relates to hooking AutoHotkey script functions.

Lexikos
  • Administrators
  • 9844 posts
  • AutoHotkey Foundation
  • Last active:
  • Joined: 17 Oct 2006
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.

tinku99
  • Members
  • 560 posts
  • Last active: Feb 08 2015 12:54 AM
  • Joined: 03 Aug 2007
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?

tinku99
  • Members
  • 560 posts
  • Last active: Feb 08 2015 12:54 AM
  • Joined: 03 Aug 2007
It works for builtins if you override the builtin first:

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.

Lexikos
  • Administrators
  • 9844 posts
  • AutoHotkey Foundation
  • Last active:
  • Joined: 17 Oct 2006

It works for builtins if you override the builtin first:

No, it works for user-defined functions which are obscuring built-in functions. ;)

"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:
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:
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. :roll:

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."

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?

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.

tinku99
  • Members
  • 560 posts
  • Last active: Feb 08 2015 12:54 AM
  • Joined: 03 Aug 2007

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.
<!-- m -->http://www.gnu.org/s... ... tions.html<!-- m --> . 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.

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.

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:
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?


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.

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...

Lexikos
  • Administrators
  • 9844 posts
  • AutoHotkey Foundation
  • Last active:
  • Joined: 17 Oct 2006

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.

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.

Its much cleaner to just hook functions which is runtime and much more useful.

That is not possible with commands, hence my suggestion.

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.

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.

Another example is profiling

No, that is the same example, worded differently. :p

This can allow graceful exit rather than crashes on your client computers,

How so?

tinku99
  • Members
  • 560 posts
  • Last active: Feb 08 2015 12:54 AM
  • Joined: 03 Aug 2007
I tried hooking a funciton, and it fails:
x := RegisterCallback("f")
MouseMove , 4, 5
return

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

crashes in RegisterCallbackCStub on the following line:
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?

Lexikos
  • Administrators
  • 9844 posts
  • AutoHotkey Foundation
  • Last active:
  • Joined: 17 Oct 2006
You misunderstand how RegisterCallbackCStub works:
[*:dmx7g9sq]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.
[*:dmx7g9sq]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.

tinku99
  • Members
  • 560 posts
  • Last active: Feb 08 2015 12:54 AM
  • Joined: 03 Aug 2007
// adapted from IPC.ahk by Majkinetor
// 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. :D

Lexikos
  • Administrators
  • 9844 posts
  • AutoHotkey Foundation
  • Last active:
  • Joined: 17 Oct 2006
:?

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:
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.

majkinetor
  • Moderators
  • 4512 posts
  • Last active: May 20 2019 07:41 AM
  • Joined: 24 May 2006
Great for logging and troubleshooting. Thx.
Posted Image