views:

1632

answers:

10

Is there a way to launch a C# application with the following features?

  1. It determines by command-line parameters whether it is a windowed or console app
  2. It doesn't show a console when it is asked to be windowed and doesn't show a GUI window when it is running from the console.

For example,

myapp.exe /help
would output to stdout on the console you used, but
myapp.exe
by itself would launch my Winforms or WPF user interface.

The best answers I know of so far involve having two separate exe and use IPC, but that feels really hacky.


What options do I have and trade-offs can I make to get the behavior described in the example above? I'm open to ideas that are Winform-specific or WPF-specific, too.

+6  A: 

Write two apps (one console, one windows) and then write another smaller app which based on the parameters given opens up one of the other apps (and then would presumably close itself since it would no longer be needed)?

TheTXI
This approach seems the least hacky to me. You have a clear separation of concerns and are keeping things lean and simple.
AndyM
+1  A: 

Maybe this link will provide some insight into what you are looking to do.

Jason Miesionczek
+3  A: 

NOTE: I haven't tested this, but I believe it would work...

You could do this:

Make your app a windows forms application. If you get a request for console, don't show your main form. Instead, use platform invoke to call into the Console Functions in the Windows API and allocate a console on the fly.

(Alternatively, use the API to hide the console in a console app, but you'd probably see the console "flicker" as it was created in this case...)

Reed Copsey
+1, and Eric P added a code sample
Lucas
+1  A: 

One way to do this is to write a Window app that doesn't show a window if the command line arguments indicate it shouldn't.

You can always get the command line arguments and check them before showing the first window.

Arnshea
but what about opening the console window when not showing a window?
Lucas
open up cmd.exe?
Arnshea
+2  A: 

As far as I am aware there is a flag in the exe that tells it whether to run as console or windowed app. You can flick the flag with tools that come with Visual Studio, but you cann't do this at runtime.

If the exe is compiled as a console, then it will always open a new console if its not started from one. If the the exe is an application then it can't output to the console. You can spawn a separate console - but it won't behave like a console app.

I the past we have used 2 separate exe's. The console one being a thin wrapper over the forms one (you can reference an exe as you would reference a dll, and you can use the [assembly:InternalsVisibleTo("cs_friend_assemblies_2")] attribute to trust the console one, so you don't have to expose more than you need to).

Colin
+24  A: 

Make the app a regular windows app, and create a console on the fly if needed.

More details at this link (code below from there)

using System;
using System.Windows.Forms;

namespace WindowsApplication1 {
  static class Program {
    [STAThread]
    static void Main(string[] args) {
      if (args.Length > 0) {
        // Command line given, display console
        if ( !AttachConsole(-1) ) { // Attach to an parent process console
           AllocConsole(); // Alloc a new console
        }

        ConsoleMain(args);
      }
      else {
        Application.EnableVisualStyles();
        Application.SetCompatibleTextRenderingDefault(false);
        Application.Run(new Form1());
      }
    }
    private static void ConsoleMain(string[] args) {
      Console.WriteLine("Command line = {0}", Environment.CommandLine);
      for (int ix = 0; ix < args.Length; ++ix)
        Console.WriteLine("Argument{0} = {1}", ix + 1, args[ix]);
      Console.ReadLine();
    }

    [System.Runtime.InteropServices.DllImport("kernel32.dll")]
    private static extern bool AllocConsole();

    [System.Runtime.InteropServices.DllImport("kernel32.dll")]
    private static extern bool AttachConsole(int pid);

  }
}
Eric Petroelje
Doesn't this create a new console window? I thought the original problem was to output to the "console you used" when the user types MyApp.exe /help.
MichaC
You are right - to attach to an existing console, you would use AttachConsole(-1). Updated code to reflect that.
Eric Petroelje
The biggest glitch with this approach seems to be that the process returns right away to the launching console when you launch from a shell. In that case, the stdout begins writing over other the rest of the text as you use that shell/console.Would be very handy if there was a way to avoid that, too, but I haven't found anything yet.
Matthew
Most WinForm Apps have an option to close the executable down when you close the main form. If you follow this convention of using a mainform, you could do a .ShowDialog on your main form, so when it closes, the entire app closes. My answer below has an example of this.
Richard R
+3  A: 

I've done this by creating two separate apps.

Create the WPF app with this name: MyApp.exe. And create the console app with this name: MyApp.com. When you type your app name in the command line like this MyApp or MyApp /help (without .exe extension) the console app with the .com extension will take precedence. You can have your console application invoke the MyApp.exe according to the parameters.

This is exactly how devenv behaves. Typing devenv at the command line will launch Visual Studio's IDE. If you pass parameters like /build, it will remain in the command line.

Anthony Brien
Yeah, this is the old school way I've used in the past. I was hoping for another approach. Thanks, though.
Matthew
+1  A: 

No 1 is easy.

No 2 can't be done, I don't think.

The docs say:

Calls to methods such as Write and WriteLine have no effect in Windows applications.

The System.Console class is initialized differently in console and GUI applications. You can verify this by looking at the Console class in the debugger in each application type. Not sure if there's any way to re-initialize it.

Demo: Create a new Windows Forms app, then replace the Main method with this:

    static void Main(string[] args)
    {
        if (args.Length == 0)
        {
            Application.EnableVisualStyles();
            Application.SetCompatibleTextRenderingDefault(false);
            Application.Run(new Form1());
        }
        else
        {
            Console.WriteLine("Console!\r\n");
        }
    }

The idea is that any command line parameters will print to the console and exit. When you run it with no arguments, you get the window. But when you run it with a command line argument, nothing happens.

Then select the project properties, change the project type to "Console Application", and recompile. Now when you run it with an argument, you get "Console!" like you want. And when you run it (from the command line) with no arguments, you get the window. But the command prompt won't return until you exit the program. And if you run the program from Explorer, a command window will open and then you get a window.

+1  A: 

I would create a solution that is a Windows Form App since there are two functions you can call that will hook into the current console. So you can treat the program like a console program. or by default you can launch the GUI.

The AttachConsole function will not create a new console. For more information about AttachConsole, check out PInvoke: AttachConsole

Below a sample program of how to use it.

using System.Runtime.InteropServices;

namespace Test
{
    /// <summary>
    /// This function will attach to the console given a specific ProcessID for that Console, or
    /// the program will attach to the console it was launched if -1 is passed in.
    /// </summary>
    [DllImport("kernel32.dll", SetLastError = true)]
    private static extern bool AttachConsole(int dwProcessId);

    [DllImport("kernel32.dll", SetLastError = true)]
    private static extern bool FreeConsole();


    [STAThread]
    public static void Main() 
    {   
        Application.ApplicationExit +=new EventHandler(Application_ApplicationExit);
        string[] commandLineArgs = System.Environment.GetCommandLineArgs();

        if(commandLineArgs[0] == "-cmd")
        {
            //attaches the program to the running console to map the output
            AttachConsole(-1);
        }
        else
        {
            //Open new form and do UI stuff
            Form f = new Form();
            f.ShowDialog();
        }

    }

    /// <summary>
    /// Handles the cleaning up of resources after the application has been closed
    /// </summary>
    /// <param name="sender"></param>
    public static void Application_ApplicationExit(object sender, System.EventArgs e)
    {
        FreeConsole();
    }
}
Richard R
+3  A: 

I blogged about this a while back How To Create a Console/Window Hybrid Application in C#

Jeffrey Knight