Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

SendPipeMessage unreliable when used in a hotkey function #9

Open
evilC opened this issue Jun 14, 2017 · 9 comments

Comments

@evilC
Copy link

@evilC evilC commented Jun 14, 2017

using AutoHotkey.Interop;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;

namespace TestApp
{
    class Program
    {
        static void Main(string[] args)
        {
            //grab a copy of the AutoHotkey singleton instance
            var ahk = AutoHotkeyEngine.Instance;

            var ipcHandler = new Func<string, string>(fromAhk =>
            {
                Console.WriteLine("received message from ahk " + fromAhk);
                //System.Threading.Thread.Sleep(3000); //simulating lots of work
                return ".NET: I LIKE PIE!";
            });

            //the initalize pipes module only needs to be called once per application
            ahk.InitalizePipesModule(ipcHandler);

            ahk.ExecRaw(@"
            BindHotkey(hk, id){
                fn := Func(""HkEvent"").Bind(id, ""1"")
                hotkey, % hk, % fn

                fn := Func(""HkEvent"").Bind(id, ""0"")
                hotkey, % hk "" up"", % fn
            }

            HkEvent(id, state){
                SendPipeMessage(""HK EVENT! ID = "" id "", State = "" state)
            }

            ");

            ahk.ExecRaw(@"BindHotkey(""F12"", ""999"")");

            while (true)
            {
                Thread.Sleep(100);
            }
        }
    }
}

Sometimes a runtime error is thrown ("Functions cannot contain functions")
Sometimes none of the hotkeys work
Hotkeys NEVER fire as often as they should (Spam F12 and you do not see a console log for each press / release)
If you have a release hotkey, after about the 5th press/release, all hotkeys stop firing

@evilC evilC changed the title Function Objects do not seem to be supported for Hotkeys (Crashes) Intermittent behavior with boundfunc hotkeys Jun 14, 2017
@evilC

This comment has been minimized.

Copy link
Author

@evilC evilC commented Jun 14, 2017

It appears that the problem is to do with SendPipeMessage

            var ipcHandler = new Func<string, string>(fromAhk =>
            {
                Console.WriteLine("received message from ahk " + fromAhk);
                return ".NET: I LIKE PIE!";
            });

            ahk.ExecRaw(@"
                F11::
                    Tooltip, % ""HERE - "" A_TickCount
                    return
                
                F12::
                    SendPipeMessage(""HERE - "" A_TickCount)
                    return
            ");

The F11 hotkey displays a tooltip reliably on every press, but the F12 hotkey does not.

@evilC evilC changed the title Intermittent behavior with boundfunc hotkeys SendPipeMessage unreliable when used in a hotkey function Jun 14, 2017
@evilC

This comment has been minimized.

Copy link
Author

@evilC evilC commented Jun 14, 2017

The problem appears to be due to the fact that SendPipeMessage is a two-way communication:

SendPipeMessage(strMessage) {
	global A__PIPECLIENT
	A__PIPECLIENT.write(strMessage)
	sleep, 100
	A__PIPECLIENT_RESULT := A__PIPECLIENT.read()
	sleep, 100
	return A__PIPECLIENT_RESULT
}

Those sleep 100s clearly are causing the problem.

If I comment out everything after A__PIPECLIENT.write(strMessage) and ignore the return value, I get callbacks for all hotkeys.

All I actually want is one-way communication from the script to the host C# code (ie an Action instead of a Func) I tried to change the code in this way, but had no joy.

I have been chatting to HotkeyIt about this, and he reckons this could also be done with ObjShare, although he hasn't done C# before and I have not got a clue how this all works. We'll keep beavering away, but if you are about then I could really do with some help on this one.

@amazing-andrew

This comment has been minimized.

Copy link
Owner

@amazing-andrew amazing-andrew commented Jun 15, 2017

How does the program behave, if you just take the sleeps out ?

@evilC

This comment has been minimized.

Copy link
Author

@evilC evilC commented Jun 15, 2017

Yeah it seems OK if I take them out and don't try to do anything with a return value, but I don't know enough about it to know if it is safe to do so, or could be more performant without it.
HotkeyIt also thought it would be better to implement communication via ObjShare, but neither of use are really sure how to implement this in C# - any ideas?
https://github.com/HotKeyIt/ahkdll/blob/master/source/resources/reslib/ObjShare.ahk

@amazing-andrew

This comment has been minimized.

Copy link
Owner

@amazing-andrew amazing-andrew commented Jun 16, 2017

The CSharp code acts as a Server by hosting the Named Pipe, used for communication.
The AHK code acts as a client by making the connection only when it needs to send something.

Currently the named pipe server behaves like the following:

  • .NET : It opens the named pipe and waits for a connection to be made.
  • AHK : When you use SendPipeMessage in AHK, it makes a connection the pipe message, ahk sends the message as a string. AHK then waits in a loop for a response back from the named pipe.
  • .NET : the named pipe server receives the string, and then sends the string to the named pipe handler function. the function runs and returns a string. It sends the response string by writing the string response back into the named pipe. Only after a response has been sent back to the AHK client does the server start waiting for a new connection. This does mean that the named pipe server does not handle more than 1 connection at a time.
  • AHK : once it receives the response back it returns it as a result to the SendPipeMessage function.

Overall the .NET and AHK processes are waiting on each other before they continue, so they block each other's threads. The sleeps are probably put in by me for testing purposes. Sometimes I find AHK is more stable when you give it some time around important pieces of code.

If you find that your system handles the calls without the two sleeps well, I don't see an issue with removing them. I agree it may perform better.

Also I've looked at ObjShare, and please correct me if i'm wrong. But it looks like a way to share an AHK object between AHK Threads. I'm not sure how this can translate sending a message to the hosting environment and returning a result back from it. Do you have an example in any programming language of the ObjShare being used to call a function in the hosting environment, outside of AHK and then return the result back to AHK? I can look at this and perhaps find a way to replicate it in CSharp.

@evilC

This comment has been minimized.

Copy link
Author

@evilC evilC commented Jun 16, 2017

Not got time to read this all right now, will get back to it, but for now here is an example of inter-thread communication using a function object wrapped with ObjShare:

Main thread launching second thread, passing callback func object wrapped with ObjShare:
https://github.com/evilC/UCR/blob/master/Classes/Profile.ahk#L106

Child thread getting function object:
https://github.com/evilC/UCR/blob/master/Threads/ProfileInputThread.ahk#L14

Child thread using function object:
https://github.com/evilC/UCR/blob/master/Threads/ProfileInputThread.ahk#L195

@jaryn-kubik

This comment has been minimized.

Copy link

@jaryn-kubik jaryn-kubik commented Jul 11, 2017

So I've been toying around with this and the SendPipeMessage really is kinda unreliable.
However from what I found out, since the ahkdll runs in the same process, you don't even need any sort of IPC for communicating, you can just directly call a C# delegate with DllCall.

Example:

void AHKCallback(string s)
{
   Console.WriteLine(s);
}

[UnmanagedFunctionPointer(CallingConvention.StdCall)]
delegate void AHKDelegate([MarshalAs(UnmanagedType.LPWStr)]string s);

AHKDelegate ahkDelegate = AHKCallback;

void writeCrapInConsoleOnPressingA()
{
   IntPtr ptr = Marshal.GetFunctionPointerForDelegate(ahkDelegate);
   var ahk = AutoHotkeyEngine.Instance;
   ahk.ExecRaw(@"a::
      DllCall(" + ptr + @", ""Str"", ""whatever you wanna pass to AHKCallback"")
      return");
}
@amazing-andrew

This comment has been minimized.

Copy link
Owner

@amazing-andrew amazing-andrew commented Jul 11, 2017

I like this method a lot. Should be simple and less hassles implementing this type communication between the two worlds.

@evilC

This comment has been minimized.

Copy link
Author

@evilC evilC commented Jul 16, 2017

So if we switched to this method, does that mean that all the pipes code could be removed?
I need to have lots of copies of AHK running - up to a minimum of about 6, but ideally as many as the user is willing to sacrifice memory for.
Why so many? Because I need one for "Bind Mode" (Has hotkeys to all keyboard and mouse buttons declared) which is used to detect which key the end-user wishes to use, then one copy of AHK per user profile.
This way, it is easy to turn on/off sets of hotkeys - you just Suspend/Unsuspend the copies of AHK as appropriate.
In order to handle "Shifted" inputs, my system uses child profiles - so these must be available pretty much instantly, and could contain hundreds of hotkeys - so in this case they need to be loaded and suspended so they can be activated at very short notice.
I managed to alter the code to not be a singleton, and in order to do so had to comment out a bunch of stuff to do with pipes.
Am hoping maybe switching to this method will also make this possible without having to have my own custom fork.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
3 participants
You can’t perform that action at this time.