Nota
O acesso a esta página requer autorização. Podes tentar iniciar sessão ou mudar de diretório.
O acesso a esta página requer autorização. Podes tentar mudar de diretório.
O Pseudoconsole do Windows, às vezes também chamado de pseudoconsole, ConPTY ou PTY do Windows, é um mecanismo projetado para criar um host externo para atividades do subsistema de modo de caractere que substituem a parte de interatividade do usuário da janela de host do console padrão.
Hospedar uma sessão de 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. Em contraste, uma sessão de 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 este sistema na postagem inicial do blog do anúncio.
Exemplos completos de uso do Pseudoconsole estão disponíveis em nosso repositório GitHub microsoft/terminal no diretório de exemplos.
Preparação dos canais de comunicação
A primeira etapa é criar um par de canais de comunicação síncronos que serão fornecidos durante a criação da sessão de 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 arquivo ou dispositivo de E/S como um fluxo de arquivos ou pipe são aceitáveis, desde que uma estrutura OVERLAPPED não seja necessária para comunicação assíncrona.
Advertência
Para evitar condições de corrida e impasses, é 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 seu aplicativo. A manutenção de todas as atividades do pseudoconsole no mesmo thread pode resultar em um deadlock onde um dos buffers de comunicação é preenchido e aguarda sua ação enquanto você tenta enviar uma solicitação de bloqueio em outro canal.
Criando o pseudoconsole
Com os canais de comunicação que foram estabelecidos, identifique a extremidade "leitura" do canal de entrada e a extremidade "escrita" 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 (na contagem de caracteres). Estas são as dimensões que serão aplicadas à superfície de exibição para a janela de apresentação final (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 de modo de caractere cliente que investigam informações usando as funções de console do lado do cliente , como GetConsoleScreenBufferInfoEx , e dita o layout e o posicionamento do texto quando os clientes usam funções como WriteConsoleOutput.
Finalmente, um campo flags é fornecido na criação de um pseudoconsole para executar funcionalidades especiais. Por padrão, defina isso como 0 para não ter nenhuma funcionalidade especial.
No momento, apenas um sinalizador especial está disponível para solicitar a herança da posição do cursor de uma sessão de console já anexada ao chamador da API do pseudoconsole. Isso é destinado ao uso em cenários mais avançados, onde um aplicativo de hospedagem que está preparando uma sessão de pseudoconsole também é um aplicativo de modo de caractere cliente de outro ambiente de console.
Um trecho de exemplo é fornecido abaixo utilizando o 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 trecho está incompleto e é usado apenas para demonstração desta chamada específica. Você precisará gerenciar a vida útil do HANDLEs adequadamente. A falha ao gerenciar o tempo de vida dos HANDLEs corretamente pode resultar em cenários de impasse, 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 fornecidos 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 detetem corretamente um canal quebrado quando a sessão do pseudoconsole fechar sua cópia das alças.
Preparando-se para a Criação do Processo Infantil
A próxima fase é preparar a estrutura STARTUPINFOEX que transmitirá as informações do 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 processos e threads.
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, ligar novamente fornecendo o ponteiro de memória opaco para configurá-lo como a lista de atributos.
Em seguida, chame UpdateProcThreadAttribute passando a lista de atributos inicializados com o sinalizador PROC_THREAD_ATTRIBUTE_PSEUDOCONSOLE, o identificador de pseudoconsole e o tamanho do identificador de 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 ligar para alertar o sistema de que a referência do 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
Fechar a sessão do pseudoconsole enquanto o processo hospedado ainda está sendo iniciado e conectado pode resultar em uma caixa de diálogo de erro sendo mostrada pelo aplicativo cliente. A mesma caixa de diálogo de erro é mostrada se o processo hospedado recebe um identificador de 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 do pseudoconsole
Uma vez que o processo é criado com êxito, o aplicativo de hospedagem pode usar a extremidade de gravação do pipe de entrada para enviar informações de interação do usuário para o pseudoconsole e a extremidade de leitura do pipe de saída para receber informações de apresentação gráfica do pseudo console.
Cabe completamente ao aplicativo de hospedagem decidir como lidar com atividades adicionais. O aplicativo de hospedagem pode iniciar uma janela em outro thread para coletar a entrada de interação do usuário e serializá-la na extremidade de gravação do pipe de entrada para o pseudoconsole e o aplicativo de modo de caractere hospedado. Outro thread poderia ser iniciado para drenar a extremidade de leitura do tubo de saída para o pseudoconsole, decodificar o texto e as informações da sequência do terminal virtual e apresentá-las na tela.
Os threads também podem ser usados para retransmitir as informações dos canais do pseudoconsole para um canal ou dispositivo diferente, incluindo uma rede para informações remotas para outro processo ou máquina e evitando qualquer transcodificação local das informações.
Redimensionando o pseudoconsole
Ao longo do tempo de execução, pode haver uma circunstância pela qual o tamanho do buffer precisa ser alterado devido a uma interação do usuário ou 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 do Pseudoconsole
Para encerrar a sessão, chame a função ClosePseudoConsole com o identificador da criação original do pseudoconsole. Todos os aplicativos de modo de caractere de cliente anexados, como o da chamada CreateProcess , serão encerrados quando a sessão for fechada. Se o filho original era um aplicativo do tipo shell que cria outros processos, todos os processos anexados relacionados na árvore também serão encerrados.
Advertência
Fechar a 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 do pseudoconsole pode emitir uma atualização de quadro final para hOutput a qual deve ser drenada do buffer do canal de comunicação. Além disso, se PSEUDOCONSOLE_INHERIT_CURSOR foi selecionado durante a criação do pseudoconsole, tentar fechar o pseudoconsole sem responder à mensagem de consulta de herança do cursor (recebida hOutput e respondida via hInput) pode resultar em outra condição de deadlock. Recomenda-se que os canais de comunicação para o pseudoconsole sejam atendidos em threads individuais e permaneçam drenados e processados até serem quebrados por conta própria pela saída do aplicativo cliente ou pela conclusão das atividades de desmontagem ao chamar a função ClosePseudoConsole .