Тема: AHK: Библиотека для консольных приложений
Здравствуйте форумчане, решил поделиться с Вами своей библиотекой для консольных приложений на AutoHotKey, которую я использую в своих программах. Думаю, будет полезным для всех. Знаю, что когда-то была такая же библиотека на иностранных форумах, но она не работает в Windows 10 и выше.
Возможности, которые откроются с этой библиотекой:
автоматическое подключение к хосту окна консоли, откуда был вызван скрипт;
принятие ввода текста от пользователя;
вывод текста в консоль;
автоматическое изменение кодовой страницы для ввода/вывода на 1251 (во-избежание "крякозябр");
Имеется большой минус, связанный с подключением скрипта к консоли. Заключается он в том, что скрипты AutoHotKey подразумеваются системой как не консольные, следовательно если просто подключиться к консоли, то вводы/выводы будут путаться, и решением этой проблемы в этой библиотеке является замораживание процесса. Это стабильно работает, но если скрипт вызван из стандартной командной строки. Но в случае PowerShell было замечено, что ввод новых команд не является возможным, однако вывод работает отлично. Если Вы знаете метод, с помощью которого можно обойтись без замораживания, то буду рад, посмотреть Ваше решение в этом обсуждении.
Сама библиотека, с примером в конце:
global attached, hStdIn, hStdOut
OnExit("ExitConsole") ; нужно разморозить предыдущий процесс
; Переменная "attached" содержит либо "0" (ноль), либо "1" (единицу). Если равно 0, то консоль создана в новом окне; если равно, то консоль подключилась к другой.
class console {
init() {
console.create()
DllCall("SetConsoleOutputCP", "int", 1251)
DllCall("SetConsoleCP", "int", 1251)
}
flushBuffer() {
hStdout.Read(0)
}
create() {
attached = 1
if (DllCall("AttachConsole", "uInt", Process_GetCurrentParentProcessID(), "Cdecl int") != 1) {
DllCall("AllocConsole")
attached = 0
}
hStdIn := FileOpen(DllCall("GetStdHandle", "int", -10, "ptr"), "h `n")
hStdOut := FileOpen(DllCall("GetStdHandle", "int", -11, "ptr"), "h `n")
if (attached) {
console.writeln("") ; отступ одной строки от вывода прошлого процесса
Process_Suspend(Process_GetCurrentParentProcessID()) ; замораживание процесса, из которого был вызван скрипт
ControlSend,, {enter}, % "ahk_pid " Process_GetCurrentParentProcessID() ; пропускаем ввод прошлого процесса
}
}
read() {
result := RTrim(hStdIn.ReadLine(), "`n")
console.flushBuffer()
return result
}
write(text) {
result := hStdOut.write(text)
console.flushBuffer()
return result
}
writeln(text) {
result := hStdOut.WriteLine(text)
console.flushBuffer()
return result
}
}
Process_GetCurrentProcessID(){
Return DllCall("GetCurrentProcessId")
}
Process_GetCurrentParentProcessID(){
Return Process_GetParentProcessID(Process_GetCurrentProcessID())
}
Process_GetProcessName(ProcessID){
Return Process_GetProcessInformation(ProcessID, "Str", 260, 36) ; TCHAR szExeFile[MAX_PATH]
}
Process_GetParentProcessID(ProcessID){
Return Process_GetProcessInformation(ProcessID, "UInt *", 4, 24) ; DWORD th32ParentProcessID
}
Process_GetProcessThreadCount(ProcessID){
Return Process_GetProcessInformation(ProcessID, "UInt *", 4, 20) ; DWORD cntThreads
}
Process_GetProcessInformation(ProcessID, CallVariableType, VariableCapacity, DataOffset){
hSnapshot := DLLCall("CreateToolhelp32Snapshot", "UInt", 2, "UInt", 0) ; TH32CS_SNAPPROCESS = 2
if (hSnapshot >= 0)
{
VarSetCapacity(PE32, 304, 0) ; PROCESSENTRY32 structure
DllCall("ntdll.dll\RtlFillMemoryUlong", "UInt", &PE32, "UInt", 4, "UInt", 304) ; Set dwSize
VarSetCapacity(th32ProcessID, 4, 0)
if (DllCall("Process32First", "UInt", hSnapshot, "UInt", &PE32))
Loop
{
DllCall("RtlMoveMemory", "UInt *", th32ProcessID, "UInt", &PE32 + 8, "UInt", 4)
if (ProcessID = th32ProcessID)
{
VarSetCapacity(th32DataEntry, VariableCapacity, 0)
DllCall("RtlMoveMemory", CallVariableType, th32DataEntry, "UInt", &PE32 + DataOffset, "UInt", VariableCapacity)
DllCall("CloseHandle", "UInt", hSnapshot)
Return th32DataEntry ; Process data found
}
if not DllCall("Process32Next", "UInt", hSnapshot, "UInt", &PE32)
Break
}
DllCall("CloseHandle", "UInt", hSnapshot)
}
Return ; Cannot find process
}
Process_GetModuleFileNameEx(ProcessID) ; modified version of shimanov's function
{
if A_OSVersion in WIN_95, WIN_98, WIN_ME
Return Process_GetProcessName(ProcessID)
; #define PROCESS_VM_READ (0x0010)
; #define PROCESS_QUERY_INFORMATION (0x0400)
hProcess := DllCall( "OpenProcess", "UInt", 0x10|0x400, "Int", False, "UInt", ProcessID)
if (ErrorLevel or hProcess = 0)
Return
FileNameSize := 260
VarSetCapacity(ModuleFileName, FileNameSize, 0)
CallResult := DllCall("Psapi.dll\GetModuleFileNameExA", "UInt", hProcess, "UInt", 0, "Str", ModuleFileName, "UInt", FileNameSize)
DllCall("CloseHandle", hProcess)
Return ModuleFileName
}
Process_Suspend(PID_or_Name){
PID := (InStr(PID_or_Name,".")) ? ProcExist(PID_or_Name) : PID_or_Name
h:=DllCall("OpenProcess", "uInt", 0x1F0FFF, "Int", 0, "Int", pid)
If !h
Return -1
DllCall("ntdll.dll\NtSuspendProcess", "Int", h)
DllCall("CloseHandle", "Int", h)
}
Process_Resume(PID_or_Name){
PID := (InStr(PID_or_Name,".")) ? ProcExist(PID_or_Name) : PID_or_Name
h:=DllCall("OpenProcess", "uInt", 0x1F0FFF, "Int", 0, "Int", pid)
If !h
Return -1
DllCall("ntdll.dll\NtResumeProcess", "Int", h)
DllCall("CloseHandle", "Int", h)
}
ProcExist(PID_or_Name=""){
Process, Exist, % (PID_or_Name="") ? DllCall("GetCurrentProcessID") : PID_or_Name
Return Errorlevel
}
ExitConsole() {
console.writeln("") ; отступ в одну строку после вывода программы
Process_Resume(Process_GetCurrentParentProcessID()) ; размораживание процесса, из которого был вызван скрипт
}
; Инициализация консоли
console.init()
; Пробуем написать в нее что-либо:
console.writeln("Привет, Мир!")
console.write("Введите Ваше имя: ")
name := console.read() ; получаем текст, который ввел пользователь и записываем его в переменную name.
console.writeln("Рад познакомиться, " name "!")
runwait, %ComSpec% /c pause ; замена getch
Если нужно изменить заголовок окна консоли, то можно воспользоваться DllCall (пример ниже работает, если Ваша версия AHK является 32-разрядной).
DllCall("SetConsoleTitleW", "str", "заголовок")
Также стоит учесть, что вывод всех консольных приложений также будет в этой консоли. Рекомендуется их вызывать через RunWait, так как потоки вывода/ввода будут путаться. Пример ниже.
RunWait, cmd.exe
Сообщество ВКонтакте