Встречайте псевдоконсоль Windows (ConPTY)

Автор оригинала: Rich Turner
  • Перевод
Статья опубликована 2 августа 2018 года

Это вторая статья про командную строку Windows, где мы обсудим новую инфраструктуру и программные интерфейсы псевдоконсоли Windows, то есть Windows Pseudo Console (ConPTY): зачем мы её разработали, для чего она нужна, как работает, как её использовать и многое другое.

В прошлой статье «Тяжкое наследие прошлого. Проблемы командной строки Windows» мы рассказали о предпосылках появления терминала и эволюции командной строки в Windows, а также начали изучать внутреннее устройство Windows Console и инфраструктуры Windows Command-Line. Мы также обсудили многие преимущества и главные недостатки консоли Windows.

Один из недостатков заключается в том, что Windows пытается быть «полезной», но мешает разработчикам альтернативных и сторонних консолей, разработчикам служб и т.д. При создании консоли или службы разработчикам нужно иметь доступ к каналам связи, по которым их терминал/служба обменивается данными с приложениями командной строки, или предоставлять доступ к ним. В мире *NIX это не проблема, потому что *NIX предоставляет инфраструктуру «псевдотерминала» (PTY), которая позволяет легко создавать коммуникационные каналы для консоли или службы. Но в Windows такого не было…

… до настоящего времени!

От TTY к PTY


Прежде чем подробно рассказать о нашей разработке, давайте кратко вернёмся к развитию терминалов.

Вначале был TTY


Как обсуждалось в прошлой статье, в первые дни вычислений пользователи управляли компьютерами с помощью электромеханических телетайпов (TTY), подключённых к компьютеру через какой-то последовательный канал связи (обычно через токовую петлю 20 мА).


Кен Томпсон и Деннис Ричи (стоя) работают на DEC PDP-11 по телетайпу (сообщения без электронного дисплея)

Распространение терминалов


На смену телетайпам пришли компьютеризированные терминалы с электронными дисплеями (обычно ЭЛТ-экранами). Как правило, терминалы — очень простые устройства (отсюда термин «тупой терминал»), содержащие только электронику и вычислительную мощность, необходимую для следующих задач:

  1. Приём текстового ввода с клавиатуры.
  2. Буферизация введённого текста по одной строке (включая локальное редактирование перед отправкой).
  3. Отправка/получение текста по последовательному каналу (обычно через некогда повсеместный интерфейс RS-232).
  4. Отображение полученного текста на дисплее терминала.

Несмотря на простоту (а может, благодаря ей), терминалы быстро стали основным средством для управления миникомпьютерами, мейнфреймами и серверами: большинство операторов ввода данных, компьютерных операторов, системных администраторов, учёных, исследователей, разработчиков программного обеспечения и светил индустрии работали на терминалах DEC, IBM, Wyse и многих других.


Адмирал Грейс Хоппер в своём офисе с терминалом DEC VT220 на столе

Распространение программных терминалов


Начиная с середины 1980-х годов вместо специализированных терминалов постепенно начали применяться компьютеры общего назначения, которые становились более доступными, популярными и мощными. Во многих ранних ПК и других компьютерах 80-х годов имелись терминальные приложения, которые открывали соединение по порту RS-232 на ПК и обменивались данными с кем угодно на другом конце соединения.

По мере того как компьютеры общего назначения становились всё более изощрёнными, появился графический пользовательский интерфейс (GUI) и целый новый мир одновременно работающих приложений, включая терминальные приложения.

Но возникла проблема: как терминальному приложению взаимодействовать с другим приложением командной строки, запущенным на той же машине? И как физически подключить последовательный кабель между двумя приложениями, работающими на одном компьютере?

Появление псевдотерминала (PTY)


В мире *NIX проблему решили введением псевдотерминала (PTY).

PTY эмулирует серийное телекоммуникационное оборудование в компьютере, выставляя ведущее и ведомое псевдоустройства (“master” и “slave”): терминальные приложения подключаются к ведущему псевдоустройству, а приложения командной строки (например, оболочки вроде cmd, PowerShell и bash) — к ведомому псевдоустройству. Когда терминальный клиент передаёт текст и/или команды управления (закодированные как текст) ведущему псевдоустройству, текст транслируется на связанное с ним ведомое. Текст от приложения направляется на ведомое псевдоустройство, затем обратно на ведущее и, таким образом, на терминал. Данные всегда отправляются/принимаются асинхронно.


Приложение/оболочка псевдотерминала

Важно отметить, что «ведомое» псевдоустройство эмулирует поведение физического терминала и преобразует командные символы в сигналы POSIX. Например, если пользователь вводит в терминал CTRL+C, то значение ASCII для CTRL+C (0x03) отправляется через ведущее устройство. При получении на ведомом псевдоустройстве значение 0x03 удаляется из входного потока и генерируется сигнал SIGINT.

Такая инфраструктура PTY широко используется терминальными приложениями *NIX, менеджерами текстовых панелей (например, screen, tmux) и т.д. Данные приложения вызывают openpty(), который возвращает пару файловых дескрипторов (fd) для ведущего и ведомого устройств PTY. Затем приложение может форкнуть/выполнить дочернее приложение командной строки (например, bash), которое использует свои ведомые fd для прослушивания и возврата текста на подключённый терминал.

Этот механизм позволяет терминальным приложениям «разговаривать» непосредственно с приложениями командной строки, запущенными локально, как терминал разговаривал бы с удалённым компьютером через последовательное/сетевое соединение.

Что, нет псевдоконсоли Windows?


Как мы обсуждали в предыдущей статье, в то время как консоль Windows концептуально похожа на традиционный терминал *NIX, она отличается несколькими ключевыми способами, особенно на самых низких уровнях, которые могут вызвать проблемы у разработчиков приложений командной строки Windows, сторонних терминалов/консолей и серверных приложений:

  1. В Windows нет инфраструктуры PTY: когда пользователь запускает приложение командной строки (например, Cmd, PowerShell, wsl, ipconfig и т.д.), то Windows сама «подключает» новый или существующий экземпляр консоли к приложению.
  2. Windows мешает сторонним консолям и серверным приложениям: Windows (в настоящее время) не даёт терминалам способ предоставления каналов связи, через которые они хотят взаимодействовать с приложением командной строки. Сторонним терминалам приходится создавать консоли за пределами экрана, отправлять туда введённые пользователем данные и скрапить выдачу, перерисовывая её на собственном дисплее сторонней консоли!
  3. Только в Windows есть Console API: приложения командной строки Windows полагаются на Win32 Consol API, что снижает переносимость кода, поскольку все остальные платформы поддерживают текст/VT, а не API.
  4. Нестандартный удалённый доступ: зависимость приложений командной строки от Consol API существенно затрудняет взаимодействие и сценарии удалённого доступа.

Что делать?


Многие, многие разработчики часто просили PTY-подобный механизм под Windows, особенно те, кто работает с инструментами ConEmu/Cmder, Console2/ConsoleZ, Hyper, VSCode, Visual Studio, WSL, Docker и OpenSSH.

Даже Питер Брайт — технологический редактор Ars Technica — попросил внедрить механизм PTY через несколько дней, как я начал работать в команде Console:



И недавно ещё раз:



Что ж, мы наконец-то сделали это: мы создали псевдоконсоль для Windows:

Добро пожаловать в псевдоконсоль Windows (ConPTY)


С момента образования Console Team около четырёх лет назад группа занималась капитальным ремонтом консоли Windows и внутренних механизмов работы командной строки. При этом мы регулярно и тщательно рассматривали описанные выше вопросы и многие другие связанные вопросы и проблемы. Но инфраструктура и код были не готовы, чтобы сделать возможным выпуск псевдоконсоли… до настоящего момента!

Новая инфраструктура псевдоконсоли Windows (ConPTY), API и некоторые другие соответствующие изменения устранят/облегчат целый класс проблем… не ломая обратную совместимость с существующими приложениями командной строки!

Новые Win32 ConPTY API (официальная документация будет скоро опубликована) теперь доступны в последних инсайдерских сборках Windows 10 и соответствующих Windows 10 Insider Preview SDK. Они появятся в следующем крупном релизе Windows 10 (где-то осенью/зимой 2018).

Архитектура консоли/ConHost


Чтобы понять ConPTY, нужно изучить архитектуру консоли Windows, а точнее… ConHost!

Важно понимать, что хотя ConHost реализует всё, что вы видите и знаете как приложение Windows Console, но ConHost также содержит и реализует большую часть инфраструктуры командной строки Windows! Отныне же ConHost становится настоящим «консольным узлом», поддерживая все приложения командной строки и/или GUI-приложения, которые взаимодействуют с приложениями командной строки!

Как? Почему? Что? Давайте разберёмся подробнее.

Вот высокоуровневое представление внутренней архитектуры консоли/ConHost:



В сравнении с архитектурой из предыдущей статьи, ConHost теперь содержит несколько дополнительных модулей для обработки VT и новый модуль ConPTY, реализующий открытые API:

  • ConPTY API: новые программные интерфейсы Win32 ConPTY API обеспечивают механизм, похожий на модель POSIX PTY, но в преломлении на Windows.
  • VT Interactivity: получает входящий текст в кодировке UTF-8, преобразует каждый отображаемый текстовый символ в соответствующую запись INPUT_RECORD и сохраняет во входном буфере. Он также обрабатывает управляющие последовательности, такие как 0x03 (CTRL+C), преобразуя их в KEY_EVENT_RECORDS, которые производят соответствующее управляющее действие.
  • VT Renderer: генерирует последовательности VT, необходимые для перемещения курсора и рендеринга текста и стиля в областях выходного буфера, которые изменились с предыдущего кадра.

Хорошо, но что это на самом деле значит?

Как работают приложения командной строки Windows?


Чтобы лучше понять влияние новой инфраструктуры ConPTY, давайте рассмотрим, как до сих пор работали консольные приложения Windows и приложения командной строки.

Всякий раз, когда пользователь запускает приложение командной строки, такое как Cmd, PowerShell или ssh, Windows создаёт новый процесс Win32, в который загружает исполняемый двоичный файл приложения и любые зависимости (ресурсы или библиотеки).

Вновь созданный процесс обычно наследует дескрипторы stdin и stdout от своего родителя. Если родительский процесс был процессом Windows GUI, то дескрипторы stdin и stdout отсутствуют, поэтому Windows развернёт и присоединит новое приложение к новому экземпляру консоли. Связь между приложениями командной строки и их консолью передается через ConDrv.

Например, при запуске из экземпляра PowerShell без повышенных прав новый процесс приложения унаследует родительские дескрипторы stdin/stdout и, следовательно, получит входные данные и выдаст выходные данные в ту же консоль, что и родитель.

Здесь нужно немного оговориться, потому что в некоторых случаях приложения командной строки запускаются прикреплёнными к новому экземпляру консоли, особенно по соображениям безопасности, но обычно верно описание выше.

В конечном счёте, когда запускается приложение командной строки/оболочка, Windows подключает его к экземпляру консоли (ConHost.exe) через ConDrv:



Как работает ConHost?


Всякий раз, когда выполняется приложение командной строки, Windows подключает приложение к новому или существующему экземпляру ConHost. Приложение и его экземпляр консоли подключаются через драйвер консоли режима ядра (ConDrv), который отправляет/получает сообщения IOCTL, содержащие сериализованные запросы вызовов API и/или текстовые данные.

Исторически, как указано в предыдущей статье, работа ConHost сегодня относительно проста:

  • Пользователь генерирует входные данные с клавиатуры/мыши/ручки/тачпада, которые преобразуются в KEY_EVENT_RECORD или MOUSE_EVENT_RECORD и сохраняются во входном буфере.
  • Входной буфер опустошается по одной записи, выполняя запрошенные входные действия, такие как вывод текста на экран, перемещение курсора, копирование/вставка текста и т.д. Многие из этих действий приводят к изменению содержимого буфера вывода. Эти изменённые области записываются движком состояния ConHost.
  • В каждом кадре консоль отображает на дисплее изменённые области буфера вывода.

Когда приложение командной строки вызывает Windows Console API, то вызовы API сериализуются в сообщения IOCTL и отправляются через драйвер ConDrv. Он затем доставляет сообщения IOCTL на присоединённую консоль, которая декодирует и выполняет запрошенный вызов API. Возвращаемые/выходные значения сериализуются обратно в сообщение IOCTL и отправляются обратно в приложение через ConDrv.

ConHost: вклад в прошлое ради будущего


Microsoft старается по возможности поддерживать обратную совместимость с существующими приложениями и инструментами. Особенно для командной строки. На самом деле 32-битные версии Windows 10 ещё могут запускать многие/большинство 16-битных приложений и исполняемых файлов Win16!

Как упоминалось выше, одна из ключевых ролей ConHost заключается в предоставлении сервисов своим приложениям командной строки, особенно устаревшим приложениям, которые вызывают и полагаются на консольный API Win32. Теперь ConHost предлагает и новые сервисы:

  • Бесшовная PTY-подобная инфраструктура для коммуникации с современными консолями и терминалами
  • Модернизация устаревших/традиционных приложений командной строки
    • Получение и преобразование UTF-8 текста/VT во входные записи (как будто введённые пользователем)
    • Вызовы консольного API для размещаемого приложения, соответствующим образом обновляя его буфер вывода
    • Отображение изменённых областей буфера вывода в кодировке UTF-8, текст/VT

Ниже приведен пример, как современное консольное приложение общается с приложением командной строки через ConPTY ConHost.



В этой новой модели:

  1. Консоль:
    1. Создаёт собственные каналы связи
    2. Вызывает ConPTY API для создания ConPTY, заставляя Windows запустить экземпляр ConHost, подключённый к другому концу каналов
    3. Создаёт экземпляр приложения командной строки (например, PowerShell), подключённый к ConHost, как обычно
  2. ConHost:
    1. Читает UTF-8 текст/VT на входе и преобразует его в записи INPUT_RECORD, которые отправляются в приложение командной строки
    2. Выполняет вызовы API из приложения командной строки, которые могут изменять содержимое буфера вывода
    3. Отображает изменения в буфере вывода в кодировке UTF-8 (текст/VT) и отправляет полученный текст в свою консоль
  3. Приложение командной строки:
    1. Работает как обычно, читает входные данные и вызывает Console API, не имея никакого понятия, что его ConPTY ConHost переводит ввод и вывод из/в UTF-8!

Последний момент важен! Когда старое приложение командной строки использует вызовы к Console API вроде WriteConsoleOutput(...), то указанный текст записывается в соответствующий выходной буфер ConHost. Периодически ConHost отображает изменённые области буфера вывода в виде текста/VT, который отправляется через stdout обратно в консоль.

В конечном счёте, даже традиционные приложения командной строки снаружи «говорят» текстом/VT без каких-либо изменений!

Используя новую инфраструктуру ConPTY, сторонние консоли теперь могут напрямую взаимодействовать с современными и традиционными приложениями командной строки и обмениваться со всеми ними данными в тексте/VT.

Удалённое взаимодействие с приложениями командной строки Windows


Описанный выше механизм отлично работает на одном компьютере, но также помогает при взаимодействии, например, с инстансом PowerShell на удалённом компьютере Windows или в контейнере.

При удалённом запуске приложения командной строки (т.е. на удалённых компьютерах, серверах или в контейнерах) имеется проблема. Дело в том, что приложения командной строки на удалённых машинах общаются с локальным инстансом ConHost, потому что сообщения IOCTL не предназначены для передачи по сети. Как же передать ввод из локальной консоли на удалённую машину и как получить выдачу из приложения, запущенного там? Более того, что делать с машинами Mac и Linux, где есть терминалы, но нет Windows-совместимых консолей?

Таким образом, чтобы удалённо управлять машиной Windows, нам нужен какой-то брокер связи, который сможет прозрачно сериализовать данные по сети, управлять временем жизни экземпляра приложения и т.д.

Возможно, что-то вроде ssh?

К счастью, OpenSSH недавно портировали на Windows и добавили как дополнительную опцию Windows 10. PowerShell Core также использует ssh в качестве одного из поддерживаемых протоколов удалённого взаимодействия PowerShell Core Remoting. А для тех, кто работал в Windows PowerShell, удалённое взаимодействие Windows PowerShell Remoting по-прежнему остаётся приемлемым вариантом.

Давайте посмотрим, как сейчас OpenSSH для Windows позволяет удалённо управлять оболочками и приложениями командной строки Windows:



В данный момент OpenSSH включает некоторые нежелательные усложнения:

  1. Пользователь:
    1. Запускает ssh-клиент, а Windows подключает экземпляр консоли как обычно
    2. Вводит текст в консоль, которая отправляет нажатия клавиш в ssh-клиент
  2. ssh-клиент:
    1. Считывает входные данные как байты текстовых данных
    2. Отправляет текстовые данные по сети в прослушивающую службу sshd
  3. Служба sshd проходит несколько этапов:
    1. Запускает оболочку по умолчанию (например, Cmd), которая заставляет Windows создать и подключить новый экземпляр консоли
    2. Находит и подключается к консоли экземпляра Cmd
    3. Перемещает консоль за пределы экрана (и/или скрывает её)
    4. Отправляет входные данные, полученные от клиента ssh, в консоль вне экрана в качестве входных данных
  4. Экземпляр cmd работает так, как всегда:
    1. Собирает входные от сервиса sshd
    2. Выполняет работу
    3. Вызывает Console API для выдачи/стилизации текста, перемещения курсора и т.д.
  5. Присоединённая [внеэкранная] консоль:
    1. Выполняет вызовы API, обновляя выходной буфер
  6. Служба sshd:
    1. Скрапит буфер вывода внеэкранной консоли, находит различия, кодирует их в текст/VT и отправляет обратно...
  7. Клиенту ssh, который отправляет текст...
  8. Консоли, которая выводит текст на экран

Весело, правда? Вовсе нет! В такой ситуации многое может пойти наперекосяк, особенно в процессе имитации и отправки пользовательского ввода и очистки выходного буфера внеэкранной консоли. Это приводит к нестабильности, сбоям, повреждению данных, чрезмерному потреблению энергии и т.д. Кроме того, не все приложения выполняют работу по снятию не только самого текста, но и его свойств, из-за чего теряется форматирование и цвет!

Удалённая работа с использованием современных ConHost и ConPTY


Наверняка мы можем улучшить ситуацию? Да, конечно, можем — давайте сделаем несколько архитектурных изменений и применим наш новый ConPTY:



Из диаграммы видно, что схема изменилась следующим образом:

  1. Пользователь:
    1. Запускает ssh-клиент, а Windows подключает экземпляр консоли как обычно
    2. Вводит текст в консоль, которая отправляет нажатия клавиш в ssh-клиент
  2. ssh-клиент:
    1. Считывает входные данные как байты текстовых данных
    2. Отправляет текстовые данные по сети в прослушивающую службу sshd
  3. Служба sshd:
    1. Создаёт каналы stdin/stdout
    2. Вызывает ConPTY API для инициации ConPTY
    3. Запускает экземпляр Cmd, подключённый к другому концу ConPTY. Windows инициирует и подключает новый экземпляр ConHost
  4. Экземпляр cmd работает так, как всегда:
    1. Собирает входные от сервиса sshd
    2. Выполняет работу
    3. Вызывает Console API для выдачи/стилизации текста, перемещения курсора и т.д.
  5. Экземпляр ConPTY ConHost:
    1. Выполняет вызовы API, обновляя выходной буфер
    2. Отображает изменённые регионы выходного буфера как текст/VT в кодировке UTF-8, который отправляется обратно в консоль/терминал по ssh

Такой подход с использованием ConPTY явно чище и проще для службы sshd. Вызовы Windows Console API выполняются полностью в экземпляре ConHost приложения командной строки, который преобразует все видимые изменения в текст/VT. Кто бы не подключался к ConHost, ему необязательно знать, что приложение там вызывает Console API, а не генерирует текст/VT!

Согласитесь, что этот новый механизм удалённого взаимодействия ConPTY приводит к элегантной, последовательной и простой архитектуре. В сочетании с мощными функциями, встроенными в ConHost, поддержкой старых приложений и отображением изменений от приложений, вызывающих консольные Console API, в виде текста/VT, новая инфраструктура ConHost и ConPTY помогает нам перенести прошлое в будущее.

ConPTY API и как его использовать


ConPTY API доступен в текущей версии Windows 10 Insider Preview SDK.

К настоящему времени я уверен, что вам не терпится увидеть какой-то код ;)

Взглянем на декларации API:

// Creates a "Pseudo Console" (ConPTY).
HRESULT WINAPI CreatePseudoConsole(
                                _In_ COORD size,        // ConPty Dimensions
                                _In_ HANDLE hInput,     // ConPty Input
                                _In_ HANDLE hOutput,	// ConPty Output
                                _In_ DWORD dwFlags,     // ConPty Flags
                                _Out_ HPCON* phPC);     // ConPty Reference

// Resizes the given ConPTY to the specified size, in characters.
HRESULT WINAPI ResizePseudoConsole(_In_ HPCON hPC, _In_ COORD size);

// Closes the ConPTY and all associated handles. Client applications attached 
// to the ConPTY will also terminated. 
VOID WINAPI ClosePseudoConsole(_In_ HPCON hPC);

Вышеприведённый ConPTY по API по сути выставляет для использования три новых функции:

  • CreatePseudoConsole(size, hInput, hOutput, dwFlags, phPC)
    • Создаёт pty размерностью в w колонок и h строк, используя каналы, созданные вызывающей стороной:
      • size: ширина и высота (в символах) буфера ConPTY
      • hInput: для записи входных данных в PTY в виде последовательностей текст/VT в кодировке UTF-8
      • hOutput: для чтения выдачи из PTY в виде последовательностей текст/VT в кодировке UTF-8
      • dwFlags: Возможные значения:
        • PSEUDOCONSOLE_INHERIT_CURSOR: созданный ConPTY попытается наследовать позицию курсора родительского приложения терминала
      • phPC: дескриптор консоли для созданного ConPty
    • Возвращает: успех/неудача. В случае успеха phPC содержит дескриптор нового ConPty

    ResizePseudoConsole(hPC, size)
    • Изменяет размер внутреннего буфера ConPTY для отображения определённой ширины и высоты

    ClosePseudoConsole (hPC)
    • Закрывает ConPTY и все связанные дескрипторы. Клиентские приложения, подключённые к ConPTY, также завершаются, как если бы они выполнялись в окне консоли, которое закрывается

    Использование ConPTY API


    Ниже приведён небольшой пример кода вызова ConPTY API для создания псевдоконсоли и присоединения приложения командной строки к созданному ConPTY.

    Примечание: полная реализация будет опубликована в нашем репозитории GitHub

        // Note: Most error checking removed for brevity.
        
        // ...
        
        // Initializes the specified startup info struct with the required properties and
        // updates its thread attribute list with the specified ConPTY handle
        HRESULT InitializeStartupInfoAttachedToConPTY(STARTUPINFOEX* siEx, HPCON hPC)
        {
            HRESULT hr = E_UNEXPECTED;
            size_t size;
    
            siEx->StartupInfo.cb = sizeof(STARTUPINFOEX);
            
            // Create the appropriately sized thread attribute list
            InitializeProcThreadAttributeList(NULL, 1, 0, &size);
            std::unique_ptr<BYTE[]> attrList = std::make_unique<BYTE[]>(size);
            
            // Set startup info's attribute list & initialize it
            siEx->lpAttributeList = reinterpret_cast<PPROC_THREAD_ATTRIBUTE_LIST>(
                attrList.get());
            bool fSuccess = InitializeProcThreadAttributeList(
                siEx->lpAttributeList, 1, 0, (PSIZE_T)&size);
                
            if (fSuccess)
            {
                // Set thread attribute list's Pseudo Console to the specified ConPTY
                fSuccess = UpdateProcThreadAttribute(
                                lpAttributeList,
                                0,
                                PROC_THREAD_ATTRIBUTE_PSEUDOCONSOLE,
                                hPC,
                                sizeof(HPCON),
                                NULL,
                                NULL);
                return fSuccess ? S_OK : HRESULT_FROM_WIN32(GetLastError());
            }
            else
            {
                hr = HRESULT_FROM_WIN32(GetLastError());
            }
            return hr;
        }
        
        // ...
        
        HANDLE hOut, hIn;
        HANDLE outPipeOurSide, inPipeOurSide;
        HANDLE outPipePseudoConsoleSide, inPipePseudoConsoleSide;
        HPCON hPC = 0;
    
        // Create the in/out pipes:
        CreatePipe(&inPipePseudoConsoleSide, &inPipeOurSide, NULL, 0);
        CreatePipe(&outPipeOurSide, &outPipePseudoConsoleSide, NULL, 0);
    
        // Create the Pseudo Console, using the pipes
        CreatePseudoConsole(
            {80, 32}, 
            inPipePseudoConsoleSide, 
            outPipePseudoConsoleSide, 
            0, 
            &hPC);
    
        // Prepare the StartupInfoEx structure attached to the ConPTY.
        STARTUPINFOEX siEx{};
        InitializeStartupInfoAttachedToConPTY(&siEx, hPC);
    
        // Create the client application, using startup info containing ConPTY info
        wchar_t* commandline = L"c:\\windows\\system32\\cmd.exe";
        PROCESS_INFORMATION piClient{};
        fSuccess = CreateProcessW(
                        nullptr,
                        commandline,
                        nullptr,
                        nullptr,
                        TRUE,
                        EXTENDED_STARTUPINFO_PRESENT,
                        nullptr,
                        nullptr,
                        &siEx->StartupInfo,
                        &piClient);
    
        // ...

    Теперь cmd.exe подключён к экземпляру ConPTY, созданному CreatePseudoConsole(). Вызывающий объект использует созданные им дескрипторы ConPTY для записи и чтения в/из экземпляра Cmd. Размер псевдоконсоли изменяется с помощью ResizePseudoConsole(), а закрытие — по вызову ClosePseudoConsole().

    Запись в псевдоконсоль


    Запись входных данных в ConPTY осуществляется просто:

    // Input "echo Hello, World!", press enter to have cmd process the command,
    //  input an up arrow (to get the previous command), and enter again to execute.
    std::string helloWorld = "echo Hello, World!\n\x1b[A\n";
    DWORD dwWritten;
    WriteFile(hIn, helloWorld.c_str(), (DWORD)helloWorld.length(), &dwWritten, nullptr);

    Изменение размера псевдоконсоли


    Следующий сценарий показывает, как изменить размер ConPTY:

    // Suppose some other async callback triggered us to resize.
    //      This call will update the Terminal with the size we received.
    HRESULT hr = ResizePseudoConsole(hPC, {120, 30});

    Закрытие псевдоконсоли


    Нет ничего проще закрытия ConPTY:

    ClosePseudoConsole(hPC);

    Примечание: закрытие ConPTY завершит связанный ConHost и любые присоединённые клиенты.

    Призыв к действию!


    Внедрение ConPTY API — пожалуй, одно из самых фундаментальных и раскрепощающих изменений, произошедших с командной строкой Windows за последние годы… если не десятилетия!

    Мы уже портировали на ConPTY API некоторые инструменты Microsoft, а сейчас сотрудничаем с несколькими командами внутри Microsoft (подсистема Windows для Linux (WSL), команды Windows Containers, VSCode, Visual Studio и др.), а также с некоторыми независимыми разработчиками, включая @ConEmuMaximus5 — создателя потрясающей консоли ConEmu для Windows.

    Но нам нужна ваша помощь, чтобы распространить информацию и начать использовать новые ConPTY API.

    Разработчики приложений командной строки


    Если у вас традиционное приложение командной строки, то вы свободны и можете ничего не делать: ConHost сделает всю работу за вас. Программа может продолжать работать как раньше и полагаться на вызовы Console API. Приложение продолжит работать как обычно, в то же время получив дополнительный бонус в виде улучшенного, более качественного удалённого взаимодействия.

    Но если хотите, можно постепенно ввести новую поддержку VT, например, для новых функций — решать вам.

    С другой стороны, если вы сейчас планируете новые приложения командной строки Windows, то мы настоятельно рекомендуем транслировать текст/VT в кодировке UTF-8 вместо обращения к Console API: такой «разговор на VT» даст доступ ко многим функциям, которые не будут доступны через Console API (например, поддержка 16M RGB True Color).

    Разработчики сторонних консолей/сервисов


    Если вы работаете над автономным приложением консоли/терминала или интегрируете консоль в приложение, то мы настоятельно призываем вас как можно скорее изучить и принять новые ConPTY API: использование новых программных интерфейсов вместо старого механизма внеэкранной консоли, скорее всего, ликвидирует несколько классов ошибок, одновременно повысив стабильность, надёжность и производительность.

    В качестве примера команда VSCode в настоящее время решает вопрос (GitHub #45693) с несколькими проблемами, вызванными нынешним отсутствием псевдоконсоли Windows.

    Обнаружение ConPTY API


    Новые ConPTY API станут доступны в релизе Windows 10 осенью/зимой 2018 года.

    Для поддержки более ранних версий Windows, вероятно, потребуется в рантайме проверять, поддерживает ли текущая версия ConPTY. Как и с большинством Win32 API, эффективным способом проверки наличия API является использование метода Runtime Dynamic Linking путём вызова LoadLibrary() и GetProcAddress().

    Если текущая версия Windows поддерживает ConPTY, ваше приложение сможет найти и вызвать новые API ConPTY. Если нет, придётся вернуться к запутанным механизмам, используемым до сих пор.

    Итак, на чём мы остановились?


    Ещё одна длинная статья… это становится привычкой! Ещё раз, если вы смогли дочитать до этого места, СПАСИБО! :D

    Из приведённой информации можно сделать многие выводы, но важно подчеркнуть, почему мы вносим такие улучшения, а также суть реализованных изменений. Наша цель — устранить целый класс проблем и ограничений для разработчиков консольных и серверных приложений, а также сделать разработку кода для инфраструктуры командной строки Windows более мощной, последовательной и увлекательной.

    Будем рады отзывам через хаб обратной связи. О более сложных проблемах сообщайте в репозитории Windows Console на GitHub. А если есть вопросы, стучитесь ко мне в твиттер.
Поддержать автора
Поделиться публикацией

Комментарии 23

    +3
    Еще чуть чутьпара тысяч ведер и золотой ключик у нас в кармане и из виды таки получится linux.
      0
      imgur.com/a/Rw2tPjS
      Дык уже.
        0
        От этого эмулятора до нормальной ОС еще как до луны пешком.

        Но направление выбрано верное…
          –3
          Если бы у всех (и у меня в т.ч.) была кнопка «скачать» в этом дурацком магазине майкрософта, то было бы замечательно (а это, как бы, очень популярная проблема: www.google.ru/search?q=no+download+button+in+microsoft+store). А так, можно в некотором роде считать, что этого функционала нет, т.к. установить его физически не представляется возможным ниоткуда, кроме как из этого «магазина».

          Ну т.е. как бы есть возможность, но пользоваться можно лишь избранным, тем, кто не успел снести всякие xbox, триальные игры и прочие никчёмные вшитые сервисы из стандартной поставки.
            +1
            Я избранный. Мне это льстит.
            Можно ещё наверное поставить это дело из PS (Find-Package / Install-Package? не пробовал).
            Ну а если вы похерили store, то кто виноват? Можно и в apt прописать мусор вместо путей к репозиториям.
              –4
              Можно ещё наверное поставить это дело из PS (Find-Package / Install-Package? не пробовал).


              Я гуглил на эту тему и не нашёл вообще альтернатив. Возможно хреново и не слишком активно гуглил. Но пока Docker + ConEmu больше прельщают на этом поприще и не доставляют никаких проблем.

              Ну а store бы просто не работал, если бы пути потерялись кажется, разве нет? А тут всё есть, и списки, и комментарии, и аутентификация работает. А кнопки просто нет и всё. Причём исчезла как раз после того, как я начал чистить комп от откровенного шлака после очередного обновления (там, где «люди» появились).
                0
                1. В PS вводите
                Invoke-WebRequest -Uri https://aka.ms/wsl-ubuntu-1604 -OutFile ~/Ubuntu.zip -UseBasicParsing

                2. Распаковываете полученный Ubuntu.zip
                3. Заходите в распакованную папку
                4. Запускаете ubuntu.exe
                5. Проходите стандартную процедуру установки дистра в WLS(юзернейм, пароль и подождать чуть)
                6. ?????
                7. PROFIT
              0
              Включите WSL в «Панель управления -> Программы и компоненты -> Включение и отключение компонентов Windows». Затем перезагрузите машину и попробуйте поискать кнопку «скачать» еще раз.
                –1
                Эта схема уже проверенная. Не работает.

                Ну и вы не так поняли проблему. Кнопки «скачать» нет вообще ни на одной странице приложения в этом store. Не важно что это, какие требования, аутентифицирован в магазине или нет. Просто нету и всё.

                Я же не единственный такой с этой проблемой. Там на форуме M$ полно такого (выше ещё ссылка в гугл есть, где наглядно видны масштабы) и куча всяких рекомендаций, начиная с циклической установки всего что есть встроенного через PS, заканчивая правками в реестре и предложенными Вами способами. Лично мне вообще ничего не помогло.
                  +3
                  Вы не единственный по одной простой причине — существует очень много «чудиков», которые сначала «Причём исчезла как раз после того, как я начал чистить комп от откровенного шлака», а потом искренне удивляются что что-то сломалось и винят в этом только Microsoft. Когда же им говорят что сами и виноваты — не понимают о чем речь.
                0
                Store и Metro приложения у меня выпилены, но bash и убунту поставились. Кажется по этой инструкции.
                Все заработало, приложения написанные под никсы запускаются. WSL ставить не пришлось.
            –2

            Сколько понадобилось? 20 лет или 30? А вообще правильно говорят, что история движется по спирали...

              –4
              Шикарно, напомнило новости об открытии фонарного столба, остановки и платной колонки для воды. Функционал putty скоро будет достигнут или еще надо подождать?
                –2
                Интересно чего-то минусуете. Правда глаза жгёт? Ресайз ширины консоли в винде мышкой уже работает или надо mode con cols=120 писать. Шрифты досих пор в реестре надо прописывать. Убогий стандартный диалог выбора директории никому не упёрся? Копипаст с клавиатуры только в деятке добавили, а функционала readline досих пор нет не говоря уже о bash-completition. Даже когда в консоли MMC появится поиск это будет отельной статьей о том как это трудно было сделать.
                А так в целом молодцы конечно, стараетесь.
                С нетерпением ждём TurboVision dotCore dotNet чего уж там.
                  +1
                  Ресайз ширины консоли в винде мышкой уже работает

                  Работает во все стороны.

                  Шрифты досих пор в реестре надо прописывать.

                  Шрифт командной строки выбирается в её настройках.
                    –1
                    Остальные пункты?
                +1

                ШГ поправили, мыло убрали, ssh из коробки завезли, скоро из виндовс получится нормальный линукс. X11 сторонние годные уже есть(та же реализация из cygwin).


                Кстати тот же ssh до cmd и powershell оболочек был вполне себе поднимабелен в cygwin, еще лет эдак 5 назад, хотя, упражнение это было средней степени паршивости(и, возможно в бэкэнде, были те самые окошки консоли за пределами экрана).


                Ещё бы убогое поведение панельки переключения приложений починили, эх, мечты мечты...

                  0

                  Не совсем понятно, что нам мешает передать три дескриптора (StdIn, StdOut, StdErr) через STARTUPINFO при запуске дочернего процесса, а флагами CreateProcess() попросить систему не создавать ему консоль, и не наследовать её у родителя? К чему такие сложности с окошком консоли за шраницей экрана?

                    0
                    По идее, такое сработает только в простейшем случае. Вывод какого-нибудь FAR (или другой программы со сложным CUI) вы так не перехватите.
                      –2
                      Сначала сами сделали, апи которое нафиг никому было не нужно. А теперь героически придумывают как его переделать. Чтобы все на него перешли. Всё уже давно всё есть и работает. Или в том что есть опять "фатальный недостаток"?
                      putty: mc
                      image
                        0
                        Ваша желчь совершенно неуместна. Почему разработчики Windows NT выбрали подход с использованием специализированных функций API — описано в статье (особенно в первой части). С описанием плюсов и минусов обоих подходов. Нерелигиозному человеку должно быть понятно.
                          –2
                          Никакой желчи. Но извините как была у винды консоль унылым гавном так и осталась. Как были объекты с невменяемыми интерфейсами так-же и создаются плюс куча вариаций. Там где нужен интерфейс его не предоставляют, а где не нужен штампуют пачками. При этом хорошие идеи умудряются похоронить и выкать более «новые» и не менее недоработанное.
                      0
                      1. Job control. Stdin должна получать только foreground группа процессов ("cat | grep | sed" — это три процесса в одной группе), с возможностью менять её атомарно session leader-ом (bash).
                      2. Сигналы. Ctrl+C преобразуется в SIGTERM ядром. Это значит, что обычный юзер может послать сигнал привелигерованному процессу (обычно это sudo smthng), если оно привязано к его терминалу.

                    Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.

                    Самое читаемое