tags:

views:

751

answers:

6

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?

A: 

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.

Chris Doggett
I'm running it from CMD.exe
Malfist
Sounds stupid, but try debugging it with a breakpoint on the Console.WriteLine, and see if it's even being hit. The 'foreach' won't call any of its code if there are no arguments on the command line.
Chris Doggett
+2  A: 

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.

Grzenio
That works when I launch it from the command line, but when debugging it in VS, nothing but a console shows up.
Malfist
And when I launch it without trying to use the command line, it still launches a command line. :(
Malfist
Yeah, you can't win - either you will always have the command line or never :(.
Grzenio
I expect you can forget about debugging - the VsHosting process complicates matters even further. It's just a model that's not supported.
Henk Holterman
A: 

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...

Ian
Maybe because I want there to be a way to run my program through the command line without a gui?
Malfist
I think a downvote for this is kind of harsh...(out of votes for today or I'd throw you one to recover)
Kyle Walsh
You're supposed to downvote if the answer was not helpful, and this was not helpful. Sorry.
Malfist
If the answer is de-constructive sure. But I made a suggestion and asked for some clarity. Consoles + Form apps don't run hand in hand, so I was trying to get some more detail from you.
Ian
Thanks for your comment Kyle. (I've still not quite figured out all the voting system yet) but appreciate the support :)
Ian
+2  A: 

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.");
   }
}
BFree
Thanks! This works, however, AllocConsole shouldn't be called unless you actually need one. Also, if I launch the app from a console, AllocConsole opens a new console.
Malfist
Yea, I was just demonstrating it, not that you should actually do it in your constructor. Also, I would make my app a Windows app FIRST if I was going to use any Windows stuff, and then just AllocConsole to mimic Console behavior.
BFree
The windowed part is finished, I'm trying trying to add commandline access to it since I'm used to using the command line on linux and this is going to be deployed to servers and may need to run as a service or on a cron job
Malfist
+3  A: 

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;
        }
    }
}
Doesn't quite work either, doesn't exactly attach the console.
Malfist
For some reason sometimes (!!?) it doesn't attach to console. This may be some interaction with administrative rights/etc. If it doesn't seem to work, restart cmd.exe, it may help.
Pasi Savolainen
+1  A: 

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.