在窗口应用程序中写入命令行

我正在尝试让我的基于WinForm的C#与命令行合作,但是我很难让它发挥得很好。 例如,我有这个代码:

[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."); } } } 

它应该使用Console.WriteLine写入控制台,但我什么也看不见,只显示MessageBox。

我究竟做错了什么?

尝试AttachConsole(-1)重定向Console.Out,为我工作:

 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); ///  /// The main entry point for the application. ///  [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; } } } 

Windows应用程序模型使它有点痛苦。 您需要将应用程序类型更改为Console(在属性中),它应该神奇地开始工作。 无论如何,您的表单都会出现,因为它是由自动生成的代码显式创建的。

我的建议是创建一个Windows应用程序,然后P / Invoke来获得一个控制台。 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."); } } 

稍微玩了一下,并看到AllocConsole和AttachConsole的警告,我认为最好的想法是有两个.NET exe文件,比如foo.exe和fooconsole.exe。 当您需要控制台输出时,请使用fooconsole.exe。

您的主要function以外的所有代码都可以放在.NET项目中,该项目会生成DLL(类库)。 这包括所有获胜表格,包括您的主窗口。 exe项目中唯一剩下的就是一个小的main函数,它反过来调用DLL中的静态函数。 所有这一切都可以在不诉诸P-Invoke的情况下完成。

在你的DLL类库中:

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

在发出Windows EXE(foo.exe)的项目中,这是唯一的代码:

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

在发出控制台EXE(fooconsole.exe)的项目中,这是唯一的代码:

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

当然,在两个EXE项目中,您需要在同一解决方案中引用DLL项目。 在项目属性的“应用程序”选项卡上,可以更改项目类型 – Windows应用程序(EXE),控制台应用程序(EXE)或类库(DLL)。

请注意,可以以编程方式确定EXE文件是否使用Windows或控制台子系统,但它可能不值得 – 许多P-Invoke并且您必须查看EXE文件的PE头的字节。

此外,AllocConsole / AttachConsole方法很有趣,如果您从命令行或批处理文件运行程序,并尝试将输出(stdout和/或stderr)重定向到文件,它将无法工作 – 它不会转到该文件。 见http://www.nabble.com/WIN32:-Spawning-a-command-line-process-td21681465.html

再次,可以解决,但它需要更多的P-Invoke,可能不值得。

作为一种最佳实践,我从未在EXE项目中投入比简单的Main函数更多的东西,而Main函数又调用DLL中的静态函数。 有很多原因,但有一个很好的理由是unit testing工具使用DLL比使用EXE更好。

还要注意Main函数的forms,它接受args的字符串数组,并返回一个int。 这是最好的使用forms,因为您可以在批处理文件中使用ERRORLEVEL来处理您的EXE返回的任何数字 – 传统上0表示成功,大于0表示失败。

您是从Visual Studio在调试模式(F5)中运行它,还是从命令行运行它? 在VS中,您将在“输出”选项卡中看到控制台输出(Ctrl + Alt + O)。 否则,没有可写入的控制台。

你需要用它做什么?

如果它是用于调试(因为用户永远不会看到控制台),那么我建议使用Debug.WriteLine()。 然后应该毫无问题地进入控制台……