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

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

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

Ответы [ 9 ]

85 голосов
/ 30 января 2009

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

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

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

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

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

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

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

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

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

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

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

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

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

10 голосов
/ 30 января 2009

Посетите блог Раймонда на эту тему:

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

Его первое предложение: «Вы не можете, но вы можете попытаться подделать это».

6 голосов
/ 30 января 2009

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

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

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

5 голосов
/ 16 марта 2013

Существует простой способ сделать то, что вы хотите. Я всегда использую его при написании приложений, которые должны иметь как 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
        });
      }
    }
  }
3 голосов
/ 18 сентября 2015

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

SendKeys.SendWait("{ENTER}");

Затем консоль получает последний ключ 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.

3 голосов
/ 05 июля 2009

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

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

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

2 голосов
/ 27 января 2013
/*
** 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);
    }
1 голос
/ 13 мая 2013
0 голосов
/ 15 ноября 2014

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

...