首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> C語言 >> VC >> 關於VC++

打造專屬 IDE:在 GUI 介面即時捕獲主控台程式標準輸出

     編輯:關於VC++

Visual Studio、JBuilder 等 IDE 編譯程式時,輸出視窗會即時顯示編譯器、連結器的文字訊息,卻不會彈出 DOS 視窗。原理是 IDE 在背景啟動命令列工具,並即時捕獲其標準輸出到 GUI 介面。本文詳細說明如何實作這項核心技術。

應用場景

  • 自行開發 IDE 整合編譯器/除錯工具
  • 在應用程式內嵌全文搜尋、壓縮解壓縮等主控台工具
  • 整合無原始碼的舊版主控台程式

核心架構:中介程式

GUI 程式無主控台,無法直接重導向標準輸出。解決方案:編寫一個「中介程式」,負責啟動目標工具並重導向其輸出,再透過匿名管道即時傳回 GUI 程式。

圖:VS 6.0 Output 視窗執行 javac.exe 編譯 Java 程式

圖:中介程式架構示意圖

中介程式 wspawn.cpp

#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <string>
#include <windows.h>
using namespace std;

void exit_friendly(void) { puts("請不要單獨執行 wspawn."); exit(0); }

int main(int argc, char *argv[])
{
    HANDLE hWrite = NULL;
    DWORD dwWrited;
    int i = 0, ret = 0, len = 0;
    char psBuffer[256];
    FILE* child_output;
    string command_line = "";

    // 解析命令列:wspawn -h <管道控制代碼> <真正命令> [參數...]
    if (argc < 2) exit_friendly();
    if (!stricmp(argv[1], "-h"))
    {
        if (argc < 4) exit_friendly();
        hWrite = (HANDLE)atoi(argv[2]);
        i = 3;
    }
    else i = 1;

    for (; i < argc; i++) { command_line += argv[i]; command_line += " "; }

    // 使用 _popen 建立子行程並重導向 stdout
    if ((child_output = _popen(command_line.c_str(), "rt")) == NULL) exit(1);

    while (!feof(child_output))
    {
        if (fgets(psBuffer, 255, child_output) != NULL)
        {
            if (hWrite)
            {
                len = strlen(psBuffer);
                WriteFile(hWrite, &len, sizeof(int), &dwWrited, NULL);  // 先寫長度
                WriteFile(hWrite, psBuffer, len, &dwWrited, NULL);      // 再寫內容
            }
            else printf(psBuffer);  // 無管道時直接輸出
        }
    }
    len = 0;  // 寫入 0 表示結束
    if (hWrite) WriteFile(hWrite, &len, sizeof(int), &dwWrited, NULL);
    return _pclose(child_output);
}

GUI 主程式:建立管道與背景執行緒

建立 MFC 對話框專案,加入多行編輯框 IDC_EDIT1(屬性勾選 Multiline, Vertical scroll, Auto VScroll, Want return),對應變數 CEdit m_edit1。

// CMyIDEDlg.h
protected:
    static UINT myThread(LPVOID pParam);

// CMyIDEDlg.cpp
void CMyIDEDlg::OnOK()
{
    AfxBeginThread(myThread, this);
    InvalidateRect(NULL);
    UpdateWindow();
}

UINT CMyIDEDlg::myThread(LPVOID pParam)
{
    PROCESS_INFORMATION pi;
    STARTUPINFO siStartInfo;
    SECURITY_ATTRIBUTES saAttr;
    CString Output, tmp;
    char command_line[200];
    DWORD dwRead;
    char* buf; int len;
    HANDLE hRead, hWrite;
    CMyIDEDlg* pDlg = (CMyIDEDlg*)pParam;

    // 建立可繼承的匿名管道
    saAttr.nLength = sizeof(SECURITY_ATTRIBUTES);
    saAttr.bInheritHandle = TRUE;
    saAttr.lpSecurityDescriptor = NULL;
    if (!CreatePipe(&hRead, &hWrite, &saAttr, 0))
    { AfxMessageBox("建立管道失敗"); return 0; }

    // 準備 wspawn 命令列,傳入寫入端控制代碼
    memset(&pi, 0, sizeof(pi));
    sprintf(command_line, "wspawn -h %d cl /?", (unsigned int)hWrite);  // 測試用 cl.exe /?

    ZeroMemory(&siStartInfo, sizeof(STARTUPINFO));
    siStartInfo.cb = sizeof(STARTUPINFO);
    siStartInfo.wShowWindow = SW_HIDE;
    siStartInfo.dwFlags = STARTF_USESHOWWINDOW;

    if (!CreateProcess(NULL, command_line, NULL, NULL, TRUE, 0, NULL, NULL, &siStartInfo, &pi))
    { AfxMessageBox("呼叫 wspawn 失敗"); return 0; }

    // 讀取管道並即時顯示
    if (!ReadFile(hRead, &len, sizeof(int), &dwRead, NULL) || dwRead == 0) return 0;
    while (len)
    {
        buf = new char[len + 1];
        memset(buf, 0, len + 1);
        if (!ReadFile(hRead, buf, len, &dwRead, NULL) || dwRead == 0) return 0;
        
        tmp = buf;
        tmp.Replace("\n", "\r\n");  // 換行相容編輯框
        Output += tmp;
        pDlg->m_edit1.SetWindowText(Output);
        pDlg->InvalidateRect(NULL);
        pDlg->UpdateWindow();
        delete[] buf;

        if (!ReadFile(hRead, &len, sizeof(int), &dwRead, NULL) || dwRead == 0) return 0;
    }

    WaitForSingleObject(pi.hProcess, 30000);
    CloseHandle(hRead); CloseHandle(hWrite);
    return 0;
}

圖:myIDE 執行畫面,即時顯示 cl.exe /? 說明文字

補充說明

  • _popen() 僅能重導向 stdout 或 stdin。若需同時捕獲 stderr(如 javac 錯誤輸出),須改用 _pipe()、_dup() 等傳統 API 完整實作。
  • 背景執行緒確保 GUI 不會因長時間編譯而凍結。
  • 執行前請確認 cl.exe 在系統 PATH 中,或修改命令列為完整路徑。
  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved