96

I want to make a C# program that can be run as a CLI or GUI application depending on what flags are passed into it. Can this be done?

I have found these related questions, but they don't exactly cover my situation:

Community
  • 1
  • 1
BCS
  • 75,627
  • 68
  • 187
  • 294
  • Check out this question: [http://stackoverflow.com/questions/199182/c-hiding-form-when-running-form-program-from-the-command-line](http://stackoverflow.com/questions/199182/c-hiding-form-when-running-form-program-from-the-command-line) – benPearce Jan 29 '09 at 22:00
  • 1
    Just for the record: it is really related to the operating system, not CLR. For example, with Mono on Linux there is no problem creating such application (in fact, every application is console, but can also do whatever with windows) - just as with Java or any other *nix program. And common pattern is to have logging on console while using GUI for the user. – konrad.kruczynski Jan 24 '12 at 23:26

9 Answers9

114

Jdigital's answer points to Raymond Chen's blog, which explains why you can't have an application that's both a console program and a non-console* program: The OS needs to know before the program starts running which subsystem to use. Once the program has started running, it's too late to go back and request the other mode.

Cade's answer points to an article about running a .Net WinForms application with a console. It uses the technique of calling AttachConsole after the program starts running. This has the effect of allowing the program to write back to the console window of the command prompt that started the program. But the comments in that article point out what I consider to be a fatal flaw: The child process doesn't really control the console. The console continues accepting input on behalf of the parent process, and the parent process is not aware that it should wait for the child to finish running before using the console for other things.

Chen's article points to an article by Junfeng Zhang that explains a couple of other techniques.

The first is what devenv uses. It works by actually having two programs. One is devenv.exe, which is the main GUI program, and the other is devenv.com, which handles console-mode tasks, but if it's used in a non-console-like manner, it forwards its tasks to devenv.exe and exits. The technique relies on the Win32 rule that com files get chosen ahead of exe files when you type a command without the file extension.

There's a simpler variation on this that the Windows Script Host does. It provides two completely separate binaries, wscript.exe and cscript.exe. Likewise, Java provides java.exe for console programs and javaw.exe for non-console programs.

Junfeng's second technique is what ildasm uses. He quotes the process that ildasm's author went through when making it run in both modes. Ultimately, here's what it does:

  1. The program is marked as a console-mode binary, so it always starts out with a console. This allows input and output redirection to work as normal.
  2. If the program has no console-mode command-line parameters, it re-launches itself.

It's not enough to simply call FreeConsole to make the first instance cease to be a console program. That's because the process that started the program, cmd.exe, "knows" that it started a console-mode program and is waiting for the program to stop running. Calling FreeConsole would make ildasm stop using the console, but it wouldn't make the parent process start using the console.

So the first instance restarts itself (with an extra command-line parameter, I suppose). When you call CreateProcess, there are two different flags to try, DETACHED_PROCESS and CREATE_NEW_CONSOLE, either of which will ensure that the second instance will not be attached to the parent console. After that, the first instance can terminate and allow the command prompt to resume processing commands.

The side effect of this technique is that when you start the program from a GUI interface, there will still be a console. It will flash on the screen momentarily and then disappear.

The part in Junfeng's article about using editbin to change the program's console-mode flag is a red herring, I think. Your compiler or development environment should provide a setting or option to control which kind of binary it creates. There should be no need to modify anything afterward.

The bottom line, then, is that you can either have two binaries, or you can have a momentary flicker of a console window. Once you decide which is the lesser evil, you have your choice of implementations.

* I say non-console instead of GUI because otherwise it's a false dichotomy. Just because a program doesn't have a console doesn't mean it has a GUI. A service application is a prime example. Also, a program can have a console and windows.

Glenn Slayden
  • 17,543
  • 3
  • 114
  • 108
Rob Kennedy
  • 161,384
  • 21
  • 275
  • 467
  • 1
    I know this is an old answer, but on the red-herring points about editbin, I believe the purpose of that trick is to get the CRT to link a `WinMain` function with appropriate parameters (so compile with `/SUBSYSTEM:WINDOWS`) then change the mode ex post facto so the loader launches a console host. For more feedback, I tried this with `CREATE_NO_WINDOW` in CreateProcess and `GetConsoleWindow() == NULL` as my check-if-relaunched or not. This doesn't fix the console flicker, but it does mean not having a special cmd argument. –  May 27 '13 at 16:06
  • This is a great answer, but for completeness it's probably worth stating what the main differences between a console and 'non-console' program are (misunderstanding here seems to lead to many of the mistaken answers below). That is: a console app, launched from the console, will not return control to the parent console until it's completed, whereas a GUI app will fork, and return immediately. When unsure, you can use DUMPBIN /headers and look for the SUBSYSTEM line to see exactly what flavor you've got. – piers7 Aug 04 '14 at 01:05
  • This is an obsolete best answer. At least from a C/C++ perspective. See dantill's solution below for Win32, which probably could be adapted to C# by someone. – B. Nadolson Aug 13 '15 at 05:47
  • 1
    I don't consider this answer obsolete. The method works well, and the answer's rating speaks for itself. Dantill's approach disconnects stdin from the console app. I've provided a C version of Kennedy's "momentary flicker" approach below as a separate answer (yes, I know, OP posted about C#). I've used it several times and am quite happy with it. – willus Nov 23 '15 at 00:56
  • You could do this in Java .. ) – Antoniossss Jul 11 '19 at 14:58
  • I doubt that, @Antoniossss, given that Java programs are subject to the same rules as any other program on Windows. If you really have a solution, though, I invite you to post your own answer here. – Rob Kennedy Jul 11 '19 at 20:02
  • You dont have to doubt, just run gui program with java not javaw and you will end up with GUI window and console opened. – Antoniossss Jul 12 '19 at 07:43
  • 1
    I think you've misunderstood the question, @Antoniossss. The goal is a single binary that _can_ behave as either kind of program, _at its option_, not one that behaves as _both simultaneously_. The latter is easy. The former is not, and can only be achieved through various amounts of "faking it." – Rob Kennedy Jul 15 '19 at 17:15
  • Zhang's article is a dead link. Please replace it with https://web.archive.org/web/20100516144303/http://blogs.msdn.com/junfeng/archive/2004/02/06/68531.aspx or http://archive.is/adfbB. – rkagerer Feb 09 '20 at 18:51
  • @rkagerer You don’t have to go that far, Microsoft still has it: . – Alex Shpilkin Jan 12 '21 at 22:31
  • Dantill's example is another implementation of the `AttachConsole` method that I dispelled in my second paragraph. – Rob Kennedy Jan 13 '21 at 19:42
11

Check out Raymond's blog on this topic:

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

His first sentence: "You can't, but you can try to fake it."

jdigital
  • 11,926
  • 4
  • 34
  • 51
6

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

Just check the command line arguments before the WinForms Application. stuff.

I should add that in .NET it is RIDICULOUSLY easy to simply make a console and GUI projects in the same solution which share all their assemblies except main. And in this case, you could make the command line version simply launch the GUI version if it is launched with no parameters. You would get a flashing console.

Cade Roux
  • 88,164
  • 40
  • 182
  • 265
  • The existence of command line params is hardly a sure fire indication. Plenty of windows apps can take command line params – Neil N Oct 27 '09 at 15:15
  • 3
    My point was if there are none, launch the GUI version. If you want the GUI version launched with parameters, presumably you can have a parameter for that. – Cade Roux Oct 28 '09 at 13:54
5

There is an easy way to do what you want. I'm always using it when writing apps that should have both a CLI and a GUI. You have to set your "OutputType" to "ConsoleApplication" for this to work.

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
        });
      }
    }
  }
user1566352
  • 131
  • 2
  • 7
  • 1
    I love this and it works fine on my Windows 7 dev machine.However I have a (virtual) Windows XP machine and it would seem that the restarted process always gets a console and so disappears in an endless loop restarting itself. Any ideas? – Simon Hewitt Apr 02 '13 at 11:43
  • 1
    Be very careful with this, on Windows XP this indeed leads to an unlimited respawn loop that is very hard to kill. – user Feb 21 '16 at 20:48
3

I think the preferred technique is what Rob called the devenv technique of using two executables: a launcher ".com" and the original ".exe". This is not that tricky to use if you have the boilerplate code to work with (see below link).

The technique uses tricks to have that ".com" be a proxy for the stdin/stdout/stderr and launch the same-named .exe file. This give the behavior of allowing the program to preform in a command line mode when called form a console (potentially only when certain command-line arguments are detected) while still being able to launch as a GUI application free of a console.

I hosted a project called dualsubsystem on Google Code that updates an old codeguru solution of this technique and provides the source code and working example binaries.

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
gabeiscoding
  • 524
  • 5
  • 9
3

Here is what I believe to be the simple .NET C# solution to the problem. Just to restate the problem, when you run the console "version" of the app from a command line with a switch, the console keeps waiting (it doesn't return to the command prompt and the process keeps running) even if you have an Environment.Exit(0) at the end of your code. To fix this, just before calling Environment.Exit(0), call this:

SendKeys.SendWait("{ENTER}");

Then the console gets the final Enter key it needs to return to the command prompt and the process ends. Note: Don't call SendKeys.Send(), or the app will crash.

It's still necessary to call AttachConsole() as mentioned in many posts, but with this I get no command window flicker when launching the WinForm version of the app.

Here's the entire code in a sample app I created (without the WinForms code):

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());
            }
        }
    }
}

Hope it helps someone from also spending days on this problem. Thanks for the hint go to @dantill.

LTDev
  • 51
  • 4
  • I tried this and the problem is that anything written using `Console.WriteLine` does not advance the (parent) console's text cursor. So when you app exits, the cursor position is in the wrong place and you have to press enter a few times just to get it back to a "clean" prompt. – Tahir Hassan Feb 26 '16 at 11:54
  • @TahirHassan You can automate the prompt capture and cleanup as described here, but it's still not a perfect solution: https://stackoverflow.com/questions/1305257/using-attachconsole-user-must-hit-enter-to-get-regular-command-line/59340459#59340459 – rkagerer Feb 09 '20 at 18:58
2
/*
** 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);
    }
willus
  • 501
  • 3
  • 7
2

I have written up an alternative approach which avoids the console flash. See How to create a Windows program that works both as a GUI and console application.

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
dantill
  • 29
  • 2
  • 1
    I was skeptical but it works flawlessly. Like really, really flawlessly. Excellent job! First true solution to issue I've seen. (It is C/C++ code. Not C# code.) – B. Nadolson Aug 13 '15 at 05:41
  • I agree with B. Nadolson. This works (for C++), without relaunching the process, and without multiple EXEs. – GravityWell Nov 09 '16 at 03:20
  • 2
    Drawbacks to this method: (1) it has to send an extra keystroke to the console when it's done, (2) it cannot redirect the console output to a file, and (3) it apparently has not been tested with attached stdin (which I'd guess also cannot be redirected from a file). To me, that's too many trades just to avoid momentarily flashing up a console window. The re-launch method at least provides a true dual console/GUI. I've distributed such an app to tens of thousands of users, and have not received one single complaint or comment about the momentarily-flashing-up console window. – willus Apr 02 '17 at 18:08
0

Run AllocConsole() in a static constructor works for me

Shaggy
  • 61
  • 1
  • 3