none
Console/Window Hybrid Application

    Question

  • I am interested in creating a Windows Application that can be run as a Console Application if the user executes it with a particular command line argument. Is it possible to create a single executable that can be run in either Windows or Console mode, and if so how is it done?
    Tuesday, August 21, 2007 7:54 PM

All replies


  • If you're thinking of your application as something that's invoked from the command line and either does or does not start up a gui depending on a command line argument, then the answer is "yes".

    You'd do this as follows:

    1) Create a new Windows Application
    2) Go to Properties and change the "Output type" from "Windows Application" to "Console Application".

    if you just hit "f5" here, you'll already see that you have both a console window and a form.

    3) Now change the code in Program.cs to the snippet below.   If you build this and cd to the Debug directory, you can see it working with
    c:\...\ConsoleApp.exe  < =console mode
    c:\...\ConsoleApp.exe usegui <== gui mode

    A great way to test your different modes depending on the command line args is by passing in command line arguments automatically when you hit F5: Preferences->Debug->Command Line Arguments (i.e. "usegui")
     
    Code Snippet

    using System;
    using System.Collections.Generic;
    using System.Text;
    using System.Windows.Forms;

    namespace ConsoleApplication
    {
        class Program
        {
            [STAThread]
            static void Main(string[] args)
            {
                string mode = string.Empty;
                if(args.Length > 0) mode = args[0];

                if (mode == "usegui")
                {
                    Application.EnableVisualStyles();

                    Application.SetCompatibleTextRenderingDefault(false);

                    Application.Run(new Form1());
                }
                else
                {
                    Console.WriteLine("Console Mode");
                    Console.ReadLine();
                }
            }
        }
    }


    Hope that helps.

    For a discussion of how this isn't really possible, http://www.thescripts.com/forum/thread688409.html
    Tuesday, August 21, 2007 8:50 PM
  • Thanks for the prompt reply. This is about how far I had gotten myself.

    What I am looking for is a way to prevent the Console window from showing when the GUI is displayed.
    Currently I can have it display Console only or Window and Console, when what I want is Console only or GUI only.
    Tuesday, August 21, 2007 9:05 PM


  • hi h3nry1
    I think my sample code plus this thread
    http://forums.microsoft.com/MSDN/ShowPost.aspx?PostID=631961&SiteID=1
    should get you most of the way.
    -Jeff
    Tuesday, August 21, 2007 9:19 PM
  • I'm getting closer, however with the AllocConsole() method described in the other thread, the application opens a new Console window everytime. If the user executes my program in Console mode from the command line I want the output to appear in the current window, just like the default behaviour for Console Applications. With this method, when the user executes the program from the command line a new console window is spawned for the application.
    Wednesday, August 22, 2007 5:02 PM

  • Just out of curiosity, how will users start your application?  With a double click?  If so, what's the switch that chooses GUI vs Console Window ?
    Wednesday, August 22, 2007 5:07 PM
  • It will be started with a double-click usually, in which case the user will get the normal GUI.
    However, if it is started with the /console switch it will run as a console application, preferably in the command window that executed it. If it is started from the run dialog, or a batch script, shortcut, etc. with the appropriate switch it should open its own console window.
    Wednesday, August 22, 2007 5:14 PM

  • Hi h3nry1.
    Ok so you actually have 4 use cases:
    1) User starts application from existing cmd window, and runs in GUI mode
    2) User double clicks to start application, and runs in GUI mode
    3) User starts applicaiton from existing cmd window, and runs in command mode
    4) User double clicks to start application, and runs in command mode.

    I'm not sure how you'd pass arguments to your application in each of these cases: you'll have to go into this code and force scenario #4.  Overall, I actually think this sort of approach will run you into a lot more trouble down the road than it's worth.  For example, you'll have to have two different UIs' -- one for the GUI and one for the command / shell.  You're going to have to build some strange central logic engine that abstracts from GUI vs. command line, and it's just going to get weird.

    So if I were you, I'd step back and think about how this will be used in practice, and whether this sort of mode-switching is worth the work. 

    But assuming you've done that already and really need this, I think this code will do it for you.  Again, I wouldn't use this code myself because as soon as I run into situations where I need API calls to get something done, I tend to stop and ask myself "am I overcomplicating things?".  That said, I've seen lots of posts asking how to do this in several forums, so maybe this will be useful.

    BUG SCENARIO: you have multiple command windows open, and this is somehow executed
    from a console that's NOT the upper-most console (the window on top).  This will cause the application
    to spawn a new console.  It's hard to imagine how this scenario could be replicated, but you can see the problem assuming GetForegroundWindow(); is 100% accurate. 
    POSSIBLE RESULTION: look through all command windows that are open, looking for the one that just executed your .exe?

    Hope that helps,
    Jeff

    Output type=Windows Application
    Code Snippet

    using System;
    using System.Collections.Generic;
    using System.Windows.Forms;
    using System.Runtime.InteropServices;
    using System.Diagnostics;
    using Microsoft.Win32;

    namespace WindowsApplication
    {
        static class Program
        {

            /*
        There are 4 possible ways this can run:
        1) User starts application from existing cmd window, and runs in GUI mode
        2) User double clicks to start application, and runs in GUI mode
        3) User starts applicaiton from existing cmd window, and runs in command mode
        4) User double clicks to start application, and runs in command mode.

        To run in console mode, start a cmd shell and enter:
            c:\path\to\Debug\dir\WindowsApplication.exe console
            To run in gui mode,  EITHER just double click the exe, OR start it from the cmd prompt with:
            c:\path\to\Debug\dir\WindowsApplication.exe (or pass the "gui" argument).
            To start in command mode from a double click, change the default below to "console".
        In practice, I'm not even sure how the console vs gui mode distinction would be made from a
        double click...
            string mode = args.Length > 0 ? args[0] : "console"; //default to console
        */

            [DllImport("kernel32.dll", SetLastError = true)]
            [return: MarshalAs(UnmanagedType.Bool)]
            static extern bool AllocConsole();

            [DllImport("kernel32.dll", SetLastError = true)]
            [return: MarshalAs(UnmanagedType.Bool)]
            static extern bool FreeConsole();

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

            [DllImport("user32.dll")]
            static extern IntPtr GetForegroundWindow();

            [DllImport("user32.dll", SetLastError = true)]
            static extern uint GetWindowThreadProcessId(IntPtr hWnd, out int lpdwProcessId);

            [STAThread]
            static void Main(string[] args)
            {

                //TODO: better handling of command args, (handle help (--help /?) etc.)
                string mode = args.Length > 0 ? args[0] : "gui"; //default to gui

                if (mode == "gui")
                {
                    MessageBox.Show("Welcome to GUI mode");

                    Application.EnableVisualStyles();

                    Application.SetCompatibleTextRenderingDefault(false);

                    Application.Run(new Form1());
                }
                else if (mode == "console")
                {

                    //Get a pointer to the forground window.  The idea here is that
                    //IF the user is starting our application from an existing console
                    //shell, that shell will be the uppermost window.  We'll get it
                    //and attach to it
                    IntPtr ptr = GetForegroundWindow();

                    int  u;

                    GetWindowThreadProcessId(ptr, out u);

                    Process process = Process.GetProcessById(u);

                    MessageBox.Show(process.ProcessName);

                    if (process.ProcessName == "cmd" )    //Is the uppermost window a cmd process?
                    {
                        AttachConsole(process.Id);
                       
                        //we have a console to attach to ..
                        Console.WriteLine("hello. It looks like you started me from an existing console.");
                    }
                    else
                    {
                        //no console AND we're in console mode ... create a new console.

                        AllocConsole();

                        Console.WriteLine("hello. It looks like you double clicked me to start AND you want console mode.  Here's a new console.");
                        Console.WriteLine("press any key to continue ...");
                        Console.ReadLine();       
                    }

                    FreeConsole();
                }
            }
        }
    }


    Wednesday, August 22, 2007 11:54 PM
  • Thanks for your observation about cmd and doubleclick, I haven't thought about it. However, I don't like your way of determining the host process. There always is a possibility, that a program will be launched at the same moment that your application is and that program's main window will get the focus - making your application work incorrectly.

    There is, however, a lot easier way to do what you've intended to.

     

    if (!AttachConsole(-1))

      AllocConsole();

     

    // Do sth in console

     

    FreeConsole();

     

    -1 is defined as ATTACH_PARENT_PROCESS constant and causes the AttachConsole to attach the calling process to console of its parent process. If it is not possible (for example, program was run by doubleclicking its icon in Windows Explorer), it fails and returns FALSE. In such occasion you may want to allocate your own console and that is precisely what my sample code does :)

    Note, that there is also an elegant way to determine the host process by WinAPI (without trick with capturing active window), but you always risk, that the parent process does not exist anymore. The above method does not suffer from this problem.

    One more thing: both your and mine code will not work as it is supposed to ;) The problem is, that if application is marked as a GUI program, cmd won't wait for it to finish its work and will continue to wait for user's input (or worse, it will continue to execute batch file commands, which may flood the console with their own outputs).

    Best regards -- Spook.

    Sunday, August 14, 2011 7:09 PM
  • One more thing: both your and mine code will not work as it is supposed to ;) The problem is, that if application is marked as a GUI program, cmd won't wait for it to finish its work and will continue to wait for user's input (or worse, it will continue to execute batch file commands, which may flood the console with their own outputs).

    If you're running the app form Powershell, you can use '| Out-Default' which will direct the output to the default output location--the console--but using the pipe will force the command shell to wait until the program exits before proceeding.
    Tuesday, February 07, 2012 2:15 PM