AHK-Python Process Communication

Get help with using AutoHotkey (v1.1 and older) and its commands and hotkeys
connorwb93
Posts: 12
Joined: 19 Mar 2017, 22:34

AHK-Python Process Communication

20 Mar 2017, 01:04

My question is about COM, but for some background: I'm trying to write a general autocorrect program for Windows. I want to write the actual engine in a more traditional language (e.g. Python) because it is somewhat complicated and I am also unsure of how efficiently AHK's associative arrays are implemented, and performance is a concern.

So, ideally, there will be an AHK script running that monitors words I type and sends them to the Python script, which potentially returns corrections for AHK to apply.

I am running into trouble setting up the COM communication between the two processes, though. I have been trying to use this repo to do so: https://github.com/tinku99/ahkpy. (I found my way there from this thread: https://autohotkey.com/board/topic/6178 ... ntry394589.) I stopped trying to implement the test of basic communication once I got to where it was writing to my registry (this function, which is to register the COM objects or their functions https://github.com/tinku99/ahkpy/blob/m ... terids.ahk). I am completely new to using COM, and haven't messed with my computer's registry much at all, so I was a little sketched out by RegWrite.

So, my questions are:
1. Is there some much nicer way to do this that I'm missing? If not...
2. Is writing to my registry something that makes sense here..?
3. I don't see anywhere that this gets cleaned up after the fact. Does this clean itself, or is it going to leave junk there?
4. Are there any other resources for how to accomplish this that I should be aware of?
Thanks!
User avatar
Masonjar13
Posts: 1555
Joined: 20 Jul 2014, 10:16
Location: Не Россия
Contact:

Re: AHK-Python Process Communication

20 Mar 2017, 02:01

OS: Windows 10 Pro | Editor: Notepad++
My Personal Function Library | Old Build - New Build
lexikos
Posts: 9554
Joined: 30 Sep 2013, 04:07
Contact:

Re: AHK-Python Process Communication

20 Mar 2017, 02:06

ComDispatch is obsolete, as current versions of AutoHotkey support passing objects to COM APIs natively.

ObjRegisterActive can be used to create a COM server (that's its purpose) which Python should be able to utilise with whatever function it has equivalent to ComObjActive/GetActiveObject.

You may not need to modify the registry. If the Python function will accept a CLSID, just pass it the GUID you create for use with ObjRegisterActive.
connorwb93
Posts: 12
Joined: 19 Mar 2017, 22:34

Re: AHK-Python Process Communication

20 Mar 2017, 02:50

Masonjar13 - I'm familiar with that, but got frustrated with it and think that a more thorough version could work a lot better - the idea being to take a string that isn't in a dictionary and return any dictionary strings that are close to it and pick the best one. It requires more complicated data structures to do efficiently, though, as you can probably guess.

Lexikos - thanks a lot (also, your work on AHK is awesome). Having read about this a little more, if the AHK script is running actively and calling the Python script's interface (but not providing services of its own), I don't think I even need to register it. Cool.
User avatar
Masonjar13
Posts: 1555
Joined: 20 Jul 2014, 10:16
Location: Не Россия
Contact:

Re: AHK-Python Process Communication

20 Mar 2017, 03:16

For future reference, so you consistently get the answers your looking for, you should state that other implementations aren't what you're looking for, if applicable. That's usually a go-to thing for those helping, as to not have to re-write code if not necessary.

Also, you should be able to run an AHK script in a separate thread under the Python script, to have only a single process running instead of two. This is done by sending a script (file or string) through AutoHotkey.dll. Completely optional in this scenario. See AHK_H (documentation is included in the download) if you're interested in that. Note that syntax is slightly different and may have to be changed accordingly. Should throw you a nice little error in such a case.

Welcome to the forums, by the way :)
OS: Windows 10 Pro | Editor: Notepad++
My Personal Function Library | Old Build - New Build
connorwb93
Posts: 12
Joined: 19 Mar 2017, 22:34

Re: AHK-Python Process Communication

21 Mar 2017, 02:41

Masonjar13 wrote:For future reference, so you consistently get the answers your looking for, you should state that other implementations aren't what you're looking for, if applicable. That's usually a go-to thing for those helping, as to not have to re-write code if not necessary.

Also, you should be able to run an AHK script in a separate thread under the Python script, to have only a single process running instead of two. This is done by sending a script (file or string) through AutoHotkey.dll. Completely optional in this scenario. See AHK_H (documentation is included in the download) if you're interested in that. Note that syntax is slightly different and may have to be changed accordingly. Should throw you a nice little error in such a case.

Welcome to the forums, by the way :)
Thanks!

I'm not too worried about having two processes, but I am a little worried about how AHK will handle receiving more keystrokes while it waits for the Python end to reply.. but haven't been able to test that yet, as I'm still trying to figure COM stuff out.
connorwb93
Posts: 12
Joined: 19 Mar 2017, 22:34

Re: AHK-Python Process Communication

21 Mar 2017, 02:57

Ok, new question:

After a lot of thrashing around, I have a working Python COM server. I tested it with a Python client.

Now I'm trying to test it with an AHK client, simply calling ComObjCreate() and pinging it. I ran into this error on running the script: "The specified module could not be found" (pointing at the ComObjCreate() line). This happens with both the progID and the CLSID. It's being thrown by Windows, but without a stack trace or something I'm not sure what's causing the issue, and I haven't been able to find much documentation detail on the inner workings of ComObjCreate (and also I can't find "ComObjCreate" on the AutoHotkey_L github page, so I'm confused by that).

So, here's the code I'm trying to run:

Code: Select all

ts := ComObjCreate("Python.TestServer")
if (ts = "") {
	msgbox % "Error creating COM object."
	Exit
}
else {
	msgbox % ts
}

p1 := ts.ping()
p2 := ts.ping()

msgbox % "p1 = " p1 "`np2 = " p2
Any ideas?
User avatar
Masonjar13
Posts: 1555
Joined: 20 Jul 2014, 10:16
Location: Не Россия
Contact:

Re: AHK-Python Process Communication

21 Mar 2017, 03:06

If you click the command in your code box, it will open up the documentation page for it. Official, up-to-date documentation can be found here (link is at the top of the forum page: "Commands").

My best guess, because I'm unsure of how COM works in this particular scenario, is that you'll need to use ComObjActive().
OS: Windows 10 Pro | Editor: Notepad++
My Personal Function Library | Old Build - New Build
connorwb93
Posts: 12
Joined: 19 Mar 2017, 22:34

Re: AHK-Python Process Communication

21 Mar 2017, 19:18

I have been looking at the documentation, but don't know how to debug it with the given info.

ComObjActive() throws an "operation unavailable" error. All the examples in this thread https://autohotkey.com/board/topic/5698 ... v11/page-1 just use create, so it seems like I should just be able to do that?

For reference, the Python client that works is simply this. the Dispatch() call seems analogous to how ComObjCreate() is used, but I am not positive it isn't doing anything else

Code: Select all

import win32com.client

s = win32com.client.Dispatch("Python.TestServer")

for i in range(3):
	var = s.ping(1)
	print var
lexikos
Posts: 9554
Joined: 30 Sep 2013, 04:07
Contact:

Re: AHK-Python Process Communication

22 Mar 2017, 02:42

If the Python COM server is registered as in-process (i.e. via a dll), you must be able to load it into the AutoHotkey process. You can't do that if the dll is 32-bit and the process is 64-bit, or vice versa.

If the Python COM server is registered as out-of-process (i.e. via an exe), I don't think it matters. However, 32-bit processes probably look for the class in a different section of the registry than 64-bit processes. You must make sure it is registered correctly.

If you use ObjRegisterActive like I suggested, you won't have these problems. A 32-bit process can retrieve an object registered by a 64-bit process and vice versa. Registration can be only a function-call, with no registry changes. If you want Python to be the COM server, it just needs to use the RegisterActiveObject Win32 function.
connorwb93
Posts: 12
Joined: 19 Mar 2017, 22:34

Re: AHK-Python Process Communication

23 Mar 2017, 19:58

That worked! I was assuming that 64-bit Python would make a 64-bit COM server, but it does not. Thanks.

I do want the Python program to be the server. Am I misunderstanding the purpose of ObjRegisterActive, or does it not make sense to use it in that case?


EDIT:
in the off chance someone else needs to do something similar, here is how I did this:
pywin32 is the python library for this. Its .chm help file has a quickstart guide for both server and client sides, which are very helpful.

My server code for a test server is:

Code: Select all

import time
import pythoncom
import win32com.server.register
import win32com.server.exception
import admin

class BasicServer:

	# note - these are comma-delimited
	# list of all method names exposed to COM
	_public_methods_ = ["ping"]
	
	# list of all attribute names exposed to COM
	_public_attrs_ = []
	
	# list of all read-only exposed attributes
	_readonly_attrs_ = ["count"]		
	
	# this server's CLS ID
	_reg_clsid_ = "{E88C95B9-B6BA-4365-BC3E-42C07E33B944}"
	
	# this server's (user-friendly) program ID
	_reg_progid_ = "Python.TestServer"
	
	# optional description
	_reg_desc_ = "Test COM server"
	
	
	def __init__(self):
		
		self.count = 0
		
	def ping(self, wait=""):
	
		if wait != "":
			waitCount = int(str(wait))
			time.sleep(waitCount)
		
		self.count = self.count + 1
				
		return self.count
		
	@staticmethod
	def reg():
		
		win32com.server.register.UseCommandLine(BasicServer)
		
	@staticmethod
	def unreg():
		
		if not admin.isUserAdmin():
			admin.runAsAdmin()
		win32com.server.register.UnregisterServer(BasicServer._reg_clsid_, BasicServer._reg_progid_)

			
if __name__ == "__main__":

	import sys
	if len(sys.argv) < 2:
		print "Error: need to supply arg (""--register"" or ""--unregister"")"
		sys.exit(1)
	elif sys.argv[1] == "--register":
		BasicServer.reg()
	elif sys.argv[1] == "--unregister":
		print "Starting to unregister..."	
		BasicServer.unreg()
		print "Unregistered COM server."
	else:
		print "Error: arg not recognized"
	
The client AHK code simply uses the native object syntax:

Code: Select all

ts := ComObjCreate("Python.TestServer")
msgbox % ts.ping()
lexikos
Posts: 9554
Joined: 30 Sep 2013, 04:07
Contact:

Re: AHK-Python Process Communication

23 Mar 2017, 22:47

connorwb93 wrote:I do want the Python program to be the server. Am I misunderstanding the purpose of ObjRegisterActive,
ObjRegisterActive() would effectively make the AutoHotkey script a COM server. As I said, if you want Python to be the server, you can use exactly the same techniques but whichever functions are appropriate. The underlying Win32 functions are RegisterActiveObject and GetActiveObject. I have no idea what wrappers exist in the Python win32com module.

The difference is that instead of registering into the registry and having the COM server (a new Python process) started (indirectly) by ComObjCreate, the active object is registered only in memory by a process which is already running, and that same object (or a proxy to it) is retrieved by ComObjActive.
connorwb93
Posts: 12
Joined: 19 Mar 2017, 22:34

Re: AHK-Python Process Communication

02 Apr 2017, 04:47

(thanks again for the answers)

This project is much farther along now (!) - I'll post the rest of it at some point if it gets to a semi-useful state.
At this point I have:
- AHK tracking words I type
- AHK submitting words to the python script for evaluation
- the python script returns the best dictionary word that is close to the submitted word (or null if the word is in the dictionary). It uses a prefix trie
- AHK replaces my typed word if necessary.

The challenge right now is implementing the AHK side so that it doesn't get messed up by continuous typing, and I'd appreciate any feedback anyone has on it. The problem is that during the word replacement generation + overwrite process, if I continue typing at a rapid pace (during the fraction of a second before it completes) the extra hotkeys mess up the script. The python script takes some amount of time (around 55 ms on average, but haven't timed it across the COM interface yet) to finish running, and the AHK script also takes some time - probably more - to backspace x times and write the new word.

So, is this fixable, and how should I go about it? I hoped Critical would be exactly what I wanted to buffer hotkeys, but it didn't seem to do anything - am I using it wrong? Any sort of buffering solution would be ideal, I think. Should I be looking at AutoHotkey_H or anything else? I could get the python script to run a lot faster if I worked on it and/or rewrote it in a faster language, but I don't expect that to really be my problem at this point?

Here is what I am currently trying to do:

Code: Select all

class TypoBuilder {
	__New() {
		this.stack := ""
		this.temp_disabled := False
		
		this.ac := ComObjCreate("Python.AutoCorrectServer")
		
	}
	
	addLetter(char) {
		this.stack := this.stack . char
	}
	
	wipe() {
		this.stack := ""
		this.temp_disabled := False
	}
	
	tempDisable() {
		this.temp_disabled := True
	}
	
	execute(end_char) {
		if (this.temp_disabled = True) {
			this.wipe()
		} else if (this.stack != "") {
			typo := this.stack
			prev_str_len := StrLen(typo)
			new_word := this.ac.query(typo)
			if (new_word != "") {
				Loop, %prev_str_len% {
					send {BS} ; backspace
				}
				send % new_word
			}
			this.wipe()
		}
		send % end_char
	}
}	
TypoBuilder stores the built-up strings in its stack variable. For each letter key, there is a hotkey like so:

Code: Select all

~a::
	builder.addLetter("a")
	return
~b::
	builder.addLetter("b")
	return
.
.
.

Most "ending chars" like space signify the end of a word and actually trigger the python script to run by calling execute:

Code: Select all

$Space::
	;Critical
	builder.execute(" ")
	return
.
.
.
So.. how do I buffer keys I type while execute() is running?
lexikos
Posts: 9554
Joined: 30 Sep 2013, 04:07
Contact:

Re: AHK-Python Process Communication

02 Apr 2017, 06:15

Why does it get messed up by continuous typing? Is it because the user might type some characters before the script has a chance to backspace the old word?

I suppose the only way around that would be to block what the user is typing (at least while you're processing the last word). You can use a hotkey without tilde (a:: instead of ~a::), either by having an extra set of hotkeys under #If or by removing/applying tilde dynamically with the Hotkey command. The hotkey could buffer whatever the user typed without actually letting it get through to the active window (yet).
connorwb93
Posts: 12
Joined: 19 Mar 2017, 22:34

Re: AHK-Python Process Communication

03 Apr 2017, 00:48

I think the backspacing mixing with user keyboard input is the biggest problem, but it also seems like it might be actually modifying what is sent to the server somehow (like rapid keypresses after hitting space are fowling up the word before it is sent), but that is probably an unrelated issue.

I could block input while the previous word is being processed, but I'm not sure how disruptive that would be. I might try separating out the backspace/replacement part (as opposed to word processing) and only blocking input during it. I would have to track if other letters had been typed during that time too.

Is sending backspace in a loop as fast as sending e.g. {BS 5}? It seemed to be from eyeballing it, and I couldn't figure out how to use {BS x} to send a variable number of presses. The faster I can perform the input the less disruptive it will be, obviously.
lexikos
Posts: 9554
Joined: 30 Sep 2013, 04:07
Contact:

Re: AHK-Python Process Communication

03 Apr 2017, 02:45

A loop should never be faster than sending {BS 5}. It may be as fast. If SetKeyDelay is in effect, the delay is probably much larger than any loop overhead.

If you're using SendInput (and it hasn't reverted to SendEvent) {BS 5} should send a sequence of keystrokes which cannot be interrupted (I think the system buffers keyboard events while it is running). If you use a loop, that means individual Send calls, which means they can be interrupted.
I couldn't figure out how to use {BS x} to send a variable number of presses.
Just use a variable... ;)

Code: Select all

x := 5
Send {BS %x%}
connorwb93
Posts: 12
Joined: 19 Mar 2017, 22:34

Re: AHK-Python Process Communication

04 Apr 2017, 06:25

The percent mark syntax strikes again... thanks
MrDoge
Posts: 151
Joined: 27 Apr 2020, 21:29

Re: AHK-Python Process Communication

24 Jan 2021, 16:46

@connorwb93 thank you for the COM server
@lexicos I didn't understand, did you mean that there's a more efficient way to achieve this ?


I wrote a tutorial at Stack Overflow, hope it helps someone
https://stackoverflow.com/questions/65780086/how-to-program-hotstrings-in-python-like-in-autohotkey#65783573
  • I had to edit the script a bit to make it work
  • how to make it work with AutoHotkeyU64.exe

Return to “Ask for Help (v1)”

Who is online

Users browsing this forum: jaka1 and 143 guests