Может ли один исполняемый файл быть одновременно консольным и графическим приложением?

StackOverflow https://stackoverflow.com/questions/493536

Вопрос

Я хочу сделать C# программа, которая может быть запущена как CLI или GUI-приложение в зависимости от того, какие флаги в нее переданы.Можно ли это сделать?

Я нашел эти связанные вопросы, но они не совсем охватывают мою ситуацию:

Это было полезно?

Решение

Ответ Jdigital указывает на Блог Рэймонда Чена, что объясняет , почему у вас не может быть приложения , которое одновременно является консольной программой и неконсольной* программа:Операционная система должна знать перед запуском программы какую подсистему использовать.Как только программа запустится, будет слишком поздно возвращаться и запрашивать другой режим.

Ответ Кейда указывает на статья о запуске .Сетевое приложение WinForms с консолью.Он использует технику вызова AttachConsole после того, как программа начнет работать.Это приводит к тому, что программа может выполнить обратную запись в окно консоли командной строки, которая запустила программу.Но комментарии в этой статье указывают на то, что я считаю фатальным недостатком: Дочерний процесс на самом деле не управляет консолью. Консоль продолжает принимать входные данные от имени родительского процесса, и родительский процесс не знает, что он должен дождаться завершения дочернего процесса, прежде чем использовать консоль для других целей.

Статья Чэня указывает на статья Цзюньфэн Чжана , в которой объясняется пара других методов.

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

Существует более простой вариант этого, который выполняет узел сценариев Windows.Он предоставляет два полностью отдельных двоичных файла, wscript.exe и cscript.exe.Аналогично, Java предоставляет java.exe для консольных программ и javaw.exe для неконсольных программ.

Вторая техника Цзюньфэна - это то, что ильдазм использует.Он цитирует процесс , который ильдазмавтор прошел через это, когда запускал его в обоих режимах.В конечном счете, вот что это делает:

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

Недостаточно просто позвонить FreeConsole чтобы первый экземпляр перестал быть консольной программой.Это потому, что процесс, который запустил программу, cmd.exe, "знает", что он запустил программу консольного режима и ожидает, когда программа прекратит запуск.Зовущий FreeConsole сделал бы ильдазм прекратите использовать консоль, но это не приведет к запуску родительского процесса начать используя консоль.

Таким образом, первый экземпляр перезапускается сам (я полагаю, с дополнительным параметром командной строки).Когда ты позвонишь CreateProcess, есть два разных флага , которые нужно попробовать, DETACHED_PROCESS и CREATE_NEW_CONSOLE, любой из которых гарантирует, что второй экземпляр не будет присоединен к родительской консоли.После этого первый экземпляр может завершиться и позволить командной строке возобновить обработку команд.

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

Часть статьи Цзюньфэна об использовании редактирование я думаю, что изменение флага консольного режима программы - это отвлекающий маневр.Ваш компилятор или среда разработки должны предоставлять настройку или опцию для управления тем, какой тип двоичного файла он создает.После этого не должно быть необходимости что-либо изменять.

Таким образом, суть в том, что у вас может быть либо два двоичных файла, либо мгновенное мерцание окна консоли.Как только вы решите, что является меньшим злом, у вас будет свой выбор реализаций.

* Я говорю неконсольный вместо того , чтобы Графический интерфейс пользователя потому что в противном случае это ложная дихотомия.Просто потому, что у программы нет консоли, это не значит, что у нее есть графический интерфейс.Приложение-служба является ярким примером.Кроме того, программа может иметь консоль и Windows.

Другие советы

Ознакомьтесь с блогом Рэймонда на эту тему:

https://devblogs.microsoft.com/oldnewthing/20090101-00/?p=19643

Его первое предложение:"Ты не можешь, но ты можешь попытаться притвориться".

http://www.csharp411.com/console-output-from-winforms-application/

Просто проверьте аргументы командной строки перед WinForms Application. всякое такое.

Я должен добавить, что в .NET до смешного легко просто создать консольный и графический проекты в одном решении, которые совместно используют все свои сборки, кроме main.И в этом случае вы могли бы заставить версию командной строки просто запускать версию GUI, если она запущена без параметров.Вы бы получили мигающую консоль.

Есть простой способ сделать то, что вы хотите.Я всегда использую его при написании приложений, которые должны иметь как CLI, так и графический интерфейс.Вы должны установить для своего "OutputType" значение "ConsoleApplication", чтобы это сработало.

class Program {
  [DllImport("kernel32.dll", EntryPoint = "GetConsoleWindow")]
  private static extern IntPtr _GetConsoleWindow();

  /// <summary>
  /// The main entry point for the application.
  /// </summary>
  [STAThread]
  static void Main(string[] args) {
    Application.EnableVisualStyles();
    Application.SetCompatibleTextRenderingDefault(false);

    /*
     * This works as following:
     * First we look for command line parameters and if there are any of them present, we run the CLI version.
     * If there are no parameters, we try to find out if we are run inside a console and if so, we spawn a new copy of ourselves without a console.
     * If there is no console at all, we show the GUI.
     * We make an exception if we find out, that we're running inside visual studio to allow for easier debugging the GUI part.
     * This way we're both a CLI and a GUI.
     */
    if (args != null && args.Length > 0) {

      // execute CLI - at least this is what I call, passing the given args.
      // Change this call to match your program.
      CLI.ParseCommandLineArguments(args);

    } else {
      var consoleHandle = _GetConsoleWindow();

      // run GUI
      if (consoleHandle == IntPtr.Zero || AppDomain.CurrentDomain.FriendlyName.Contains(".vshost"))

        // we either have no console window or we're started from within visual studio
        // This is the form I usually run. Change it to match your code.
        Application.Run(new MainForm());
      else {

        // we found a console attached to us, so restart ourselves without one
        Process.Start(new ProcessStartInfo(Assembly.GetEntryAssembly().Location) {
          CreateNoWindow = true,
          UseShellExecute = false
        });
      }
    }
  }

Я думаю, что предпочтительная техника - это то, что Роб назвал девенв техника использования двух исполняемых файлов:лаунчер ".com" и оригинальный ".exe".Это не так сложно использовать, если у вас есть шаблонный код для работы (см. Ссылку ниже).

Этот метод использует хитрости, чтобы этот ".com" был прокси-сервером для stdin / stdout / stderr и запускал одноименный exe-файл.Это позволяет программе выполнять предварительную настройку в режиме командной строки при вызове из консоли (потенциально только при обнаружении определенных аргументов командной строки), сохраняя при этом возможность запуска в качестве приложения с графическим интерфейсом без консоли.

Я принимал у себя проект под названием двойная вспомогательная система в Google Code это обновляет старое решение codeguru для этой техники и предоставляет исходный код и двоичные файлы с рабочими примерами.

Вот то, что я считаю простым решением проблемы на .NET C #.Просто чтобы повторить проблему, когда вы запускаете консольную "версию" приложения из командной строки с помощью переключателя, консоль продолжает ждать (она не возвращается в командную строку, и процесс продолжает выполняться), даже если у вас есть Environment.Exit(0) в конце вашего кода.Чтобы исправить это, непосредственно перед вызовом Environment.Exit(0), назовите это:

SendKeys.SendWait("{ENTER}");

Затем консоль получает последнюю клавишу Ввода, необходимую для возврата в командную строку, и процесс завершается.Примечание:Не звони SendKeys.Send(), иначе приложение выйдет из строя.

Все равно нужно позвонить AttachConsole() как упоминалось во многих сообщениях, но при этом я не получаю мерцания окна командной строки при запуске версии приложения WinForm.

Вот весь код в образце приложения, которое я создал (без кода WinForms):

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

namespace ConsoleWriter
{
    static class Program
    {
        [DllImport("kernel32.dll")]
        private static extern bool AttachConsole(int dwProcessId);
        private const int ATTACH_PARENT_PROCESS = -1;

        [STAThread]
        static void Main(string[] args)
        {
            if(args.Length > 0 && args[0].ToUpperInvariant() == "/NOGUI")
            {
                AttachConsole(ATTACH_PARENT_PROCESS);
                Console.WriteLine(Environment.NewLine + "This line prints on console.");

                Console.WriteLine("Exiting...");
                SendKeys.SendWait("{ENTER}");
                Environment.Exit(0);
            }
            else
            {
                Application.EnableVisualStyles();
                Application.SetCompatibleTextRenderingDefault(false);
                Application.Run(new Form1());
            }
        }
    }
}

Надеюсь, это поможет кому-то также не тратить дни на эту проблему.Спасибо за подсказку, зайдите к @dantill.

/*
** dual.c    Runs as both CONSOLE and GUI app in Windows.
**
** This solution is based on the "Momentary Flicker" solution that Robert Kennedy
** discusses in the highest-rated answer (as of Jan 2013), i.e. the one drawback
** is that the console window will briefly flash up when run as a GUI.  If you
** want to avoid this, you can create a shortcut to the executable and tell the
** short cut to run minimized.  That will minimize the console window (which then
** immediately quits), but not the GUI window.  If you want the GUI window to
** also run minimized, you have to also put -minimized on the command line.
**
** Tested under MinGW:  gcc -o dual.exe dual.c -lgdi32
**
*/
#include <windows.h>
#include <stdio.h>

static int my_win_main(HINSTANCE hInstance,int argc,char *argv[],int iCmdShow);
static LRESULT CALLBACK WndProc(HWND hwnd,UINT iMsg,WPARAM wParam,LPARAM lParam);
static int win_started_from_console(void);
static BOOL CALLBACK find_win_by_procid(HWND hwnd,LPARAM lp);

int main(int argc,char *argv[])

    {
    HINSTANCE hinst;
    int i,gui,relaunch,minimized,started_from_console;

    /*
    ** If not run from command-line, or if run with "-gui" option, then GUI mode
    ** Otherwise, CONSOLE app.
    */
    started_from_console = win_started_from_console();
    gui = !started_from_console;
    relaunch=0;
    minimized=0;
    /*
    ** Check command options for forced GUI and/or re-launch
    */
    for (i=1;i<argc;i++)
        {
        if (!strcmp(argv[i],"-minimized"))
            minimized=1;
        if (!strcmp(argv[i],"-gui"))
            gui=1;
        if (!strcmp(argv[i],"-gui-"))
            gui=0;
        if (!strcmp(argv[i],"-relaunch"))
            relaunch=1;
        }
    if (!gui && !relaunch)
        {
        /* RUN AS CONSOLE APP */
        printf("Console app only.\n");
        printf("Usage:  dual [-gui[-]] [-minimized].\n\n");
        if (!started_from_console)
            {
            char buf[16];
            printf("Press <Enter> to exit.\n");
            fgets(buf,15,stdin);
            }
        return(0);
        }

    /* GUI mode */
    /*
    ** If started from CONSOLE, but want to run in GUI mode, need to re-launch
    ** application to completely separate it from the console that started it.
    **
    ** Technically, we don't have to re-launch if we are not started from
    ** a console to begin with, but by re-launching we can avoid the flicker of
    ** the console window when we start if we start from a shortcut which tells
    ** us to run minimized.
    **
    ** If the user puts "-minimized" on the command-line, then there's
    ** no point to re-launching when double-clicked.
    */
    if (!relaunch && (started_from_console || !minimized))
        {
        char exename[256];
        char buf[512];
        STARTUPINFO si;
        PROCESS_INFORMATION pi;

        GetStartupInfo(&si);
        GetModuleFileNameA(NULL,exename,255);
        sprintf(buf,"\"%s\" -relaunch",exename);
        for (i=1;i<argc;i++)
            {
            if (strlen(argv[i])+3+strlen(buf) > 511)
                break;
            sprintf(&buf[strlen(buf)]," \"%s\"",argv[i]);
            }
        memset(&pi,0,sizeof(PROCESS_INFORMATION));
        memset(&si,0,sizeof(STARTUPINFO));
        si.cb = sizeof(STARTUPINFO);
        si.dwX = 0; /* Ignored unless si.dwFlags |= STARTF_USEPOSITION */
        si.dwY = 0;
        si.dwXSize = 0; /* Ignored unless si.dwFlags |= STARTF_USESIZE */
        si.dwYSize = 0;
        si.dwFlags = STARTF_USESHOWWINDOW;
        si.wShowWindow = SW_SHOWNORMAL;
        /*
        ** Note that launching ourselves from a console will NOT create new console.
        */
        CreateProcess(exename,buf,0,0,1,DETACHED_PROCESS,0,NULL,&si,&pi);
        return(10); /* Re-launched return code */
        }
    /*
    ** GUI code starts here
    */
    hinst=GetModuleHandle(NULL);
    /* Free the console that we started with */
    FreeConsole();
    /* GUI call with functionality of WinMain */
    return(my_win_main(hinst,argc,argv,minimized ? SW_MINIMIZE : SW_SHOWNORMAL));
    }


static int my_win_main(HINSTANCE hInstance,int argc,char *argv[],int iCmdShow)

    {
    HWND        hwnd;
    MSG         msg;
    WNDCLASSEX  wndclass;
    static char *wintitle="GUI Window";

    wndclass.cbSize        = sizeof (wndclass) ;
    wndclass.style         = CS_HREDRAW | CS_VREDRAW;
    wndclass.lpfnWndProc   = WndProc;
    wndclass.cbClsExtra    = 0 ;
    wndclass.cbWndExtra    = 0 ;
    wndclass.hInstance     = hInstance;
    wndclass.hIcon         = NULL;
    wndclass.hCursor       = NULL;
    wndclass.hbrBackground = NULL;
    wndclass.lpszMenuName  = NULL;
    wndclass.lpszClassName = wintitle;
    wndclass.hIconSm       = NULL;
    RegisterClassEx (&wndclass) ;

    hwnd = CreateWindowEx(WS_EX_OVERLAPPEDWINDOW,wintitle,0,
                          WS_VISIBLE|WS_OVERLAPPEDWINDOW,
                          100,100,400,200,NULL,NULL,hInstance,NULL);
    SetWindowText(hwnd,wintitle);
    ShowWindow(hwnd,iCmdShow);
    while (GetMessage(&msg,NULL,0,0))
        {
        TranslateMessage(&msg);
        DispatchMessage(&msg);
        }
    return(msg.wParam);
    }


static LRESULT CALLBACK WndProc (HWND hwnd,UINT iMsg,WPARAM wParam,LPARAM lParam)

    {
    if (iMsg==WM_DESTROY)
        {
        PostQuitMessage(0);
        return(0);
        }
    return(DefWindowProc(hwnd,iMsg,wParam,lParam));
    }


static int fwbp_pid;
static int fwbp_count;
static int win_started_from_console(void)

    {
    fwbp_pid=GetCurrentProcessId();
    if (fwbp_pid==0)
        return(0);
    fwbp_count=0;
    EnumWindows((WNDENUMPROC)find_win_by_procid,0L);
    return(fwbp_count==0);
    }


static BOOL CALLBACK find_win_by_procid(HWND hwnd,LPARAM lp)

    {
    int pid;

    GetWindowThreadProcessId(hwnd,(LPDWORD)&pid);
    if (pid==fwbp_pid)
        fwbp_count++;
    return(TRUE);
    }

Я написал альтернативный подход, который позволяет избежать флэш-памяти консоли.Видишь Как создать программу для Windows, которая работает как с графическим интерфейсом, так и с консольным приложением.

Запуск AllocConsole() в статическом конструкторе работает для меня

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top