Nota:
El acceso a esta página requiere autorización. Puede intentar iniciar sesión o cambiar directorios.
El acceso a esta página requiere autorización. Puede intentar cambiar los directorios.
La pseudoconsola de Windows, a veces denominada pseudoconsola, ConPTY o Windows PTY, es un mecanismo diseñado para crear un host externo para las actividades del subsistema del modo de caracteres que reemplazan la parte de interactividad del usuario de la ventana host de consola predeterminada.
Hospedar una sesión de pseudoconsola es un poco diferente de una sesión de consola tradicional. Las sesiones de consola tradicionales se inician automáticamente cuando el sistema operativo reconoce que una aplicación en modo de caracteres está a punto de ejecutarse. Por el contrario, la aplicación de hospedaje debe crear una sesión de pseudoconsola y los canales de comunicación antes de crear el proceso con la aplicación en modo de caracteres secundario que se va a hospedar. El proceso secundario se seguirá creando mediante la función CreateProcess , pero con cierta información adicional que dirigirá al sistema operativo para establecer el entorno adecuado.
Puede encontrar información adicional sobre este sistema en la entrada de blog del anuncio inicial.
Los ejemplos completos de uso de la pseudoconsola están disponibles en nuestro repositorio de GitHub microsoft/terminal en el directorio de ejemplos.
Preparación de los canales de comunicación
El primer paso es crear un par de canales de comunicación sincrónicos que se proporcionarán durante la creación de la sesión de pseudoconsola para la comunicación bidireccional con la aplicación hospedada. El sistema de pseudoconsola procesa estos canales mediante ReadFile y WriteFile con E/S sincrónica. Los identificadores de dispositivo de E/S o archivos, como una secuencia de archivos o canalización, son aceptables siempre y cuando no se requiera una estructura SUPERPUESTA para la comunicación asincrónica.
Advertencia
Para evitar las condiciones de carrera y los interbloqueos, se recomienda encarecidamente que cada uno de los canales de comunicación se encuentre en un subproceso independiente que mantenga su propio estado de búfer de cliente y cola de mensajería dentro de la aplicación. El mantenimiento de todas las actividades de pseudoconsola en el mismo subproceso puede dar lugar a un interbloqueo donde se rellena uno de los búferes de comunicaciones y espera su acción mientras intenta enviar una solicitud de bloqueo en otro canal.
Creación de la pseudoconsola
Con los canales de comunicación que se han establecido, identifique el final "read" del canal de entrada y el final "write" del canal de salida. Este par de identificadores se proporciona al llamar a CreatePseudoConsole para crear el objeto .
Al crear, se requiere un tamaño que represente las dimensiones X e Y (en recuento de caracteres). Estas son las dimensiones que se aplicarán a la superficie de presentación para la ventana de presentación final (terminal). Los valores se usan para crear un búfer en memoria dentro del sistema de pseudoconsola.
El tamaño del búfer proporciona respuestas a las aplicaciones en modo de caracteres de cliente que sondeen para obtener información mediante funciones de consola del lado cliente como GetConsoleScreenBufferInfoEx y dictan el diseño y el posicionamiento del texto cuando los clientes usan funciones como WriteConsoleOutput.
Por último, se proporciona un campo de marcas al crear una pseudoconsola para realizar una funcionalidad especial. De forma predeterminada, establézcalo en 0 para que no tenga ninguna funcionalidad especial.
En este momento, solo hay disponible una marca especial para solicitar la heredar de la posición del cursor desde una sesión de consola ya asociada al autor de la llamada de la API de pseudoconsola. Esto está pensado para su uso en escenarios más avanzados en los que una aplicación de hospedaje que prepara una sesión de pseudoconsola también es una aplicación de modo de caracteres de cliente de otro entorno de consola.
A continuación se proporciona un fragmento de código de ejemplo que usa CreatePipe para establecer un par de canales de comunicación y crear la pseudoconsola.
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;
}
// ...
}
Nota:
Este fragmento de código está incompleto y se usa solo para la demostración de esta llamada específica. Tendrá que administrar la duración de los controladoresde forma adecuada. Si no se administra correctamente la duración de HANDLE, se pueden producir escenarios de interbloqueo, especialmente con llamadas de E/S sincrónicas.
Tras finalizar la llamada a CreateProcess para crear la aplicación del modo de caracteres de cliente asociada a la pseudoconsola, los identificadores proporcionados durante la creación deben liberarse de este proceso. Esto reducirá el recuento de referencias en el objeto de dispositivo subyacente y permitirá que las operaciones de E/S detecten correctamente un canal roto cuando la sesión de pseudoconsola cierre su copia de los identificadores.
Preparación para la creación del proceso secundario
La siguiente fase consiste en preparar la estructura STARTUPINFOEX que transmitirá la información de pseudoconsola al iniciar el proceso secundario.
Esta estructura contiene la capacidad de proporcionar información de inicio compleja, incluidos los atributos para la creación de procesos y subprocesos.
Use InitializeProcThreadAttributeList de forma de doble llamada para calcular primero el número de bytes necesarios para contener la lista, asignar la memoria solicitada y, a continuación, llamar de nuevo al puntero de memoria opaco para que se configure como la lista de atributos.
A continuación, llame a UpdateProcThreadAttribute pasando la lista de atributos inicializados con la marca PROC_THREAD_ATTRIBUTE_PSEUDOCONSOLE, el identificador de pseudoconsola y el tamaño del identificador de pseudoconsola.
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;
}
Creación del proceso hospedado
A continuación, llame a CreateProcess pasando la estructura STARTUPINFOEX junto con la ruta de acceso al archivo ejecutable y cualquier información de configuración adicional si procede. Es importante establecer la marca EXTENDED_STARTUPINFO_PRESENT al llamar a para alertar al sistema de que la referencia de pseudoconsola se encuentra en la información extendida.
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());
}
// ...
}
Nota:
Cerrar la sesión de pseudoconsola mientras el proceso hospedado todavía se está iniciando y la conexión puede dar lugar a que la aplicación cliente muestre un cuadro de diálogo de error. Se muestra el mismo cuadro de diálogo de error si el proceso hospedado recibe un identificador de pseudoconsola no válido para el inicio. En el código de inicialización del proceso hospedado, las dos circunstancias son idénticas. El cuadro de diálogo emergente de la aplicación cliente hospedada en caso de error leerá 0xc0000142 con un mensaje localizado que detalla si no se inicializa.
Comunicación con la sesión de pseudoconsola
Una vez creado correctamente el proceso, la aplicación de hospedaje puede usar el final de escritura de la canalización de entrada para enviar información de interacción del usuario a la pseudoconsola y el final de lectura de la canalización de salida para recibir información de presentación gráfica de la pseudoconsola.
Es completamente necesario que la aplicación de hospedaje decida cómo controlar la actividad adicional. La aplicación de hospedaje podría iniciar una ventana en otro subproceso para recopilar la entrada de interacción del usuario y serializarla en el extremo de escritura de la canalización de entrada para la pseudoconsola y la aplicación en modo de caracteres hospedada. Otro subproceso podría iniciarse para purgar el final de lectura de la canalización de salida para la pseudoconsola, descodificar el texto y la información de la secuencia del terminal virtual y presentarlo a la pantalla.
Los subprocesos también se pueden usar para retransmitir la información de los canales de pseudoconsola a un canal o dispositivo diferente, incluida una red para la información remota a otro proceso o máquina y evitar cualquier transcodificación local de la información.
Cambio del tamaño de la pseudoconsola
A lo largo del tiempo de ejecución, puede haber una circunstancia por la que se debe cambiar el tamaño del búfer debido a una interacción del usuario o a una solicitud recibida fuera de banda desde otro dispositivo de visualización o interacción.
Esto se puede hacer con la función ResizePseudoConsole que especifica el alto y el ancho del búfer en un recuento de caracteres.
// 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);
}
Finalizar la sesión de pseudoconsola
Para finalizar la sesión, llame a la función ClosePseudoConsole con el identificador de la creación de pseudoconsola original. Las aplicaciones de modo de caracteres de cliente adjuntas, como la de la llamada CreateProcess , se finalizarán cuando se cierre la sesión. Si el elemento secundario original era una aplicación de tipo shell que crea otros procesos, también se finalizarán los procesos adjuntos relacionados del árbol.
Advertencia
Cerrar la sesión tiene varios efectos secundarios que pueden dar lugar a una condición de interbloqueo si la pseudoconsola se usa de forma sincrónica de un solo subproceso. El acto de cerrar la sesión de pseudoconsola puede emitir una actualización final del marco al hOutput que se debe purgar desde el búfer del canal de comunicaciones. Además, si PSEUDOCONSOLE_INHERIT_CURSOR se seleccionó al crear la pseudoconsola, intentar cerrar la pseudoconsola sin responder al mensaje de consulta de herencia del cursor (recibido y hOutput respondido a través hInputde ) puede dar lugar a otra condición de interbloqueo. Se recomienda que los canales de comunicación para la pseudoconsola se atenden en subprocesos individuales y permanezcan purgados y procesados hasta que la aplicación cliente salga de su propio acuerdo o mediante la finalización de actividades de desmontaje en la llamada a la función ClosePseudoConsole .