1 (изменено: streleck1y, 2021-10-08 18:58:58)

Тема: 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
Ждать благодарности — глупо, а быть неблагодарным — подло.
Сообщество ВКонтакте

2

Re: AHK: Библиотека для консольных приложений

Спасибо! Но уже вижу явные ошибки:

    DllCall("ntdll.dll\RtlFillMemoryUlong", "UInt", &PE32, "UInt", 4, "UInt", 304)  ; Set dwSize
    VarSetCapacity(th32ProcessID, 4, 0)
    if (DllCall("Process32First", "UInt", hSnapshot, "UInt", &PE32))

Хэндлы и адреса имеют тип Ptr, а не UInt.

Разработка AHK-скриптов:
e-mail dfiveg@mail.ru
Telegram jollycoder

3

Re: AHK: Библиотека для консольных приложений

teadrinker, запомню :-)

Ждать благодарности — глупо, а быть неблагодарным — подло.
Сообщество ВКонтакте