【问题标题】:Writing to the command line in a windowed app在窗口化应用程序中写入命令行
【发布时间】:2010-10-14 14:06:45
【问题描述】:

我正在尝试让我的基于 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 出现。

我做错了什么?

【问题讨论】:

    标签: c# .net user-interface console


    【解决方案1】:

    尝试 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);
    
            /// <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;
            }
        }
    }
    

    【讨论】:

    • 也不太好用,不能完全连接控制台。
    • 出于某种原因,有时(!!?)它没有附加到控制台。这可能是与管理权限/等的一些交互。如果它似乎不起作用,请重新启动 cmd.exe,它可能会有所帮助。
    【解决方案2】:

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

    【讨论】:

    • 当我从命令行启动它时有效,但在 VS 中调试它时,只显示一个控制台。
    • 当我启动它而不尝试使用命令行时,它仍然启动命令行。 :(
    • 是的,你赢不了——要么你永远有命令行,要么永远不会:(。
    【解决方案3】:

    我的建议是创建一个 Windows 应用程序,然后 P/Invoke 以获得一个控制台。即:

    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。此外,如果我从控制台启动应用程序,AllocConsole 会打开一个新控制台。
    • 是的,我只是在演示它,并不是说您应该在构造函数中实际执行它。另外,如果我要使用任何 Windows 的东西,我会让我的应用程序成为 Windows 应用程序,然后只是 AllocConsole 来模仿控制台行为。
    • 窗口部分已完成,我正在尝试添加对其的命令行访问,因为我习惯于在 linux 上使用命令行,这将被部署到服务器上,可能需要作为服务或 cron 作业运行
    【解决方案4】:

    在玩了一会儿之后,看到 AllocConsole 和 AttachConsole 的注意事项,我认为最好的办法是拥有两个 .NET exe 文件,比如 foo.exe 和 fooconsole.exe。当您需要控制台输出时,请使用 fooconsole.exe。

    除了 main 函数之外,您的所有代码都可以放入生成 DLL(类库)的 .NET 项目中。这包括所有获胜表格,包括您的主窗口。 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 项目中添加过比调用 DLL 中的静态函数的简单 Main 函数更多的内容。原因有很多,但一个很好的原因是单元测试工具与 DLL 相比,与 EXE 相比更有效。

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

    【讨论】:

      【解决方案5】:

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

      【讨论】:

      • 听起来很愚蠢,但尝试在 Console.WriteLine 上使用断点调试它,看看它是否被命中。如果命令行上没有参数,'foreach' 不会调用它的任何代码。
      【解决方案6】:

      你需要用它做什么?

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

      【讨论】:

      • 也许是因为我希望有一种方法可以在没有 gui 的情况下通过命令行运行我的程序?
      • 我认为对此投反对票有点苛刻......(今天没有票,否则我会扔给你一个来恢复)
      • 如果答案没有帮助,您应该投反对票,而这没有帮助。对不起。
      • 如果答案是解构的。但我提出了一个建议并要求澄清一下。控制台 + 表单应用程序不能同时运行,所以我试图从您那里获得更多详细信息。
      • 感谢您的评论凯尔。 (我还没有完全弄清楚所有的投票系统)但感谢支持:)
      猜你喜欢
      • 1970-01-01
      • 2012-01-21
      • 1970-01-01
      • 2023-04-04
      • 2016-03-30
      • 1970-01-01
      • 1970-01-01
      • 2012-01-27
      • 1970-01-01
      相关资源
      最近更新 更多