Как создать приложение на C #, которое решает, отображать ли его как консольное или оконное приложение? - PullRequest
46 голосов
/ 30 апреля 2009

Есть ли способ запустить приложение C # со следующими функциями?

  1. По параметрам командной строки определяет, является ли это оконным или консольным приложением
  2. Он не отображает консоль, когда ее просят открыть окно, и не отображает окно графического интерфейса при ее запуске из консоли.

Например,

myapp.exe /help
будет выводить на стандартный вывод на используемой вами консоли, но
myapp.exe
само по себе запустит мой пользовательский интерфейс Winforms или WPF.

Лучшие ответы, которые я знаю до сих пор, включают в себя наличие двух отдельных exe-файлов и использование IPC, но это выглядит очень странно.


Какие у меня есть варианты и какие компромиссы я могу сделать, чтобы получить поведение, описанное в примере выше? Я открыт для идей, относящихся к Winform или WPF.

Ответы [ 12 ]

0 голосов
/ 11 марта 2017

Я разработал способ сделать это, включая использование стандартного ввода, но я должен предупредить вас, что это не красиво.

Проблема с использованием stdin из подключенной консоли заключается в том, что оболочка также будет читать с нее. Это приводит к тому, что входные данные иногда отправляются в ваше приложение, а иногда - в оболочку.

Решение состоит в том, чтобы блокировать оболочку на время жизни приложений (хотя технически вы можете попытаться заблокировать ее только тогда, когда вам потребуется ввод). Я выбираю это, отправляя нажатия клавиш в оболочку для запуска команды powershell, ожидающей завершения приложения.

Кстати, это также устраняет проблему, связанную с тем, что приглашение не возвращается после завершения работы приложения.

Я кратко попытался заставить его работать и с консоли powershell. Применяются те же принципы, но я не смог выполнить свою команду. Возможно, PowerShell имеет некоторые проверки безопасности для предотвращения запуска команд из других приложений. Поскольку я не очень много использую PowerShell, я не изучал его.

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

    [DllImport("kernel32", SetLastError = true)]
    private static extern bool AttachConsole(int dwProcessId);

    private const uint STD_INPUT_HANDLE = 0xfffffff6;
    private const uint STD_OUTPUT_HANDLE = 0xfffffff5;
    private const uint STD_ERROR_HANDLE = 0xfffffff4;

    [DllImport("kernel32.dll")]
    private static extern IntPtr GetStdHandle(uint nStdHandle);
    [DllImport("Kernel32.dll", SetLastError = true)]
    public static extern int SetStdHandle(uint nStdHandle, IntPtr handle);

    [DllImport("kernel32.dll", SetLastError = true)]
    private static extern int GetConsoleProcessList(int[] ProcessList, int ProcessCount);

    [DllImport("user32.dll")]
    public static extern IntPtr SendMessage(IntPtr hWnd, uint Msg, IntPtr wParam, IntPtr lParam);
    [DllImport("user32.dll")]
    public static extern IntPtr PostMessage(IntPtr hWnd, uint Msg, IntPtr wParam, IntPtr lParam);

    /// <summary>
    /// Attach to existing console or create new. Must be called before using System.Console.
    /// </summary>
    /// <returns>Return true if console exists or is created.</returns>
    public static bool InitConsole(bool createConsole = false, bool suspendHost = true) {

        // first try to attach to an existing console
        if (AttachConsole(-1)) {
            if (suspendHost) {
                // to suspend the host first try to find the parent
                var processes = GetConsoleProcessList();

                Process host = null;
                string blockingCommand = null;

                foreach (var proc in processes) {
                    var netproc = Process.GetProcessById(proc);
                    var processName = netproc.ProcessName;
                    Console.WriteLine(processName);
                    if (processName.Equals("cmd", StringComparison.OrdinalIgnoreCase)) {
                        host = netproc;
                        blockingCommand = $"powershell \"& wait-process -id {Process.GetCurrentProcess().Id}\"";
                    } else if (processName.Equals("powershell", StringComparison.OrdinalIgnoreCase)) {
                        host = netproc;
                        blockingCommand = $"wait-process -id {Process.GetCurrentProcess().Id}";
                    }
                }

                if (host != null) {
                    // if a parent is found send keystrokes to simulate a command
                    var cmdWindow = host.MainWindowHandle;
                    if (cmdWindow == IntPtr.Zero) Console.WriteLine("Main Window null");

                    foreach (char key in blockingCommand) {
                        SendChar(cmdWindow, key);
                        System.Threading.Thread.Sleep(1); // required for powershell
                    }

                    SendKeyDown(cmdWindow, Keys.Enter);

                    // i haven't worked out how to get powershell to accept a command, it might be that this is a security feature of powershell
                    if (host.ProcessName == "powershell") Console.WriteLine("\r\n *** PRESS ENTER ***");
                }
            }

            return true;
        } else if (createConsole) {
            return AllocConsole();
        } else {
            return false;
        }
    }

    private static void SendChar(IntPtr cmdWindow, char k) {
        const uint WM_CHAR = 0x0102;

        IntPtr result = PostMessage(cmdWindow, WM_CHAR, ((IntPtr)k), IntPtr.Zero);
    }

    private static void SendKeyDown(IntPtr cmdWindow, Keys k) {
        const uint WM_KEYDOWN = 0x100;
        const uint WM_KEYUP = 0x101;

        IntPtr result = SendMessage(cmdWindow, WM_KEYDOWN, ((IntPtr)k), IntPtr.Zero);
        System.Threading.Thread.Sleep(1);
        IntPtr result2 = SendMessage(cmdWindow, WM_KEYUP, ((IntPtr)k), IntPtr.Zero);
    }

    public static int[] GetConsoleProcessList() {
        int processCount = 16;
        int[] processList = new int[processCount];

        // supposedly calling it with null/zero should return the count but it didn't work for me at the time
        // limiting it to a fixed number if fine for now
        processCount = GetConsoleProcessList(processList, processCount);
        if (processCount <= 0 || processCount >= processList.Length) return null; // some sanity checks

        return processList.Take(processCount).ToArray();
    }
0 голосов
/ 30 апреля 2009

№ 1 легко.

Нет 2 не может быть сделано, я не думаю.

Документы говорят:

Вызовы, такие как Write и WriteLine, не действуют в приложениях Windows.

Класс System.Console инициализируется по-разному в консольных и графических приложениях. Вы можете убедиться в этом, посмотрев класс Console в отладчике в каждом типе приложения. Не уверен, есть ли способ переинициализировать его.

Демо-версия: Создайте новое приложение Windows Forms, затем замените метод Main следующим:

    static void Main(string[] args)
    {
        if (args.Length == 0)
        {
            Application.EnableVisualStyles();
            Application.SetCompatibleTextRenderingDefault(false);
            Application.Run(new Form1());
        }
        else
        {
            Console.WriteLine("Console!\r\n");
        }
    }

Идея состоит в том, что любые параметры командной строки будут выводиться на консоль и завершаться. Когда вы запускаете его без аргументов, вы получаете окно. Но когда вы запускаете его с аргументом командной строки, ничего не происходит.

Затем выберите свойства проекта, измените тип проекта на «Консольное приложение» и перекомпилируйте. Теперь, когда вы запускаете его с аргументом, вы получаете «Консоль!» Как ты хочешь. И когда вы запускаете его (из командной строки) без аргументов, вы получаете окно. Но командная строка не вернется, пока вы не выйдете из программы. И если вы запустите программу из Explorer, откроется командное окно, а затем вы получите окно.

...