Certain COM calls cannot be made from a HotIf condition

Report problems with documented functionality
geek
Posts: 1052
Joined: 02 Oct 2013, 22:13
Location: GeekDude
Contact:

Certain COM calls cannot be made from a HotIf condition

28 Feb 2024, 11:39

Doing certain COM calls in HotIf presents an error Error: 0x8001010D - An outgoing call cannot be made since the application is dispatching an input-synchronous call.

This code reproduces the issue:

Code: Select all

#Requires AutoHotkey v2.0
Persistent
excel := ComObject("Excel.Application")
excel.Visible := true
HotIf((*) => excel.Visible)
Hotkey("a", (key) => MsgBox(key))
My intention had been to write my HotIf callback in Python and have it be executed directly over IDispatch, but an otherwise perfectly callable IDispatch function hosted by in-process Python (using AutoHotkey.dll and CoMarshalInterThreadInterfaceInStream), or even just an out-of-process AHK script (using LresultFromObject) throws when it is called from the context of HotIf.

I noted during testing, HotIf conditions specified in JavaScript through the ahk-jk project work perfectly as expected. I don't understand all of the code in the integration layer, but I suspect it's to do with how AHK implements JsRT rather than working by IDispatch directly.

This is the original Python code I am trying to make work, using thqby's AutoHotkey.dll. You'll note, hotkey a works great but hotkey b triggers an exception dialog.

Code: Select all

import ctypes
import pythoncom
import win32com.client
import win32com.server.policy

class AhkFuncObjectProxy(win32com.server.policy.DynamicPolicy):
    _com_interfaces_ = [pythoncom.IID_IDispatch]
    def __init__(self, target):
        self._wrap_(self)
        self.target = target
    def _dynamic_(self, name, lcid, wFlags, args):
        from inspect import signature
        if name == "MinParams" and (wFlags & pythoncom.DISPATCH_PROPERTYGET):
            return len(signature(self.target).parameters)
        if name == "MaxParams" and (wFlags & pythoncom.DISPATCH_PROPERTYGET):
            return len(signature(self.target).parameters)
        if name == "_value_" and (wFlags & pythoncom.DISPATCH_METHOD):
            return self.target(*args)
        return 0

o = ctypes.CDLL("./AutoHotkey64.dll")
o.ahkFunction.restype = ctypes.c_wchar_p
thread = o.NewThread("""
Persistent
NumPut("Int64", 0x20400, "Int64", 0x46000000000000c0, IID_IDispatch := Buffer(16))
marshal(name) => (DllCall("ole32\CoMarshalInterThreadInterfaceInStream", "ptr", IID_IDispatch, "ptr", ObjPtr(%name%), "ptr*", &pstm := 0, "int"), pstm)
""", 0, 0)
def retrieve(name):
    pstream = o.ahkFunction("marshal", name, 0, 0, 0, 0, 0, 0, 0, 0, 0, thread)
    stream = pythoncom.ObjectFromAddress(int(pstream), pythoncom.IID_IStream)
    dispatch = pythoncom.CoGetInterfaceAndReleaseStream(stream, pythoncom.IID_IDispatch)
    return win32com.client.__WrapDispatch(dispatch)

def ahkFunction(func):
    return pythoncom.WrapObject(
        AhkFuncObjectProxy(func),
        pythoncom.IID_IDispatch,
        pythoncom.IID_IDispatch
    )

ahk_Hotkey = retrieve("Hotkey")
ahk_HotIf = retrieve("HotIf")

def hotkey(keyname):
    def hotkey_decorator(func):
        ahk_Hotkey(keyname, ahkFunction(func))
        return func
    return hotkey_decorator

@hotkey('a')
def onA(name):
    print('Handling', name)

ahk_HotIf(ahkFunction(lambda name: False))

@hotkey('b')
def onB(name):
    print('Handling', name)

while True:
    pythoncom.PumpWaitingMessages()
And here is a (roughly) equivalent AHK script

Code: Select all

#Requires AutoHotkey v2.0
Persistent

Condition(name) => true

NumPut("Int64", 0x20400, "Int64", 0x46000000000000c0, IID_IDispatch := Buffer(16))
lResult := DllCall("OleAcc\LresultFromObject", "Ptr", IID_IDispatch, "Ptr", 0, "Ptr", ObjPtr(Condition), "Ptr")

exec := ComObject("WScript.Shell").Exec('"' A_AhkPath '" * ' lResult)
exec.StdIn.Write "
(
NumPut("Int64", 0x20400, "Int64", 0x46000000000000c0, IID_IDispatch := Buffer(16))
DllCall("oleacc\ObjectFromLresult", "Ptr", A_Args[1], "Ptr", IID_IDispatch, "Ptr", 0, "Ptr*", &pObj := 0)
condition := ComValue(9, pObj)
HotIf(condition)
HotKey("a", (key) => MsgBox(key))
)"
exec.StdIn.Close
User avatar
thqby
Posts: 417
Joined: 16 Apr 2021, 11:18
Contact:

Re: Certain COM calls cannot be made from a HotIf condition

28 Feb 2024, 20:46

lexikos wrote:
05 May 2018, 00:38
0x8001010D means RPC_E_CANTCALLOUT_ININPUTSYNCCALL: "An outgoing call cannot be made since the application is dispatching an input-synchronous call."

The "input-synchronous call" is from the keyboard/mouse hook thread to the main thread, asking it to evaluate the #if expression.

You can't do it, so just don't.
You can use pyahk to run both ahk and py in the same thread.

Code: Select all

from ctypes import c_wchar_p
import os
import sys
current_dir = os.path.dirname(os.path.abspath(__file__))
sys.path.append(current_dir)
from pyahk import *

AhkApi.initialize()
AhkApi.addScript('''
; show tray icon
A_IconHidden:=0
F4::ExitApp
''')

from pyahk import HotIf, Hotkey

def onf6(key):
	print(key)

@WINFUNCTYPE(None, c_wchar_p)
def onf7(key):
	print(key)

Hotkey('F6', onf6)
HotIf(lambda key: False)
Hotkey('F7', onf7)
AhkApi.pumpMessages()
lexikos
Posts: 9632
Joined: 30 Sep 2013, 04:07
Contact:

Re: Certain COM calls cannot be made from a HotIf condition

29 Feb 2024, 18:55

"Input-synchronous calls" are made by calling into a COM proxy object. A proxy object is responsible for marshalling calls across threads or processes. When you have a pointer to the original object and not a proxy, a COM method call is no different to any other virtual function call; reentry and recursion are not restricted.

(Proxy objects (implemented by COM itself) are not to be confused with AutoHotkey's COM wrapper objects, which just adapt IDispatch to the internal AutoHotkey interface.)
geek
Posts: 1052
Joined: 02 Oct 2013, 22:13
Location: GeekDude
Contact:

Re: Certain COM calls cannot be made from a HotIf condition

03 Mar 2024, 13:06

My confusion has mostly stemmed from why a COM proxy call would be forbidden in this specific circumstance. In any case, I was able to work around the issue by providing an stdcall style callback (using Python's equivalent of CallbackCreate) rather than an IDispatch callback, creating a BoundFunc as DllCall.Bind(c_callback, "Str") to provide to HotIf.

That said, thqby's pyahk implementation is clearly a much better way forward for this project. Thank you, thqby

Return to “Bug Reports”

Who is online

Users browsing this forum: No registered users and 22 guests