Orion9

|
Posted: Mon May 25, 2026 13:02 Post subject: |
|
|
| AkulaBig wrote: | | Может у вас есть какая-нибудь идея, как решить эту проблему? |
Не всегда на костылях можно решить проблему, а WinScriptAdv это и есть костыль. | AkulaBig wrote: | | Таким образом самый быстрый вариант через временные файлы |
Это хороший вариант, но не самый быстрый. Через stdin/stdout должно быть быстрее, но через WinScriptAdv это проверить не получится
 Hidden text Почему нельзя просто "перехватить" дескриптор STDIN по PID? В ОС Windows дескрипторы таблиц STDIN / STDOUT формируются строго внутри структуры STARTUPINFO в момент рождения процесса. Windows не позволяет чужому изолированному процессу "на лету" внедриться и подменить системные дескрипторы ввода-вывода стандартными WinAPI вызовами. Из-за этого использование Named Pipes или файлов-аргументов — единственный стабильный способ работать без генерации процесса
Если верить болвану, придется использовать AttachConsole (подключение к чужой консоли) или Named Pipes (именованные каналы), но эти методы тоже приведут к задержкам.
На Autorun можно было попробовать, но лень. Как-то не тянет переводить DllCall для CreateProcess
 Hidden text | Code: | #Requires AutoHotkey v2.0
class ExifToolPipe {
__New(exiftoolPath) {
this.hChildStdInWr := 0
this.hChildStdOutRd := 0
this.hProcess := 0
this.hThread := 0
this.StartProcess(exiftoolPath)
}
StartProcess(exiftoolPath) {
; Настройка атрибутов безопасности для наследования дескрипторов
SA := Buffer(24, 0)
NumPut("UInt", 24, SA, 0) ; nLength
NumPut("Ptr", 0, SA, 8) ; lpSecurityDescriptor
NumPut("Int", 1, SA, 16) ; bInheritHandle
; Создаем Pipe для STDIN (Запись из AHK -> Чтение в ExifTool)
if !DllCall("CreatePipe", "Ptr*", &hChildStdInRd := 0, "Ptr*", &hChildStdInWr := 0, "Ptr", SA, "UInt", 0)
throw OSError()
; Создаем Pipe для STDOUT (Запись из ExifTool -> Чтение в AHK)
if !DllCall("CreatePipe", "Ptr*", &hChildStdOutRd := 0, "Ptr*", &hChildStdOutWr := 0, "Ptr", SA, "UInt", 0) {
DllCall("CloseHandle", "Ptr", hChildStdInRd)
DllCall("CloseHandle", "Ptr", hChildStdInWr)
throw OSError()
}
; Запрещаем наследование нашей (записывающей/читающей) стороны каналов
DllCall("SetHandleInformation", "Ptr", hChildStdInWr, "UInt", 1, "UInt", 0)
DllCall("SetHandleInformation", "Ptr", hChildStdOutRd, "UInt", 1, "UInt", 0)
; Настройка STARTUPINFO
SI := Buffer(A_PtrSize == 8 ? 104 : 68, 0)
NumPut("UInt", SI.Size, SI, 0)
NumPut("UInt", 0x00000100 | 0x00000001, SI, A_PtrSize == 8 ? 60 : 44) ; STARTF_USESTDHANDLES | STARTF_USESHOWWINDOW
NumPut("UShort", 0, SI, A_PtrSize == 8 ? 64 : 48) ; SW_HIDE (скрывает окно консоли)
NumPut("Ptr", hChildStdInRd, SI, SI.Size - 3 * A_PtrSize)
NumPut("Ptr", hChildStdOutWr, SI, SI.Size - 2 * A_PtrSize)
NumPut("Ptr", hChildStdOutWr, SI, SI.Size - A_PtrSize) ; Дублируем stdout в stderr
; Настройка PROCESS_INFORMATION
PI := Buffer(A_PtrSize == 8 ? 24 : 16, 0)
; Командная строка запуска
cmdLine := '"' exiftoolPath '" -stay_open True -@ -'
; Флаг CREATE_NO_WINDOW (0x08000000) полностью блокирует создание консольного окна
if !DllCall("CreateProcessW", "Ptr", 0, "Str", cmdLine, "Ptr", 0, "Ptr", 0, "Int", 1, "UInt", 0x08000000, "Ptr", 0, "Ptr", 0, "Ptr", SI, "Ptr", PI) {
; Закрываем все открытые дескрипторы при ошибке
DllCall("CloseHandle", "Ptr", hChildStdInRd), DllCall("CloseHandle", "Ptr", hChildStdInWr)
DllCall("CloseHandle", "Ptr", hChildStdOutRd), DllCall("CloseHandle", "Ptr", hChildStdOutWr)
throw OSError()
}
; Сохраняем нужные дескрипторы в свойствах класса
this.hChildStdInWr := hChildStdInWr
this.hChildStdOutRd := hChildStdOutRd
this.hProcess := NumGet(PI, 0, "Ptr")
this.hThread := NumGet(PI, A_PtrSize, "Ptr")
; Закрываем дескрипторы, которые больше не нужны родительскому процессу
DllCall("CloseHandle", "Ptr", hChildStdInRd)
DllCall("CloseHandle", "Ptr", hChildStdOutWr)
}
; Метод отправки команды (аналог exec.StdIn.WriteLine)
WriteLine(text) {
rawString := text "`r`n"
; ExifTool ожидает UTF-8 или системную кодировку (зависит от настроек, UTF-8 наиболее универсален)
bufLen := StrPut(rawString, "UTF-8")
buf := Buffer(bufLen, 0)
StrPut(rawString, buf, "UTF-8")
; Запись через WinAPI WriteFile
DllCall("WriteFile", "Ptr", this.hChildStdInWr, "Ptr", buf, "UInt", bufLen - 1, "UInt*", &bytesWritten := 0, "Ptr", 0)
}
; Метод чтения ответа (ваш ReadResponse переписанный под WinAPI)
ReadResponse() {
output := ""
lineBuffer := ""
; Буфер для побайтового/посимвольного чтения из пайпа
readBuf := Buffer(1, 0)
while DllCall("ReadFile", "Ptr", this.hChildStdOutRd, "Ptr", readBuf, "UInt", 1, "UInt*", &bytesRead := 0, "Ptr", 0) && bytesRead > 0 {
char := StrGet(readBuf, 1, "CP0") ; Читаем посимвольно в системной кодировке (или UTF-8 при побайтовой сборке)
if (char == "`n") {
line := Trim(lineBuffer, "`r`n")
if (line == "{ready}")
break
output .= line "`n"
lineBuffer := ""
} else {
lineBuffer .= char
}
}
return Trim(output, "`r`n")
}
; Корректное завершение работы процесса
Close() {
if this.hChildStdInWr {
this.WriteLine("-stay_open")
this.WriteLine("False")
DllCall("CloseHandle", "Ptr", this.hChildStdInWr)
this.hChildStdInWr := 0
}
if this.hChildStdOutRd
DllCall("CloseHandle", "Ptr", this.hChildStdOutRd), this.hChildStdOutRd := 0
if this.hProcess
DllCall("CloseHandle", "Ptr", this.hProcess), this.hProcess := 0
if this.hThread
DllCall("CloseHandle", "Ptr", this.hThread), this.hThread := 0
}
__Delete() {
this.Close()
}
}
|
А потом еще избавляться от однобайтового буфера
 Hidden text | Code: | ReadResponse() {
output := ""
remainder := ""
foundReady := false
; Размер буфера 4 КБ (вместо 1 байта)
bufferSize := 4096
readBuf := Buffer(bufferSize, 0)
; Читаем из пайпа блоками по 4096 байт
while DllCall("ReadFile", "Ptr", this.hChildStdOutRd, "Ptr", readBuf, "UInt", bufferSize, "UInt*", &bytesRead := 0, "Ptr", 0) && bytesRead > 0 {
; Преобразуем СРАЗУ ВЕСЬ БЛОК байтов в строку (UTF-8 или CP0 в зависимости от настроек ExifTool)
chunk := StrGet(readBuf, bytesRead, "UTF-8")
; Дописываем остаток строки с прошлого шага и очищаем его
fullText := remainder chunk
remainder := ""
; Разбиваем блок текста на массив строк по символу переноса
lines := StrSplit(fullText, "`n")
; Если блок прервался посередине строки (не заканчивается на `\n`),
; забираем последний кусок в remainder, чтобы склеить его при следующем чтении
if (SubStr(fullText, -1) != "`n") {
remainder := lines.Pop()
}
; Быстро перебираем строки в памяти AHK
for index, line in lines {
cleanLine := Trim(line, "`r")
; Если встретили маркер окончания ответа — фиксируем и выходим
if (cleanLine == "{ready}") {
foundReady := true
break
}
output .= cleanLine "`n"
}
; Если маркер {ready} найден, прекращаем чтение из пайпа
if foundReady
break
}
return Trim(output, "`n")
} |
В общем, нее... Ну его на фиг. Вот если бы Loopback сделал объект ComObject, как в Autohotkey, тогда можно было попробовать, всё свелось бы к нескольким строкам )
| AkulaBig wrote: | | Сравнение скорости работы ExifTool: |
Не совсем понятное сравнение. При разрешении 1920х1080 на панели помещается около 50 файлов. Прирост скорости от опции -stay_open с использованием временных файлов составляет где-то 2000%. Т.е. если без этой опции exitool обрабатывает 50 файлов за 20 сек., то с этой опцией у неё уйдёт на это всего 1 сек.
 Hidden text Win+[ запуск exiftool для каждого файла. Win+] держит процесс открытым и взаимодействует с ним через файл аргументов.
| Code: | SetHotkeyAction /K:W /V:219 TestExiftoolSpeed 0
SetHotkeyAction /K:W /V:221 TestExiftoolSpeed 1
Static sExif = COMMANDER_PATH & "\Plugins\wlx\ExifToolView\exiftool.exe"
Static sExifOut = TEMP & "\exiftool_out.txt", sExifArgs = TEMP & "\exiftool_args.txt"
Func TestExiftoolSpeed(StayOpen)
Local aSel = List(), sFile
Local sPath = RequestCopyDataInfo("SP"), j, files = 0, str
aSel.Text = GetSelectedItems(3, 0)
If StayOpen = 1 And Not ProcessExist("exiftool.exe") Then
FileWrite(sExifArgs, "", "UTF-8 NOBOM")
ShellExec /TT /SW_HIDE %sExif% "-stay_open True -@ ""%sExifArgs%"""
Sleep 100
Local nPID = ProcessGetId("exiftool.exe")
If nPID = 0 Then Return MsgBox("Процесс ExifTool не запущен")
EndIf
T1 = GetUptime()
ShowHint("Обработка выделенного Exiftool.exe")
For j = 0 To aSel.Count - 1
sFile = sPath & aSel[j]
If FileExist(sFile) And Not StrPos(FileGetAttr(sFile), "D") Then
files += 1
str &= ExiftoolInfo(sFile, StayOpen)
EndIf
Next
T2 = Round(GetUptime() - T1, 0) / 1000
ShowHint("Выбрано: " & aSel.Count & auCRLF & _
"Обработано: " & files & auCRLF & _
"-stay_open: " & (StayOpen ? "Да" : "Нет") & auCRLF & _
"Объем данных: " & SizeFormat(StrLen(str), 0, 'B', 2) & auCRLF & _
"Время операции: " & StrFormat("%.3f", T2) & " sec")
Free(aSel)
ClipPut(str)
EndFunc
Func ExiftoolInfo(FileName, StayOpen)
If Not StayOpen Then
ProcessExecGetOutput /C:65001 out %sExif% '-charset filename=Russian -lang ru -G -S "%FileName%"'
Else
If FileExist(sExifOut) Then
DllCall("kernel32.dll\DeleteFileW", "Wstr", sExifOut)
EndIf
Local sArgs = "-G" & auCRLF & _
"-S" & auCRLF & _
"-lang" & auCRLF & _
"ru" & auCRLF & _
"-W+!" & auCRLF & sExifOut & auCRLF & FileName & auCRLF & "-execute" & auCRLF
Static obj = BinaryFile(sExifArgs, "a")
obj.WriteStr(sArgs, "", "UTF-8")
out = "<time-out>"
For i = 1 To 50
If FileExist(sExifOut) Then
out = FileRead(sExifOut, 0, "UTF-8")
DllCall("kernel32.dll\DeleteFileW", "Wstr", sExifOut)
Break
EndIf
Sleep(10)
Next
EndIf
Return out
EndFunc |
И файловые операции не самое слабое здесь звено, их точно бояться не стоит
 Hidden text Testing shows that due to file caching, a temporary file can be very fast for relatively small outputs. In fact, if the file is deleted immediately after use, it often does not actually get written to disk. For example:
| Code: | RunWait A_ComSpec ' /c dir > C:\My Temp File.txt'
VarToContainContents := FileRead("C:\My Temp File.txt")
FileDelete "C:\My Temp File.txt" |
To avoid using a temporary file (especially if the output is large), consider using the Shell.Exec() method as shown in the examples for the Run function.
https://www.autohotkey.com/docs/v2/FAQ.htm#output
А вот от Sleep никуда не деться, он-то как раз и задерживает. 50 файлов по Sleep(10) это целые полсекунды. Взаимодейстие через stdin могло бы теоретически ускорить этот процесс. |
|