Re: AHK: Работа с потоками autohotkey.dll
Спасибо, работает.
Win10x64 AhkSpy, Hotkey, ClockGui
Вы не вошли. Пожалуйста, войдите или зарегистрируйтесь.
Чтобы отправить ответ, вы должны войти или зарегистрироваться
Спасибо, работает.
А возможно запустить в потоке анк файл как ресурс? То есть не прописывать код потока в переменной, а добавить его ресурсом.
Ну да, считываем ресурс в память по тому же принципу, потом превращаем в строку.
Получилось.
pData := LoadScriptResource(hModule, v, size)
MsgBox % StrGet(pData,size,"UTF-8")
А что за функция UnZipRawMemory, я не нашёл её в инклудах.
Это из AHK_H.
А, это для расшировки с паролем.
С BinRun никак не получается сделать универсальную версию, ERROR e_magic not found.
Непонятная штука, при добавлении ресурсом файла анк, с паролем требуется расшифровка с помощью UnZipRawMemory, то есть как будто ресурсы шифруются при добавлении, но тогда как Resource Hacker видит исходный текст этих ресурсов?
У меня видит зашифрованный. Ты про какой ресурс говоришь?
при добавлении ресурсом файла анк
Пока не пробовал, потом проверю.
Раз уж тут тема про BinRun() зашла, может подскажет кто-то.
При компиляции данного скрипта, в ресурсы будет вшит file.exe, а называться ресурс будет - "C:\Users\user\Desktop\file.exe". Так вот этот ресурс нельзя переименовывать, иначе вылетает ERROR e_magic not found. Есть альтернативные способы "вшитья" файла в ресурсы, кроме FileInstall, с последующим успешным запуском?
If 0
FileInstall, C:\Users\user\Desktop\file.exe,-
BinRun("C:\Users\user\Desktop\file.exe")
Есть альтернативные способы "вшитья" файла в ресурсы
Ну да, есть обычный способ через winapi: Updating Resources.
teadrinker
Получилось добавить ресурс, но BinRun() выдает ошибку e_magic. Видимо FileInstall и BinRun как-то зависят друг от друга. Отдельно не работает.. Как-то сложно все с этим ahk_h.
powercat, ничего не могу сказать, пока не увижу ваш код, как вы считываете ресурс, и как используете.
Всем добра!
По мотивам интересного материала.
Простая обёртка классом, для большего удобства запуска параллельных задач. Упрощает навигацию данных коммутируемого объекта и заботится о жизненном цикле подключаемых сценариев:
Class ScriptToProcess
{
__New() {
this.counter := 0
this.GUID := CreateGUID()
this.SERVER := {GLOBAL_STORAGE: []}
ObjRegisterActive(this.SERVER, this.GUID)
}
; ============================================================================================================
; Добавляет и запускает скрипт.
; script - ( string ) текст сценария или путь до файла со сценарием, который будет запущен.
; pseudonym - ( string ) псевдоним, через который будет доступен коммутируемый объект. По умолчанию
; использует внутренний счётчик. Если псевдоним уже используется, к нему
; конкатенируется текущее значение внутреннего счётчика, приращивающегося
; с каждым добавленным сценарием.
; persist - ( bool ) добавляет "#Persistent", заставляя сценарий быть постоянно активным.
; По умолчанию "false".
; tray_icon - ( int[0, 1]/string ) выключает или задаёт отображение иконки в трее.
; Допустимые значения:
; 0 - отключить показ иконки.
; 1 - включить показ иконки.
; ( string ) - содержит путь к файлу, или shell32.dll с номером.
; (например - "shell32.dll, 110"). По умолчанию 1.
; libs - ( Array(string) ) массив строк с путями к подключаемым модулям.
; callbacks - ( Array(Func()) ) последний аргумент принимает любое количество коллбэков, которые
; затем можно вызывать в том же порядке, используя глобальную константу "SELF".
; (например, первый коллбэк - SELF.callbacks[1].Call( data )).
;
; В добавленные к выполнению таким образом сценарии, ВСЕГДА предустанавливаются ряд глобальных
; констант, а так же функция завершения. А именно:
;
; PSEUDONYM - ( string ) строка, представляющая псевдоним конкретно этого контекста.
; SERVER - ( ComObject ) глобальный коммутируемый объект, через который доступны все
; добавленные сценарии.
; GLOBAL_STORAGE - ( ComObject ) глобальное хранилище, доступное каждому добавленному сценарию.
; SELF - ( ComObject ) часть глобального коммутируемого объекта, относящаяся
; непосредственно к данным, оперируемым текущим контекстом. Через неё доступны
; поля:
; done - ( Func() ) содержащее ссылку на коллбэк, завершающий работу
; текущего контекста.
; data - ( Array() ) массив. Размещайте здесь данные, предназначенные
; только для текущего контекста.
; callbacks - ( Array(Func()) ) массив коллбэков. Функции вызывающего
; сценария, вызываемые из текущего контекста.
; _Done_ - ( Func() ) функция, содержащая единственную инструкцию - "ExitApp".
; ============================================================================================================
Add(script, pseudonym := "", persist := false, tray_icon := 1, libs := "", callbacks*) {
if (FileExist(script))
FileRead, script,% script
this.counter += 1
if (!pseudonym)
pseudonym := this.counter
else if (this.SERVER.HasKey( pseudonym ))
pseudonym .= this.counter
this.SERVER[ pseudonym ] := {done: "", data: [], callbacks: callbacks}
add_content := ""
add_content .= "#NoEnv`r`n"
add_content .= persist ? "#Persistent`r`n" : ""
add_content .= tray_icon == 1 ? "" : "Menu, Tray, " . (tray_icon == 0 ? "NoIcon" : "Icon, " . tray_icon) . "`r`n"
add_content .= "Global PSEUDONYM := """ . pseudonym . """`r`n"
add_content .= "Global SERVER := ComObjActive(""" . this.GUID . """)`r`n"
add_content .= "Global GLOBAL_STORAGE := SERVER[ ""GLOBAL_STORAGE"" ]`r`n"
add_content .= "Global SELF := SERVER[ PSEUDONYM ]`r`n"
add_content .= "SELF.done := Func(""_Done_"")`r`n"
add_content .= "_Done_() {`r`n ExitApp`r`n}`r`n"
lib_content := "`r`n"
For i, lib in libs {
if (FileExist(lib))
FileRead, lib,% lib
lib_content .= lib . "`r`n"
}
script := add_content . script . lib_content
DynaRun(script)
}
; ============================================================================================================
; Удаляет и завершает выполнение добавленного сценария.
; pseudonym - псевдоним сценария, выполнение которого необходимо завершить.
; ============================================================================================================
Remove(pseudonym) {
if (pseudonym != "GLOBAL_STORAGE" && this.SERVER.HasKey( pseudonym )) {
Try
this.SERVER[ pseudonym ].done.Call()
this.SERVER.Delete( pseudonym )
}
}
; ============================================================================================================
; Завершает все активные сценарии. Вызывается автоматически при удалении экземпляра
; этого класса.
; ============================================================================================================
RemoveAll() {
tasks := []
For key in this.SERVER
tasks.Push( key )
For i, task in tasks
this.Remove( task )
}
__Get(name) {
return this.SERVER[ name ]
}
__Delete() {
this.RemoveAll()
ObjRegisterActive(this.SERVER, "")
}
}
ObjRegisterActive(Object, CLSID, Flags := 0) {
static cookieJar := {}
if !CLSID {
if (( cookie := cookieJar.Delete(Object) ) != "")
DllCall("oleaut32\RevokeActiveObject", UInt, cookie, Ptr, 0)
return
}
if cookieJar[Object]
throw Exception("Object is already registered", -1)
VarSetCapacity(_clsid, 16, 0)
if (hr := DllCall("ole32\CLSIDFromString", WStr, CLSID, Ptr, &_clsid)) < 0
throw Exception("Invalid CLSID", -1, CLSID)
hr := DllCall("oleaut32\RegisterActiveObject", Ptr, &Object, Ptr, &_clsid, UInt, Flags, UIntP, cookie, UInt)
if hr < 0
throw Exception(format("Error 0x{:x}", hr), -1)
cookieJar[Object] := cookie
}
CreateGUID()
{
VarSetCapacity(pguid, 16, 0)
if !DllCall("ole32.dll\CoCreateGuid", Ptr, &pguid) {
size := VarSetCapacity(sguid, (38 << !!A_IsUnicode) + 1, 0)
if DllCall("ole32.dll\StringFromGUID2", Ptr, &pguid, Ptr, &sguid, UInt, size)
return StrGet(&sguid)
}
}
DynaRun(tempScript, ahkPath := "", pipeName := "", args*)
{
static params := [ "UInt", PIPE_ACCESS_OUTBOUND := 0x2, "UInt", 0
, "UInt", PIPE_UNLIMITED_INSTANCES := 255, "UInt", 0
, "UInt", 0, "Ptr", 0, "Ptr", 0, "Ptr" ]
, BOM := Chr(0xFEFF)
(ahkPath = "" && ahkPath := A_AhkPath)
(pipeName = "" && pipeName := "AHK_" . A_TickCount)
Loop 1 {
for k, v in ["pipeGA", "pipe"]
%v% := DllCall("CreateNamedPipe", Str, "\\.\pipe\" . pipeName, params*)
if ( (pipe = -1 || pipeGA = -1) && error := "Can't create pipe """ . pipeName . """`nLastError: " . A_LastError )
break
sCmd := ahkPath . " ""\\.\pipe\" . pipeName . """"
for k, v in args
sCmd .= " """ . v . """"
Run, % sCmd,, UseErrorLevel HIDE, PID
if (ErrorLevel && error := "Can't open file:`n ""\\.\pipe\" . pipeName . """")
break
for k, v in ["pipeGA", "pipe"]
DllCall("ConnectNamedPipe", Ptr, %v%, Ptr, 0)
tempScript := BOM . tempScript
tempScriptSize := ( StrLen(tempScript) + 1 ) << !!A_IsUnicode
if !DllCall("WriteFile", Ptr, pipe, Str, tempScript, UInt, tempScriptSize, UIntP, 0, Ptr, 0)
error := "WriteFile failed, LastError: " . A_LastError
}
for k, v in ["pipeGA", "pipe"]
( %v% != -1 && DllCall("CloseHandle", Ptr, %v%) )
if error
throw Exception(error)
Return PID
}
#NoEnv
#SingleInstance, Force
SetWorkingDir,% A_ScriptDir
ListLines, Off
SetBatchLines, -1
OnExit, BeforeExit
SplitPath,% A_ScriptFullPath,,,, fNameNoExt
; >>--++--<< >>--++--<<
#Include ScriptToProcess.ahk
; >>--++--<< >>--++--<<
ww := 400, hh := 100
; >>--++--<< >>--++--<<
INI := "Config.ini"
if (!FileExist(INI))
FileAppend,,% INI,UTF-16
IniRead, x_pos,% INI, Main, x_pos,% A_ScreenWidth // 2 - ww // 2
IniRead, y_pos,% INI, Main, y_pos,% A_ScreenHeight // 2 - hh // 2
; >>--++--<< >>--++--<<
Gui, 1: +HWND_main_h
Gui, 1: Margin, 10, 10
Gui, 1: Add, Edit, xm y+5 w%ww% r5 v_myEdit
Gui, 1: Add, Button, xm y+5 w%ww% gDoSomething,Кнопка
Gui, 1: Show,x%x_pos% y%y_pos%,% fNameNoExt
; >>--++--<< script >>--++--<<
task =
(%
data := SELF.data
interval := -1
SetTimer, CheckMyData,% interval
Return
CheckMyData:
if (data.Count()) {
; MsgBox,% data.RemoveAt(1)
SELF.callbacks[1].Call(Format("'{}' from CallBack name => '{}'", data.RemoveAt(1), SELF.callbacks[1].name ))
SetTimer, CheckMyData,% interval := -1
} else
SetTimer, CheckMyData,% interval > -250 ? (interval -= 1) : interval
Return
)
; >>--++--<< script >>--++--<<
include_libs := []
tasks := new ScriptToProcess()
; tasks.Add("test.ahk", "helper", 1, "shell32.dll, 110", include_libs, Func("CallBackFunction"))
tasks.Add(task, "helper", 1, "shell32.dll, 110", include_libs, Func("CallBackFunction") )
helper_data := tasks[ "helper" ].data
Return
CallBackFunction(data) {
MsgBox,% data
}
DoSomething:
Gui, 1: Submit, NoHide
helper_data.Push(_myEdit)
Return
BeforeExit:
WinGetPos, xx, yy, , , ahk_id%_main_h%
Gui, 1: Submit
if (xx != x_pos && xx > 0)
IniWrite,% xx,% INI, Main, x_pos
if (yy != y_pos && yy > 0)
IniWrite,% yy,% INI, Main, y_pos
if (A_ExitReason) {
ExitApp
}
return
GuiClose:
ExitApp
В примере, основной сценарий помещает некоторые данные из поля Edit в data коммутируемого объекта, а подключаемый, ожидает эти данные, чтобы передать их обратно основному сценарию в коллбэке.
30.03.2020 UPD: Теперь позволяет подключать модули(#Include), полученные в виде массива строк с путями к ним, из параметра "libs".
KusochekDobra, приветствую!
Нужно иметь в виду, что подбный ExecScript() не поддерживает юникод. Сравните:
ExecScript("MsgBox,, ExecScript, ♪")
DynaRun("MsgBox,, DynaRun, ♪")
ExecScript(script)
{
shell := ComObjCreate("WScript.Shell")
exec := shell.Exec("AutoHotkey.exe /ErrorStdOut *")
exec.StdIn.Write(script)
exec.StdIn.Close()
Return exec.ProcessID
}
DynaRun(tempScript, ahkPath := "", pipeName := "", args*)
{
static params := [ "UInt", PIPE_ACCESS_OUTBOUND := 0x2, "UInt", 0
, "UInt", PIPE_UNLIMITED_INSTANCES := 255, "UInt", 0
, "UInt", 0, "Ptr", 0, "Ptr", 0, "Ptr" ]
, BOM := Chr(0xFEFF)
(ahkPath = "" && ahkPath := A_AhkPath)
(pipeName = "" && pipeName := "AHK_" . A_TickCount)
Loop 1 {
for k, v in ["pipeGA", "pipe"]
%v% := DllCall("CreateNamedPipe", Str, "\\.\pipe\" . pipeName, params*)
if ( (pipe = -1 || pipeGA = -1) && error := "Can't create pipe """ . pipeName . """`nLastError: " . A_LastError )
break
sCmd := ahkPath . " ""\\.\pipe\" . pipeName . """"
for k, v in args
sCmd .= " """ . v . """"
Run, % sCmd,, UseErrorLevel HIDE, PID
if (ErrorLevel && error := "Can't open file:`n ""\\.\pipe\" . pipeName . """")
break
for k, v in ["pipeGA", "pipe"]
DllCall("ConnectNamedPipe", Ptr, %v%, Ptr, 0)
tempScript := BOM . tempScript
tempScriptSize := ( StrLen(tempScript) + 1 ) << !!A_IsUnicode
if !DllCall("WriteFile", Ptr, pipe, Str, tempScript, UInt, tempScriptSize, UIntP, 0, Ptr, 0)
error := "WriteFile failed, LastError: " . A_LastError
}
for k, v in ["pipeGA", "pipe"]
( %v% != -1 && DllCall("CloseHandle", Ptr, %v%) )
if error
throw Exception(error)
Return PID
}
teadrinker, благодарю, обновил.
Не считая возможности обмениваться сообщениями между скриптами — на сегодня можно обходиться без autohotkey.dll для описанного функционала?
Какого именно?
Работы с потоками. Вначале описывается, как с помощью autohotkey.dll настроить многопоточность.
На сегодняшний день в этом плане ничего не изменилось.
teadrinker, у кода из 100 поста не работает terminate.
RunNewThreadWithAutoHotkeyDll("#persistent", A_IsCompiled ? "" : A_PtrSize = 8 ? ahkDll64 : ahkDll32)
sleep 1000
RunNewThreadWithAutoHotkeyDll()
Там первый параметр обязательный, нужно RunNewThreadWithAutoHotkeyDll(""). У меня завершение ветки происходит, но, правда, при этом основная ветка тоже крашится. Может, от версии зависит, сейчас проверил на AHK_H\v1.1.32.00.
Да, я про это и говорю.
При этом такой пример работает:
DllCall(MemLib.GetProcAddress("ahktextdll"),"Str","~lbutton up::msgbox","Str","","Str","") ; start first AutoHotkey.dll thread
Sleep 1000
MsgBox,0, AutoHotkey.dll started, press OK to terminate.
DllCall(MemLib.GetProcAddress("ahkterminate")), MemLib.Free()
MsgBox AutoHotkey.dll threads terminated and memory freed
Если TerminateThread() так сделать
TerminateThread(MemLib) {
DllCall(MemLib.GetProcAddress("ahkterminate")), MemLib.Free()
}
то тоже нормально работает, но только для одной ветки, для большего количества начинает ошибки из _Struct.ahk выдавать. Скорее всего, баги в библиотеках. Я как-то пытался связаться с HotKeyIt по поводу другого бага, но безуспешно, и бросил использовать эту конструкцию в реальных проектах.
Чтобы отправить ответ, вы должны войти или зарегистрироваться