Примечание.
Для доступа к этой странице требуется авторизация. Вы можете попробовать войти или изменить каталоги.
Для доступа к этой странице требуется авторизация. Вы можете попробовать изменить каталоги.
Псевдоконсол Windows, иногда также называемый псевдоконсолем, ConPTY или Windows PTY, — это механизм, предназначенный для создания внешнего узла для действий подсистемы в режиме символов, которые заменяют часть пользовательского взаимодействия окна узла консоли по умолчанию.
Размещение псевдоконсоля сеанса немного отличается от традиционного сеанса консоли. Традиционные сеансы консоли автоматически запускаются, когда операционная система распознает, что приложение в режиме символов будет запущено. В отличие от этого, сеанс псевдоконсоля и каналы связи должны быть созданы приложением размещения перед созданием процесса с дочерним приложением в режиме символов для размещения. Дочерний процесс по-прежнему будет создан с помощью функции CreateProcess , но с некоторыми дополнительными сведениями, которые будут направлять операционную систему для установления соответствующей среды.
Дополнительные справочные сведения об этой системе можно найти в записи блога о первоначальном объявлении.
Полные примеры использования Псевдоконсоля доступны в нашем репозитории GitHub в каталоге примеров.
Подготовка каналов связи
Первым шагом является создание пары синхронных каналов связи, которые будут предоставлены во время создания сеанса псевдоконсоля для двунаправленного взаимодействия с размещенным приложением. Эти каналы обрабатываются системой псевдоконсоля с помощью ReadFile и WriteFile с синхронным вводом-выводом. Устройства файлов или операций ввода-вывода, такие как поток файлов или канал, допустимы, если для асинхронного взаимодействия не требуется структура ПЕРЕКРЫВАЮЩИХСЯ операций ввода-вывода.
Предупреждение
Чтобы предотвратить условия гонки и взаимоблокировки, настоятельно рекомендуется обслуживать каждый из каналов связи в отдельном потоке, который поддерживает собственное состояние буфера клиента и очередь обмена сообщениями внутри приложения. Обслуживание всех псевдоконсолейных действий в одном потоке может привести к взаимоблокировке, когда один из буферов связи заполняется и ожидает выполнения действия при попытке отправить запрос блокировки на другой канал.
Создание Псевдоконсоля
Установив каналы связи, определите конец входного канала чтения и конец выходного канала записи. Эта пара дескрипторов предоставляется при вызове CreatePseudoConsole для создания объекта.
При создании требуется размер, представляющий измерения X и Y (в количестве символов). Это измерения, которые будут применяться к поверхности отображения для окончательного (терминала) окна презентации. Значения используются для создания буфера в памяти в псевдоконсоле системы.
Размер буфера предоставляет ответы на клиентские приложения в режиме символов, которые проверяют информацию с помощью функций консоли на стороне клиента , таких как GetConsoleScreenBufferInfoEx, и диктует макет и расположение текста при использовании клиентами таких функций, как WriteConsoleOutput.
Наконец, поле флагов предоставляется при создании псевдоконсоля для выполнения специальных функций. По умолчанию задайте для этого значение 0, чтобы не было специальных функций.
В настоящее время только один специальный флаг доступен для запроса наследования позиции курсора из сеанса консоли, уже подключенного к вызывающей функции API псевдоконсоля. Это предназначено для использования в более сложных сценариях, где приложение размещения, которое готовит псевдоконсольный сеанс, также является приложением режима символов клиента другой консоли.
Пример фрагмента кода представлен ниже, используя CreatePipe для создания пары каналов связи и создания псевдоконсоля.
HRESULT SetUpPseudoConsole(COORD size)
{
HRESULT hr = S_OK;
// Create communication channels
// - Close these after CreateProcess of child application with pseudoconsole object.
HANDLE inputReadSide, outputWriteSide;
// - Hold onto these and use them for communication with the child through the pseudoconsole.
HANDLE outputReadSide, inputWriteSide;
if (!CreatePipe(&inputReadSide, &inputWriteSide, NULL, 0))
{
return HRESULT_FROM_WIN32(GetLastError());
}
if (!CreatePipe(&outputReadSide, &outputWriteSide, NULL, 0))
{
return HRESULT_FROM_WIN32(GetLastError());
}
HPCON hPC;
hr = CreatePseudoConsole(size, inputReadSide, outputWriteSide, 0, &hPC);
if (FAILED(hr))
{
return hr;
}
// ...
}
Замечание
Этот фрагмент является неполным и используется только для демонстрации этого конкретного вызова. Вам потребуется управлять временем существования ОБРАБОТЧИКасоответствующим образом. Не удалось правильно управлять временем существования ОБРАБОТЧИКА, что может привести к взаимоблокировкам, особенно с синхронными вызовами ввода-вывода.
После завершения вызова CreateProcess для создания приложения в режиме символов клиента, присоединенного к псевдоконсолю, дескриптор, заданный во время создания, должен быть освобожден из этого процесса. Это уменьшит количество ссылок на базовый объект устройства и позволит операциям ввода-вывода правильно обнаружить сломанный канал, когда сеанс псевдоконсоля закрывает его копию дескрипторов.
Подготовка к созданию дочернего процесса
Следующий этап — подготовить структуру STARTUPINFOEX , которая будет передавать псевдоконсольную информацию при запуске дочернего процесса.
Эта структура содержит возможность предоставлять сложные сведения о запуске, включая атрибуты для создания процесса и потока.
Используйте InitializeProcThreadAttributeList в режиме двойного вызова, чтобы сначала вычислить количество байтов, необходимых для хранения списка, выделить запрошенную память, а затем снова вызвать указатель на непрозрачную память, чтобы он был настроен в качестве списка атрибутов.
Затем вызовите UpdateProcThreadAttribute , передав список инициализированных атрибутов с флагом PROC_THREAD_ATTRIBUTE_PSEUDOCONSOLE, дескриптор псевдоконсоля и размер дескриптора псевдоконсоля.
HRESULT PrepareStartupInformation(HPCON hpc, STARTUPINFOEX* psi)
{
// Prepare Startup Information structure
STARTUPINFOEX si;
ZeroMemory(&si, sizeof(si));
si.StartupInfo.cb = sizeof(STARTUPINFOEX);
// Discover the size required for the list
size_t bytesRequired;
InitializeProcThreadAttributeList(NULL, 1, 0, &bytesRequired);
// Allocate memory to represent the list
si.lpAttributeList = (PPROC_THREAD_ATTRIBUTE_LIST)HeapAlloc(GetProcessHeap(), 0, bytesRequired);
if (!si.lpAttributeList)
{
return E_OUTOFMEMORY;
}
// Initialize the list memory location
if (!InitializeProcThreadAttributeList(si.lpAttributeList, 1, 0, &bytesRequired))
{
HeapFree(GetProcessHeap(), 0, si.lpAttributeList);
return HRESULT_FROM_WIN32(GetLastError());
}
// Set the pseudoconsole information into the list
if (!UpdateProcThreadAttribute(si.lpAttributeList,
0,
PROC_THREAD_ATTRIBUTE_PSEUDOCONSOLE,
hpc,
sizeof(hpc),
NULL,
NULL))
{
HeapFree(GetProcessHeap(), 0, si.lpAttributeList);
return HRESULT_FROM_WIN32(GetLastError());
}
*psi = si;
return S_OK;
}
Создание размещенного процесса
Затем вызовите CreateProcess , передав структуру STARTUPINFOEX , а также путь к исполняемому файлу и любые дополнительные сведения о конфигурации, если это применимо. Важно задать флаг EXTENDED_STARTUPINFO_PRESENT при вызове системы, которая содержится в расширенной информации.
HRESULT SetUpPseudoConsole(COORD size)
{
// ...
PCWSTR childApplication = L"C:\\windows\\system32\\cmd.exe";
// Create mutable text string for CreateProcessW command line string.
const size_t charsRequired = wcslen(childApplication) + 1; // +1 null terminator
PWSTR cmdLineMutable = (PWSTR)HeapAlloc(GetProcessHeap(), 0, sizeof(wchar_t) * charsRequired);
if (!cmdLineMutable)
{
return E_OUTOFMEMORY;
}
wcscpy_s(cmdLineMutable, charsRequired, childApplication);
PROCESS_INFORMATION pi;
ZeroMemory(&pi, sizeof(pi));
// Call CreateProcess
if (!CreateProcessW(NULL,
cmdLineMutable,
NULL,
NULL,
FALSE,
EXTENDED_STARTUPINFO_PRESENT,
NULL,
NULL,
&siEx.StartupInfo,
&pi))
{
HeapFree(GetProcessHeap(), 0, cmdLineMutable);
return HRESULT_FROM_WIN32(GetLastError());
}
// ...
}
Замечание
Закрытие сеанса псевдоконсоля в то время как размещенный процесс по-прежнему запускается и подключение может привести к возникновению диалогового окна ошибки, отображаемого клиентским приложением. То же диалоговое окно ошибки отображается, если размещенный процесс получает недопустимый псевдоконсоле-дескриптор для запуска. В коде инициализации размещенного процесса два обстоятельства идентичны. Всплывающее диалоговое окно из размещенного клиентского приложения при сбое будет считываться 0xc0000142 с локализованным сообщением о сбое инициализации.
Взаимодействие с сеансом Псевдоконсоля
После успешного создания процесса приложение размещения может использовать конец записи входной трубы для отправки сведений о взаимодействии пользователей в псевдоконсоль и конец чтения выходного канала для получения графических сведений о презентации из псевдоконсоля.
Это полностью до приложения размещения, чтобы решить, как обрабатывать дальнейшие действия. Приложение размещения может запустить окно в другом потоке, чтобы собрать входные данные взаимодействия пользователя и сериализовать его в конце входной строки для псевдоконсоля и размещенного приложения в режиме символов. Другой поток можно запустить для очистки конца выходного канала для псевдоконсоля, декодировать текст и сведения о последовательности виртуальных терминалов и представить его на экране.
Потоки также можно использовать для передачи информации из псевдоконсольных каналов на другой канал или устройство, включая сеть для удаленной информации в другой процесс или компьютер и избегая локального перекодирования информации.
Изменение размера псевдоконсоля
Во время выполнения может возникнуть обстоятельства, с помощью которых размер буфера необходимо изменить из-за взаимодействия пользователя или запроса, полученного из другого устройства отображения или взаимодействия.
Это можно сделать с помощью функции ResizePseudoConsole , указывающей высоту и ширину буфера в количестве символов.
// Theoretical event handler function with theoretical
// event that has associated display properties
// on Source property.
void OnWindowResize(Event e)
{
// Retrieve width and height dimensions of display in
// characters using theoretical height/width functions
// that can retrieve the properties from the display
// attached to the event.
COORD size;
size.X = GetViewWidth(e.Source);
size.Y = GetViewHeight(e.Source);
// Call pseudoconsole API to inform buffer dimension update
ResizePseudoConsole(m_hpc, size);
}
Завершение сеанса Псевдоконсоля
Чтобы завершить сеанс, вызовите функцию ClosePseudoConsole с дескриптором из исходного псевдоконсоля. При закрытии сеанса все подключенные приложения в режиме символов клиента, такие как один из вызовов CreateProcess , будут прекращены. Если исходный дочерний элемент был приложением типа оболочки, которое создает другие процессы, все связанные присоединенные процессы в дереве также будут завершены.
Предупреждение
Закрытие сеанса имеет несколько побочных эффектов, которые могут привести к взаимоблокировке, если псевдоконсол используется в однопоточном синхронном режиме. Действие закрытия сеанса псевдоконсоля может привести к окончательному обновлению кадра, к hOutput которому следует стекать из буфера канала связи. Кроме того, если PSEUDOCONSOLE_INHERIT_CURSOR при создании псевдоконсоля выбрано, попытка закрыть псевдоконсол без ответа на сообщение запроса наследования курсора (полученное hOutput и отправленное через hInput) может привести к другому условию взаимоблокировки. Рекомендуется, чтобы каналы связи для псевдоконсоля обслуживались на отдельных потоках и остаются стекаемыми и обрабатываются до тех пор, пока не завершится их собственное согласие клиентским приложением, выходом или завершением действий разрыва при вызове функции ClosePseudoConsole .