Redirecionamento de Saída de Processos Externos em C++ Usando CreateProcess e Pipes Anônimos

Em muitas aplicações desktop, é necessário invocar utilitários de linha de comando (como ferramentas de compressão ou clientes de controle de versão) para realizar tarefas específicas. No entanto, a execução desses utilitários não deve exibir a janela do console, e a saída padrão (stdout) e de erro (stderr) deve ser capturada e exibida na interface gráfica da aplicação principal.

Para alcançar esse comportamento no Windows utilizando C++, é preciso resolver dois desafios: invocar o processo filho de forma oculta e redirecionar seus fluxos de entrada e saída.

Seleção da API do Windows

O Windows oferece três APIs principais para execução de processos externos:

  • WinExec: A mais simples, mas limitada e considerada obsoleta.
  • ShellExecute: Mais flexível, permite abrir arquivos com suas associações padrão e URLs, mas não oferece controle granular sobre o processo.
  • CreateProcess: A API mais robusta e complexa. Permite configurar atributos de segurança, herança de identificadores (handles) e redirecionamento de fluxos padrão.

Para capturar a saída do console, o CreateProcess é a escolha obrigatória, pois permite a manipulação direta dos identificadores de entrada e saída padrão do novo processo.

Mecanismo de Redirecionamento: Pipes Aônimos

Para interceptar os dados que seriam exibidos no console, utilizamos pipes anônimos. Um pipe anônimo é um canal de comunicação unidirecional que conecta dois processos. No contexto de redirecionamento, o processo pai cria o pipe e passa o identificador de gravação para o processo filho. Tudo o que o filho escrever na saída padrão será direcionado para o pipe, permitindo que o pai leia esses dados posteriormente.

Para que isso funcione, o identificador do pipe deve ser configurado como herdável, e o processo filho deve ser instruído a usar esse identificador como sua saída padrão.

Implementação Prática

Abaixo está a implementação detalhada em C++ utilizando a API Win32. O código foi estruturado para criar um pipe de saída (para capturar stdout/stderr) e um pipe de entrada (para enviar comandos ao processo filho).

1. Configuração de Atributos de Segurança e Criação dos Pipes

Primeiro, definimos a estrutura SECURITY_ATTRIBUTES para permitir que os handles sejam herdados pelo processo filho. Em seguida, criamos dois pipes anônimos: um para a saída do filho (lido pelo pai) e outro para a entrada do filho (escrito pelo pai).

#include <windows.h>
#include <iostream>
#include <string>
#include <vector>

// Estrutura para gerenciar os handles dos pipes
struct PipeHandles {
    HANDLE hRead;
    HANDLE hWrite;
};

bool InitializePipes(PipeHandles& outPipe, PipeHandles& inPipe) {
    SECURITY_ATTRIBUTES secAttr;
    secAttr.nLength = sizeof(SECURITY_ATTRIBUTES);
    secAttr.bInheritHandle = TRUE; // Permite herança pelo processo filho
    secAttr.lpSecurityDescriptor = nullptr;

    // Cria pipe para captura de saída (Filho escreve, Pai lê)
    if (!CreatePipe(&outPipe.hRead, &outPipe.hWrite, &secAttr, 0)) {
        return false;
    }
    // Garante que o handle de leitura não seja herdado pelo filho
    SetHandleInformation(outPipe.hRead, HANDLE_FLAG_INHERIT, 0);

    // Cria pipe para entrada de dados (Pai escreve, Filho lê)
    if (!CreatePipe(&inPipe.hRead, &inPipe.hWrite, &secAttr, 0)) {
        return false;
    }
    // Garante que o handle de escrita não seja herdado pelo filho
    SetHandleInformation(inPipe.hWrite, HANDLE_FLAG_INHERIT, 0);

    return true;
}

2. Configuração e Lançamento do Processo Filho

Com os pipes criados, configuramos a estrutura STARTUPINFO para mapear os handles de entrada, saída e erro padrão do processo filho para os handles de escrita e leitura dos pipes.

bool LaunchHiddenProcess(const std::wstring& commandLine, 
                         const PipeHandles& outPipe, 
                         const PipeHandles& inPipe, 
                         PROCESS_INFORMATION& procInfo) {
    
    STARTUPINFO startInfo;
    ZeroMemory(&startInfo, sizeof(STARTUPINFO));
    startInfo.cb = sizeof(STARTUPINFO);
    
    // Mapeamento dos fluxos padrão para os pipes
    startInfo.hStdError = outPipe.hWrite;
    startInfo.hStdOutput = outPipe.hWrite;
    startInfo.hStdInput = inPipe.hRead;
    startInfo.dwFlags |= STARTF_USESTDHANDLES;

    // Oculta a janela do console
    startInfo.dwFlags |= STARTF_USESHOWWINDOW;
    startInfo.wShowWindow = SW_HIDE;

    ZeroMemory(&procInfo, sizeof(PROCESS_INFORMATION));

    // Cria um buffer gravável para a linha de comando, exigência da API
    std::vector<wchar_t> cmdBuffer(commandLine.begin(), commandLine.end());
    cmdBuffer.push_back(L'\0');

    // Cria o processo
    BOOL success = CreateProcess(
        nullptr,                   // Nome do aplicativo
        cmdBuffer.data(),          // Linha de comando
        nullptr,                   // Atributos de segurança do processo
        nullptr,                   // Atributos de segurança da thread
        TRUE,                      // Herda handles (CRUCIAL)
        CREATE_NO_WINDOW,          // Flags de criação (oculta o console)
        nullptr,                   // Variáveis de ambiente
        nullptr,                   // Diretório de trabalho
        &startInfo,                // Informações de inicialização
        &procInfo                  // Informações do processo
    );

    if (success) {
        // O pai não precisa dos handles que foram passados para o filho
        CloseHandle(outPipe.hWrite);
        CloseHandle(inPipe.hRead);
    }

    return success != FALSE;
}

3. Leitura da Saída do Processo

Após o lançamento, o processo pai pode ler continuamente o pipe de saída até que o processo filho seja encerrado e o pipe seja fechado.

std::string ReadProcessOutput(HANDLE hReadPipe) {
    std::string result;
    DWORD bytesRead;
    char buffer[4096];

    // Loop de leitura até o fim do fluxo
    while (ReadFile(hReadPipe, buffer, sizeof(buffer) - 1, &bytesRead, nullptr) && bytesRead > 0) {
        buffer[bytesRead] = '\0';
        result += buffer;
    }

    return result;
}

int main() {
    PipeHandles outPipe = {nullptr, nullptr};
    PipeHandles inPipe = {nullptr, nullptr};
    PROCESS_INFORMATION procInfo;

    if (!InitializePipes(outPipe, inPipe)) {
        std::cerr << "Falha ao criar os pipes." << std::endl;
        return 1;
    }

    std::wstring cmd = L"cmd.exe /c dir C:\\";
    
    if (!LaunchHiddenProcess(cmd, outPipe, inPipe, procInfo)) {
        std::cerr << "Falha ao iniciar o processo." << std::endl;
        return 1;
    }

    // Aguarda o processo terminar para garantir que toda a saída foi escrita
    WaitForSingleObject(procInfo.hProcess, INFINITE);

    // Lê a saída capturada
    std::string output = ReadProcessOutput(outPipe.hRead);
    std::cout << "Saída capturada:\n" << output << std::endl;

    // Limpeza de recursos
    CloseHandle(outPipe.hRead);
    CloseHandle(inPipe.hWrite);
    CloseHandle(procInfo.hProcess);
    CloseHandle(procInfo.hThread);

    return 0;
}

Fluxo de Execução e Considerações

O ciclo de vida dessa integração segue uma ordem estrita para evitar deadlocks e vazamento de recursos:

  1. Criação dos Pipes: Estabelece os canais de comunicação com atributos de herança ativados.
  2. Configuração do STARTUPINFO: Vincula os fluxos padrão do futuro processo aos pipes.
  3. Invocação do CreateProcess: Inicia o utilitário externo. Imediatamente após, o processo pai deve fechar as cópias dos handles que pertencem exclusivamente ao filho (ex: o handle de escrita do pipe de saída).
  4. Leitura e Escrita: O pai lê o pipe de saída. Se o processo filho exigir entrada interativa, o pai escreve no pipe de entrada.
  5. Encerramento: O pai aguarda o fim do processo (WaitForSingleObject) e libera todos os handles restantes.

É fundamental fechar os handles não utilizados em ambos os processos. Se o pai mantiver aberto o handle de escrita do pipe de saída, a função ReadFile nunca retornará FALSE indicando o fim do fluxo, resultando em um bloqueio infinito (deadlock) na thread de leitura.

Tags: C++ Win32 API CreateProcess Anonymous Pipes Process Redirection

Publicado em 7-17 02:08