import ctypes
from ctypes.wintypes import (
BOOL, DWORD, HANDLE, HMODULE, LANGID, LPCWSTR, ULONG, USHORT)
from pathlib import Path
import sys
from time import sleep
import win32com.client
AUTOHOTKEY2_PROGID = 'AutoHotkey2.Script'
# Used FFI types and wrappers are defined after main program.
def main(args):
log_first_available_fls_index('at script start')
manifest_path = str(Path(args[1]).resolve()) if len(args) > 1 else None
first_ahk = create_com_object(AUTOHOTKEY2_PROGID, manifest_path)
log_first_available_fls_index('loaded 1st AutoHotkey COM object')
for i in range(2):
first_ahk.ahktextdll(f'a := {i}')
var = first_ahk.ahkgetvar('a')
log_first_available_fls_index('executed AutoHotkey script')
print(f' \ AutoHotkey variable is set to {i} and got back as {var}')
second_ahk = create_com_object(AUTOHOTKEY2_PROGID, manifest_path)
log_first_available_fls_index('loaded 2nd AutoHotkey COM object')
value = 128
second_ahk.ahktextdll(f'a := {value}')
var = second_ahk.ahkgetvar('a')
log_first_available_fls_index(
'executed AutoHotkey script in 2nd COM object')
print(f' \ AutoHotkey variable is set to {value} and got back as {var}')
unload_delay = 100 # ms
sleep_delay = 2 * unload_delay
sleep_delay_in_s = sleep_delay / 1000
CoFreeUnusedLibrariesEx(unload_delay, 0)
log_first_available_fls_index(
f'called CoFreeUnusedLibrariesEx({unload_delay}, 0)')
#
third_ahk = create_com_object(AUTOHOTKEY2_PROGID, manifest_path)
log_first_available_fls_index('loaded 3rd AutoHotkey COM object')
#
sleep(sleep_delay_in_s)
print(f'..sleeped {sleep_delay}ms')
CoFreeUnusedLibrariesEx(unload_delay, 0)
log_first_available_fls_index(
f'called CoFreeUnusedLibrariesEx({unload_delay}, 0)')
#
sleep(sleep_delay_in_s)
print(f'..sleeped {sleep_delay}ms')
CoFreeUnusedLibrariesEx(unload_delay, 0)
log_first_available_fls_index(
f'called CoFreeUnusedLibrariesEx({unload_delay}, 0)')
first_ahk.ahktextdll('a := 100500')
var = first_ahk.ahkgetvar('a')
log_first_available_fls_index(
'executed AutoHotkey script in 1st COM object')
print(f' \ AutoHotkey variable is set to 100500 and got back as {var}')
def log_first_available_fls_index(stage):
print(f'* {stage}')
print(f' First available FLS index is {first_available_fls_index()}')
def first_available_fls_index():
fls_index = None
try:
fls_index = FlsAlloc()
return fls_index
finally:
if fls_index is not None:
FlsFree(fls_index)
def create_com_object(progid, manifest_path=None):
act_ctx_handle = act_ctx_cookie = None
try:
if manifest_path:
act_ctx = ACTCTXW(source=manifest_path)
act_ctx_handle = CreateActCtxW(act_ctx)
act_ctx_cookie = ULONG_PTR(0)
ActivateActCtx(act_ctx_handle, act_ctx_cookie)
return win32com.client.Dispatch(progid)
finally:
if act_ctx_cookie is not None:
DeactivateActCtx(0, act_ctx_cookie)
if act_ctx_handle is not None:
ReleaseActCtx(act_ctx_handle)
#----------------------------------------------------------------------------
# Define FFI types.
#----------------------------------------------------------------------------
# Most types are defined in (and imported from) ctypes.wintypes module.
#
# https://docs.microsoft.com/en-us/windows/win32/winprog/windows-data-types
#
# typedef int BOOL
# typedef unsigned long DWORD
# typedef PVOID HANDLE
# typedef HANDLE HINSTANCE
# typedef HINSTANCE HMODULE
# typedef WORD LANGID
# typedef CONST WCHAR *LPCWSTR
# typedef void *PVOID
# typedef unsigned long ULONG
#
# #if defined(_WIN64)
# typedef unsigned __int64 ULONG_PTR;
# #else
# typedef unsigned long ULONG_PTR;
# #endif
#
# typedef unsigned short USHORT
# typedef wchar_t WCHAR
# typedef unsigned short WORD
# https://docs.python.org/3/library/ctypes.html#fundamental-data-types
# https://docs.python.org/3/library/platform.html#platform.architecture
if sys.maxsize > 2**32:
ULONG_PTR = ctypes.c_ulonglong
else:
ULONG_PTR = ctypes.c_ulong
# https://docs.microsoft.com/en-us/windows/win32/api/winnt/nc-winnt-pfls_callback_function
#
# PFLS_CALLBACK_FUNCTION PflsCallbackFunction;
#
# void PflsCallbackFunction(
# IN PVOID lpFlsData
# )
# https://docs.python.org/3/library/ctypes.html#callback-functions
PFLS_CALLBACK_FUNCTION = ctypes.WINFUNCTYPE(None, ctypes.c_void_p)
# https://docs.microsoft.com/en-us/windows/win32/api/winbase/ns-winbase-tagactctxw
class ACTCTXW(ctypes.Structure):
_fields_ = [
('size', ULONG),
('flags', DWORD),
('source', LPCWSTR),
('processorArchitecture', USHORT),
('langId', LANGID),
('assemblyDirectory', LPCWSTR),
('resourceName', LPCWSTR),
('applicationName', LPCWSTR),
('module', HMODULE)]
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.size = ctypes.sizeof(self)
#----------------------------------------------------------------------------
# Define helpers for using FFI wrappers.
#----------------------------------------------------------------------------
# https://www.pinvoke.net/default.aspx/Constants/INVALID_HANDLE_VALUE.html
INVALID_HANDLE_VALUE = HANDLE(-1).value
FLS_OUT_OF_INDEXES = DWORD(-1).value
# https://docs.python.org/3/library/ctypes.html#ctypes._FuncPtr.errcheck
def must_return_valid_handle(result, func, arguments):
if result == INVALID_HANDLE_VALUE:
raise ctypes.WinError()
return result
def must_return_true(result, func, arguments):
if not result:
raise ctypes.WinError()
return result
def must_return_valid_fls_index(result, func, arguments):
if result == FLS_OUT_OF_INDEXES:
raise ctypes.WinError()
return result
#----------------------------------------------------------------------------
# Define FFI wrappers.
#----------------------------------------------------------------------------
# https://docs.microsoft.com/en-us/windows/win32/api/winbase/nf-winbase-createactctxw
CreateActCtxW = ctypes.windll.kernel32.CreateActCtxW
CreateActCtxW.argtypes = [ctypes.POINTER(ACTCTXW)]
CreateActCtxW.restype = HANDLE
CreateActCtxW.errcheck = must_return_valid_handle
# https://docs.microsoft.com/en-us/windows/win32/api/winbase/nf-winbase-releaseactctx
ReleaseActCtx = ctypes.windll.kernel32.ReleaseActCtx
ReleaseActCtx.argtypes = [HANDLE]
ReleaseActCtx.restype = None
# https://docs.microsoft.com/en-us/windows/win32/api/winbase/nf-winbase-activateactctx
ActivateActCtx = ctypes.windll.kernel32.ActivateActCtx
ActivateActCtx.argtypes = [HANDLE, ctypes.POINTER(ULONG_PTR)]
ActivateActCtx.restype = BOOL
ActivateActCtx.errcheck = must_return_true
# https://docs.microsoft.com/en-us/windows/win32/api/winbase/nf-winbase-deactivateactctx
DeactivateActCtx = ctypes.windll.kernel32.DeactivateActCtx
DeactivateActCtx.argtypes = [DWORD, ULONG_PTR]
DeactivateActCtx.restype = BOOL
DeactivateActCtx.errcheck = must_return_true
# https://docs.microsoft.com/en-us/windows/win32/api/fibersapi/nf-fibersapi-flsalloc
FlsAlloc = ctypes.WINFUNCTYPE(DWORD, PFLS_CALLBACK_FUNCTION)(
('FlsAlloc', ctypes.windll.kernel32),
# Define default value for callback parameter (NULL)
((1, 'fls_callback', ctypes.cast(None, PFLS_CALLBACK_FUNCTION)),))
FlsAlloc.restype = DWORD
FlsAlloc.errcheck = must_return_valid_fls_index
# https://docs.microsoft.com/en-us/windows/win32/api/fibersapi/nf-fibersapi-flsfree
FlsFree = ctypes.windll.kernel32.FlsFree
FlsFree.argtypes = [DWORD]
FlsFree.restype = BOOL
FlsFree.errcheck = must_return_true
# https://docs.microsoft.com/en-us/windows/win32/api/combaseapi/nf-combaseapi-cofreeunusedlibrariesex
CoFreeUnusedLibrariesEx = ctypes.windll.combase.CoFreeUnusedLibrariesEx
CoFreeUnusedLibrariesEx.argtypes = [DWORD, DWORD]
ReleaseActCtx.restype = None
#----------------------------------------------------------------------------
# Execute main program.
#----------------------------------------------------------------------------
if __name__ == '__main__':
main(sys.argv)