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


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

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

9 72

9 ответов:

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

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

статья Чена указывает на статья Junfeng Zhang, которая объясняет несколько других методов.

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

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

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

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

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

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

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

часть в статье Junfeng об использовании editbin изменить флаг консольного режима программы-это отвлекающий маневр, я думаю. Компилятор или среда разработки должны предоставить параметр или параметр для управления типом двоичный он создает. Нет необходимости что-либо изменить впоследствии.

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

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

Проверьте блог Раймонда на эту тему:

http://blogs.msdn.com/oldnewthing/archive/2009/01/01/9259142.aspx

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

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

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

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

есть простой способ сделать то, что вы хотите. Я всегда использую его при написании приложений, которые должны иметь как в CLI и GUI. Вы должны установить свой " 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
        });
      }
    }
  }

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

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

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

/*
** 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);
    }

вот что я считаю простым решением проблемы .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.

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

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