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

判斷 .NET 應用程式以 GUI 或命令列模式啟動 — 三種偵測策略與混合模式實作

     編輯:關於.NET

前言

現代應用程式常需支援雙模式:GUI 模式 (雙擊執行、開始功能表) 與 命令列模式 (排程器、CI/CD、自動化腳本呼叫)。核心挑戰在於如何可靠偵測目前執行環境。本文提供三種策略,並示範「單一 EXE、雙模式、共用邏輯」的完整架構。

策略比較

策略原理優點缺點適用場景
1. 參數約定以第一個引數決定模式 (如 /console、/gui)明確、可控、跨平台、無相依需呼叫端配合新專案、自動化腳本、CI/CD
2. 父進程偵測檢查父進程名稱是否為 cmd.exe、powershell.exe、wt.exe無需修改呼叫方式、自動偵測若從 Run 對話框、捷徑啟動父進程為 explorer.exe,會誤判為 GUI現有專案改造、互動式 CLI 使用
3. 主控台附掛測試嘗試 AttachConsole(ATTACH_PARENT_PROCESS),成功表示有主控台最精確判斷「是否連接主控台」若從已有主控台的 GUI App (如 VS Output 視窗) 啟動會誤判需精確控制主控台輸出行為

完整實作:混合模式單一進入點


using System;
using System.Diagnostics;
using System.Runtime.InteropServices;
using System.Windows.Forms;

public static class Program
{
    [DllImport("kernel32.dll", SetLastError = true)]
    private static extern bool AttachConsole(int dwProcessId);

    [DllImport("kernel32.dll", SetLastError = true)]
    private static extern bool AllocConsole();

    [DllImport("kernel32.dll", SetLastError = true)]
    private static extern bool FreeConsole();

    [DllImport("user32.dll")]
    private static extern IntPtr GetForegroundWindow();

    [DllImport("user32.dll", SetLastError = true)]
    private static extern uint GetWindowThreadProcessId(IntPtr hWnd, out int lpdwProcessId);

    private const int ATTACH_PARENT_PROCESS = -1;

    [STAThread]
    public static void Main(string[] args)
    {
        // 1. 解析模式參數 (最高優先級)
        string mode = "gui"; // 預設 GUI
        if (args.Length > 0 && !args[0].StartsWith("/") && !args[0].StartsWith("-"))
        {
            // 非參數格式,視為檔案路徑等業務參數,維持 GUI
        }
        else if (args.Length > 0)
        {
            mode = args[0].TrimStart('/', '-').ToLowerInvariant();
        }

        // 2. 依模式分流
        switch (mode)
        {
            case "console":
            case "cli":
                RunConsoleMode(args);
                break;
            case "gui":
            default:
                // 3. 自動偵測:若從 cmd/powershell 啟動且未指定 /gui,可選擇自動進入 CLI
                if (IsLaunchedFromConsole() && args.Length == 0)
                {
                    Console.WriteLine("偵測到從命令列啟動,自動進入 CLI 模式 (加 /gui 強制 GUI)");
                    RunConsoleMode(args);
                }
                else
                {
                    RunGuiMode();
                }
                break;
        }
    }

    private static bool IsLaunchedFromConsole()
    {
        try
        {
            IntPtr fg = GetForegroundWindow();
            GetWindowThreadProcessId(fg, out int pid);
            var parent = Process.GetProcessById(pid);
            string name = parent.ProcessName.ToLowerInvariant();
            return name == "cmd" || name == "powershell" || name == "wt" || name == "terminal";
        }
        catch { return false; }
    }

    private static void RunConsoleMode(string[] args)
    {
        // 嘗試附掛父主控台 (若從 cmd 啟動)
        bool attached = AttachConsole(ATTACH_PARENT_PROCESS);
        if (!attached)
        {
            // 無父主控台 (如雙擊、排程器) → 配置新主控台
            AllocConsole();
        }

        try
        {
            Console.WriteLine($"[CLI Mode] 啟動參數: {string.Join(" ", args)}");
            // 執行 CLI 邏輯
            var cli = new CliEngine();
            int exitCode = cli.Execute(args);
            Environment.ExitCode = exitCode;
        }
        finally
        {
            if (attached) FreeConsole(); // 釋放父主控台
            else { /* 新配置的主控台隨進程結束自動關閉 */ }
        }
    }

    private static void RunGuiMode()
    {
        Application.EnableVisualStyles();
        Application.SetCompatibleTextRenderingDefault(false);
        Application.Run(new MainForm()); // WPF 用 new App().Run(new MainWindow())
    }
}

// --- CLI 邏輯封裝 ---
public class CliEngine
{
    public int Execute(string[] args)
    {
        if (args.Length == 0 || args[0] == "/?" || args[0] == "/help")
        {
            ShowHelp();
            return 0;
        }

        // 實際業務邏輯
        Console.WriteLine("執行 CLI 工作...");
        return 0;
    }

    private void ShowHelp()
    {
        Console.WriteLine(@"
MyApp - 雙模式應用程式
用法: MyApp.exe [/gui | /console] [參數...]
  /gui      強制啟動 GUI 模式
  /console  強制啟動命令列模式
  /?        顯示說明
");
    }
}

WPF 專案調整


// App.xaml.cs
public partial class App : Application
{
    protected override void OnStartup(StartupEventArgs e)
    {
        // 同樣解析 e.Args 決定模式
        if (ShouldRunCli(e.Args))
        {
            RunCliMode(e.Args);
            Shutdown(); // 結束 WPF 應用程式
        }
        else
        {
            base.OnStartup(e); // 正常啟動 GUI
        }
    }
}

Visual Studio 偵錯設定

專案屬性 → Debug → Application arguments:

  • 測試 GUI:留空或輸入 /gui
  • 測試 CLI:輸入 /console arg1 arg2

進階:同時支援 GUI 輸出與 CLI 輸出

若需在 GUI 模式下也能看到 Console.WriteLine (如 Visual Studio Output 視窗):


[DllImport("kernel32.dll")]
private static extern bool AttachConsole(int dwProcessId);

public static void EnsureConsoleAttached()
{
    // 嘗試附掛父進程主控台 (VS 偵錯時有效)
    AttachConsole(ATTACH_PARENT_PROCESS);
}

常見問題 FAQ

  1. Q: 雙擊 EXE 閃現黑框?
    A: 專案屬性 → Application → Output type = Windows Application (非 Console Application)。
  2. Q: 從排程器/服務啟動,IsLaunchedFromConsole() 回傳 false?
    A: 父進程非 cmd/powershell,屬正常行為。排程器建議明確傳遞 /console 參數。
  3. Q: CLI 模式下 Console.ReadLine() 卡住?
    A: 若 AttachConsole 失敗且未 AllocConsole,標準輸入無效。確保 RunConsoleMode 邏輯正確。
  4. Q: WPF 應用 Output type 必須是 Windows Application?
    A: 是。若設為 Console Application,GUI 啟動時會多一個黑框。

總結:選型建議

  • 新專案:採用參數約定策略 (/gui、/console),最清晰、可測試、跨平台。
  • 舊專案改造:加入父進程偵測作為預設值,保留參數覆蓋機制。
  • 需精確主控台互動:使用 AttachConsole/AllocConsole/FreeConsole 組合,精確控制主控台生命週期。
  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved