Observação
O acesso a essa página exige autorização. Você pode tentar entrar ou alterar diretórios.
O acesso a essa página exige autorização. Você pode tentar alterar os diretórios.
O Pseudoconsole do Windows, às vezes também conhecido como pseudoconsulta, ConPTY ou o Windows PTY, é um mecanismo projetado para criar um host externo para atividades de subsistema no modo de caractere que substituem a parte de interatividade do usuário da janela do host do console padrão.
Hospedar uma sessão pseudoconsole é um pouco diferente de uma sessão de console tradicional. As sessões de console tradicionais são iniciadas automaticamente quando o sistema operacional reconhece que um aplicativo de modo de caractere está prestes a ser executado. Por outro lado, uma sessão pseudoconsole e os canais de comunicação precisam ser criados pelo aplicativo de hospedagem antes de criar o processo com o aplicativo de modo de caractere filho a ser hospedado. O processo filho ainda será criado usando a função CreateProcess , mas com algumas informações adicionais que direcionarão o sistema operacional para estabelecer o ambiente apropriado.
Você pode encontrar informações adicionais sobre esse sistema na postagem inicial do blog de anúncios.
Exemplos completos de como usar o Pseudoconsole estão disponíveis em nosso repositório GitHub microsoft/terminal no diretório de exemplos.
Preparando os canais de comunicação
A primeira etapa é criar um par de canais de comunicação síncrona que serão fornecidos durante a criação da sessão pseudoconsole para comunicação bidirecional com o aplicativo hospedado. Esses canais são processados pelo sistema pseudoconsole usando ReadFile e WriteFile com E/S síncrona. Identificadores de dispositivo de E/S ou arquivo, como um fluxo de arquivo ou pipe, são aceitáveis, desde que uma estrutura OVERLAPPED não seja necessária para comunicação assíncrona.
Aviso
Para evitar condições de corrida e deadlocks, é altamente recomendável que cada um dos canais de comunicação seja atendido em um thread separado que mantenha seu próprio estado de buffer de cliente e fila de mensagens dentro do aplicativo. A manutenção de todas as atividades pseudoconsole no mesmo thread pode resultar em um deadlock em que um dos buffers de comunicação está preenchido e aguardando sua ação enquanto você tenta expedir uma solicitação de bloqueio em outro canal.
Criando o Pseudoconsole
Com os canais de comunicação estabelecidos, identifique o final de "leitura" do canal de entrada e o final de "gravação" do canal de saída. Esse par de identificadores é fornecido ao chamar CreatePseudoConsole para criar o objeto.
Na criação, é necessário um tamanho que represente as dimensões X e Y (em contagem de caracteres). Estas são as dimensões que serão aplicadas à superfície de exibição para a janela final da apresentação (terminal). Os valores são usados para criar um buffer na memória dentro do sistema pseudoconsole.
O tamanho do buffer fornece respostas para aplicativos do modo de caractere do cliente que investigam informações usando as funções de console do lado do cliente , como GetConsoleScreenBufferInfoEx , e determina o layout e o posicionamento do texto quando os clientes usam funções como WriteConsoleOutput.
Por fim, um campo de sinalizadores é fornecido na criação de um pseudoconsole para executar uma funcionalidade especial. Por padrão, defina isso como 0 para não ter nenhuma funcionalidade especial.
Neste momento, apenas um sinalizador especial está disponível para solicitar a herdância da posição do cursor de uma sessão de console já anexada ao chamador da API pseudoconsole. Isso se destina a ser usado em cenários mais avançados em que um aplicativo de hospedagem que está preparando uma sessão pseudoconsole também é um aplicativo de modo de caractere cliente de outro ambiente de console.
Um snippet de exemplo é fornecido abaixo utilizando CreatePipe para estabelecer um par de canais de comunicação e criar o pseudoconsole.
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;
}
// ...
}
Observação
Este snippet é incompleto e usado apenas para demonstração dessa chamada específica. Você precisará gerenciar o tempo de vida do HANDLEadequadamente. A falha ao gerenciar o tempo de vida do HANDLEs corretamente pode resultar em cenários de deadlock, especialmente com chamadas de E/S síncronas.
Após a conclusão da chamada CreateProcess para criar o aplicativo de modo de caractere cliente anexado ao pseudoconsole, os identificadores dados durante a criação devem ser liberados desse processo. Isso diminuirá a contagem de referência no objeto de dispositivo subjacente e permitirá que as operações de E/S detectem corretamente um canal quebrado quando a sessão pseudoconsole fechar sua cópia das alças.
Preparando-se para a criação do processo filho
A próxima fase é preparar a estrutura STARTUPINFOEX que transmitirá as informações pseudoconsole ao iniciar o processo filho.
Essa estrutura contém a capacidade de fornecer informações de inicialização complexas, incluindo atributos para criação de processo e thread.
Use InitializeProcThreadAttributeList de forma de chamada dupla para primeiro calcular o número de bytes necessários para manter a lista, alocar a memória solicitada e, em seguida, chamar novamente fornecendo o ponteiro de memória opaco para que ele seja configurado como a lista de atributos.
Em seguida, chame UpdateProcThreadAttribute passando a lista de atributos inicializados com o sinalizador PROC_THREAD_ATTRIBUTE_PSEUDOCONSOLE, o identificador pseudoconsole e o tamanho do identificador 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;
}
Criando o processo hospedado
Em seguida, chame CreateProcess passando a estrutura STARTUPINFOEX junto com o caminho para o executável e quaisquer informações de configuração adicionais, se aplicável. É importante definir o sinalizador de EXTENDED_STARTUPINFO_PRESENT ao chamar para alertar o sistema de que a referência pseudoconsole está contida nas informações estendidas.
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());
}
// ...
}
Observação
O fechamento da sessão pseudoconsole enquanto o processo hospedado ainda está sendo iniciado e a conexão pode resultar em uma caixa de diálogo de erro sendo mostrada pelo aplicativo cliente. A mesma caixa de diálogo de erro será mostrada se o processo hospedado receber um identificador pseudoconsole inválido para inicialização. Para o código de inicialização do processo hospedado, as duas circunstâncias são idênticas. A caixa de diálogo pop-up do aplicativo cliente hospedado em caso de falha será lida 0xc0000142 com uma mensagem localizada detalhando a falha na inicialização.
Comunicando-se com a sessão Pseudoconsole
Depois que o processo é criado com êxito, o aplicativo de hospedagem pode usar o final de gravação do pipe de entrada para enviar informações de interação do usuário para o pseudoconsole e o final de leitura do pipe de saída para receber informações de apresentação gráfica do pseudo console.
Cabe ao aplicativo de hospedagem decidir como lidar com outras atividades. O aplicativo de hospedagem pode iniciar uma janela em outro thread para coletar a entrada de interação do usuário e serializá-la no final de gravação do pipe de entrada para o pseudoconsole e o aplicativo de modo de caractere hospedado. Outro thread pode ser iniciado para esvaziar a extremidade de leitura do pipe de saída do pseudoconsole, decodificar as informações de sequência de terminal virtual e texto e apresentá-lo à tela.
Os threads também podem ser usados para retransmitir as informações dos canais pseudoconsole para um canal ou dispositivo diferente, incluindo uma rede para informações remotas para outro processo ou computador e evitando qualquer transcodificação local das informações.
Redimensionando o Pseudoconsole
Ao longo do runtime, pode haver uma circunstância pela qual o tamanho do buffer precisa ser alterado devido a uma interação do usuário ou a uma solicitação recebida fora da banda de outro dispositivo de exibição/interação.
Isso pode ser feito com a função ResizePseudoConsole especificando a altura e a largura do buffer em uma contagem 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);
}
Encerrando a sessão pseudoconsole
Para encerrar a sessão, chame a função ClosePseudoConsole com o identificador da criação pseudoconsole original. Todos os aplicativos anexados do modo de caractere do cliente, como o da chamada CreateProcess , serão encerrados quando a sessão for fechada. Se o filho original fosse um aplicativo do tipo shell que cria outros processos, todos os processos anexados relacionados na árvore também serão encerrados.
Aviso
O fechamento da sessão tem vários efeitos colaterais que podem resultar em uma condição de deadlock se o pseudoconsole for usado de forma síncrona de thread único. O ato de fechar a sessão pseudoconsole pode emitir uma atualização de quadro final para a hOutput qual deve ser drenada do buffer do canal de comunicações. Além disso, se PSEUDOCONSOLE_INHERIT_CURSOR tiver sido selecionado durante a criação do pseudoconsole, a tentativa de fechar o pseudoconsole sem responder à mensagem de consulta de herdamento do cursor (recebida hOutput e respondeda via hInput) poderá resultar em outra condição de deadlock. É recomendável que os canais de comunicação para o pseudoconsole sejam atendidos em threads individuais e permaneçam drenados e processados até serem interrompidos por conta própria pelo aplicativo cliente sair ou pela conclusão das atividades de teardown na chamada da função ClosePseudoConsole .