Malfist
Malfist

Reputation: 31815

Writing to the command line in a windowed app

I'm trying to get my WinForm based C# to cooperate with the commandline too, but I'm having difficulty getting it to play nice. For example, I have this code:

    [STAThread]
    static void Main(string[] args) {
        foreach (string s in args) {
            System.Windows.Forms.MessageBox.Show(s);
            Console.WriteLine("String: " + s);
        }

        Mutex appSingleton = new System.Threading.Mutex(false, "WinSyncSingalInstanceMutx");
        if (appSingleton.WaitOne(0, false)) {
            try {
                Application.EnableVisualStyles();
                Application.SetCompatibleTextRenderingDefault(false);

                //start logger
                Logger.singleton.makeOpen(true);
                Application.Run(new MainForm(false));
            } catch (Exception) {
            } finally {
                appSingleton.Close();
                Logger.singleton.makeOpen(false); 
            }
        } else {
            System.Windows.Forms.MessageBox.Show("Sorry, only one instance of WinSync can be ran at once.");
        }
    }
}

It should write to the console with the Console.WriteLine, but I see nothing, only the MessageBox shows up.

What am I doing wrong?

Upvotes: 2

Views: 3075

Answers (6)

Robert Burke
Robert Burke

Reputation:

After playing with this a bit, and seeing the caveats with AllocConsole and AttachConsole, I think the best idea is to have two .NET exe files, say foo.exe and fooconsole.exe. When you need console output, use fooconsole.exe.

All of your code other than your main function can be placed into a .NET project which produces a DLL (class library). This includes all win forms, including your main window. The only thing left in the exe project is a small main function which in turn calls a static function in your DLL. All of this can be done without resorting to P-Invoke.

In your DLL class library:

using System;
using System.Windows.Forms;

namespace MyNamespace
{
    public class MainEntry
    {
        private static bool mIsConsole = false;

        private MainEntry() { }

        public static bool IsConsoleApp
        {
            get { return mIsConsole; }
        }

        public static int DoMain(string[] args, bool isConsole)
        {
            mIsConsole = isConsole;
            try
            {
                // do whatever - main program execution
                return 0; // "Good" DOS return code
            }
            catch (Exception ex)
            {
                if (MainEntry.IsConsoleApp)
                {
                    Console.Error.WriteLine(ex.Message);
                }
                else
                {
                    MessageBox.Show(ex.Message, "Error", MessageBoxButtons.OK, MessageBoxIcon.Error);
                }
                return 1; // "Bad" DOS return code indicating failure
            }
        }
    }
}

In your project which emits a windows EXE (foo.exe), this is the ONLY code:

using System;

namespace MyNamespace
{
    static class Program
    {
        [STAThread]
        static int Main(string[] args)
        {
            return MainEntry.DoMain(args, false);
        }
    }
}

In your project which emits a console EXE (fooconsole.exe), this is the ONLY code:

using System;

namespace MyNamespace
{
    static class Program
    {
        [STAThread]
        static int Main(string[] args)
        {
            return MainEntry.DoMain(args, true);
        }
    }
}

Of course, in both EXE projects, you need a reference to the DLL project in the same solution. On the Application tab of project properties, you can change the Project type - Windows App (EXE), Console App (EXE), or class library (DLL).

Note that it is possible to programatically determine if an EXE file uses the windows or console subsystem, but it is probably not worth it - lots of P-Invoke and you have to look at the bytes of the EXE file's PE header.

Also, the AllocConsole/AttachConsole methods are funny in that if you run your program from the command line or a batch file, and try to redirect the output (stdout and/or stderr) to a file, it wil not work - it will not go to the file. See http://www.nabble.com/WIN32:-Spawning-a-command-line-process-td21681465.html

Again, possible to work around, but it requires more P-Invoke and probably not worth it.

As a best practice, I never put more into an EXE project than the simple Main function that in turn calls a static function in a DLL. There are a lot of reasons, but one good reason is that unit testing tools work better with DLLs than with EXEs.

Note also the form of the Main function which takes a string array of args, and returns an int. This is the best form to use, because you can use ERRORLEVEL in batch files to work with whatever number your EXE returns - traditionally 0 for success and larger than 0 for failure.

Upvotes: 1

Robert Burke
Robert Burke

Reputation:

Try AttachConsole(-1) to redirect Console.Out, worked for me:

using System;
using System.Collections.Generic;
using System.Windows.Forms;
using System.Runtime.InteropServices;

namespace PEFixer
{
    static class Program
    {
        [DllImport("kernel32.dll")]
        private static extern bool AttachConsole(int dwProcessId);

        /// <summary>
        /// The main entry point for the application.
        /// </summary>
        [STAThread]
        static int Main(string[] args)
        {
            if (args.Length > 0)
            {
                AttachConsole(-1);
                return Form1.doTransformCmdLine(args);
            }
            else
            {
                Application.EnableVisualStyles();
                Application.SetCompatibleTextRenderingDefault(false);
                Application.Run(new Form1());
            }
            return 0;
        }
    }
}

Upvotes: 5

BFree
BFree

Reputation: 103770

My suggestion is to create a windows app, and then P/Invoke to get a console. IE:

public Form1()
{
   [DllImport("kernel32.dll")]
   public static extern bool AllocConsole();

   [DllImport("kernel32.dll")]
   public static extern bool FreeConsole();

   public Form1()
   {
      AllocConsole();
      Console.WriteLine("Whatever.");
   }
}

Upvotes: 2

Ian
Ian

Reputation: 34549

What do you need to use it for?

If it's for debugging (as the user will never see a console) then I'd suggest using Debug.WriteLine(). Should then make it to the console without problems...

Upvotes: 0

Grzenio
Grzenio

Reputation: 36679

Windows application model makes it a bit painful. You need to change your application type to Console (in properties), and it should magically start working. Your form will appear anyway, because it is create explicitly by the autogenerated code.

Upvotes: 2

Chris Doggett
Chris Doggett

Reputation: 20777

Are you running it in Debug mode (F5) from Visual Studio, or running it from the command line? In VS, you'd see the console output in the Output tab (Ctrl+Alt+O). Otherwise, there's no console to write to.

Upvotes: 0

Related Questions