如何创build一个C#应用程序来决定是否显示为控制台或窗口应用程序?
有没有办法启动具有以下function的C#应用程序?
- 它通过命令行参数确定它是一个窗口或控制台应用程序
- 当它被要求窗口化时,它不显示控制台,并且在从控制台运行时不显示GUI窗口。
例如,
myapp.exe /帮助
会输出到你使用的控制台的标准输出,但是
MyApp.exe的
本身会启动我的Winforms或WPF用户界面。
到目前为止我所知道的最好的答案涉及到有两个单独的EXE和使用IPC,但是这感觉真的很黑。
我可以做什么select和权衡取舍上面的例子中描述的行为? 我接受的是Winform特定的或WPF特定的想法。
使应用程序成为一个普通的Windows应用程序,并根据需要即时创build控制台。
在这个链接的更多细节(从那里代码)
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的回答描述的方式,另外我用FreeConsole分离控制台并使用SendKeys命令来获取命令提示符。
[DllImport("kernel32.dll")] private static extern bool AllocConsole(); [DllImport("kernel32.dll")] private static extern bool AttachConsole(int pid); [DllImport("kernel32.dll", SetLastError = true)] private static extern bool FreeConsole(); [STAThread] static void Main(string[] args) { if (args.Length > 0 && (args[0].Equals("/?") || args[0].Equals("/help", StringComparison.OrdinalIgnoreCase))) { // get console output if (!AttachConsole(-1)) AllocConsole(); ShowHelp(); // show help output with Console.WriteLine FreeConsole(); // detach console // get command prompt back System.Windows.Forms.SendKeys.SendWait("{ENTER}"); return; } // normal winforms code Application.EnableVisualStyles(); Application.SetCompatibleTextRenderingDefault(false); Application.Run(new MainForm()); }
写两个应用程序(一个控制台,一个窗口),然后写另一个小的应用程序,基于给定的参数打开其他应用程序(然后大概会closures自己,因为它不再需要)之一?
我通过创build两个单独的应用程序来完成此操作
使用此名称创buildWPF应用程序: MyApp.exe
。 并使用以下名称创build控制台应用程序: MyApp.com
。 当您在MyApp
或MyApp /help
(不带.exe
扩展名)的命令行中键入您的应用程序名称时,具有.com
扩展名的控制台应用程序将优先。 您可以让您的控制台应用程序根据参数调用MyApp.exe
。
这正是devenv的行为。 在命令行键入devenv
将启动Visual Studio的IDE。 如果你传递/build
等参数,它将保留在命令行中。
注:我没有testing过这个,但我相信它会工作…
你可以这样做:
使您的应用程序为Windows窗体应用程序。 如果您收到控制台请求,请不要显示主窗体。 而是使用平台调用来调用Windows API中的控制台function ,并即时分配控制台。
(或者,使用API将控制台隐藏在控制台应用程序中,但是在这种情况下,您可能会看到控制台“闪烁”,因为它是在这种情况下创build的)
据我所知在exe中有一个标志,告诉它是否作为控制台或窗口的应用程序运行。 您可以使用Visual Studio附带的工具轻弹标志,但在运行时不能这样做。
如果exe被编译为一个控制台,那么它将永远打开一个新的控制台,如果它不是从一个开始。 如果exe是一个应用程序,那么它不能输出到控制台。 你可以产生一个单独的控制台 – 但它不会像控制台应用程序。
我过去使用过2个独立的exe。 控制台之一是一个薄的包装forms之一(你可以引用一个EXE,因为你会引用一个DLL,你可以使用[汇编:InternalsVisibleTo(“cs_friend_assemblies_2”)]属性信任控制台之一,所以你不'不得不暴露超过你所需要的)。
我会创build一个解决scheme,这是一个Windows窗体应用程序,因为有两个function,你可以调用,将挂钩到当前的控制台。 所以你可以把这个程序当作一个控制台程序。 或者默认情况下可以启动GUI。
AttachConsole函数不会创build一个新的控制台。 有关AttachConsole的更多信息,请查看PInvoke:AttachConsole
下面是一个如何使用它的示例程序。
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(); } }
也许这个链接将提供一些你想要做的事情。
一种方法是编写一个窗口应用程序,如果命令行参数表明它不应该显示窗口。
您可以随时获得命令行参数,并在显示第一个窗口之前检查它们。
在AttachConsole()
或AllocConsole()
调用之后要记住要使其在所有情况下都能正常工作的重要一点是:
if (AttachConsole(ATTACH_PARENT_PROCESS)) { System.IO.StreamWriter sw = new System.IO.StreamWriter(System.Console.OpenStandardOutput()); sw.AutoFlush = true; System.Console.SetOut(sw); System.Console.SetError(sw); }
我发现,无论是否有VS托pipe过程。 在调用AttachConsole
或AllocConsole
之前,使用System.Console.WriteLine
或System.Console.out.WriteLine
发送输出。 我已经包括我的方法如下:
public static bool DoConsoleSetep(bool ClearLineIfParentConsole) { if (GetConsoleWindow() != System.IntPtr.Zero) { return true; } if (AttachConsole(ATTACH_PARENT_PROCESS)) { System.IO.StreamWriter sw = new System.IO.StreamWriter(System.Console.OpenStandardOutput()); sw.AutoFlush = true; System.Console.SetOut(sw); System.Console.SetError(sw); ConsoleSetupWasParentConsole = true; if (ClearLineIfParentConsole) { // Clear command prompt since windows thinks we are a windowing app System.Console.CursorLeft = 0; char[] bl = System.Linq.Enumerable.ToArray<char>(System.Linq.Enumerable.Repeat<char>(' ', System.Console.WindowWidth - 1)); System.Console.Write(bl); System.Console.CursorLeft = 0; } return true; } int Error = System.Runtime.InteropServices.Marshal.GetLastWin32Error(); if (Error == ERROR_ACCESS_DENIED) { if (log.IsDebugEnabled) log.Debug("AttachConsole(ATTACH_PARENT_PROCESS) returned ERROR_ACCESS_DENIED"); return true; } if (Error == ERROR_INVALID_HANDLE) { if (AllocConsole()) { System.IO.StreamWriter sw = new System.IO.StreamWriter(System.Console.OpenStandardOutput()); sw.AutoFlush = true; System.Console.SetOut(sw); System.Console.SetError(sw); return true; } } return false; }
当我完成后,我也会调用这个函数,以防我在完成输出时需要命令提示符重新显示。
public static void SendConsoleInputCR(bool UseConsoleSetupWasParentConsole) { if (UseConsoleSetupWasParentConsole && !ConsoleSetupWasParentConsole) { return; } long LongNegOne = -1; System.IntPtr NegOne = new System.IntPtr(LongNegOne); System.IntPtr StdIn = GetStdHandle(STD_INPUT_HANDLE); if (StdIn == NegOne) { return; } INPUT_RECORD[] ira = new INPUT_RECORD[2]; ira[0].EventType = KEY_EVENT; ira[0].KeyEvent.bKeyDown = true; ira[0].KeyEvent.wRepeatCount = 1; ira[0].KeyEvent.wVirtualKeyCode = 0; ira[0].KeyEvent.wVirtualScanCode = 0; ira[0].KeyEvent.UnicodeChar = '\r'; ira[0].KeyEvent.dwControlKeyState = 0; ira[1].EventType = KEY_EVENT; ira[1].KeyEvent.bKeyDown = false; ira[1].KeyEvent.wRepeatCount = 1; ira[1].KeyEvent.wVirtualKeyCode = 0; ira[1].KeyEvent.wVirtualScanCode = 0; ira[1].KeyEvent.UnicodeChar = '\r'; ira[1].KeyEvent.dwControlKeyState = 0; uint recs = 2; uint zero = 0; WriteConsoleInput(StdIn, ira, recs, out zero); }
希望这可以帮助…
没有1是容易的。
没有2不能做,我不认为。
文档说:
调用Write和WriteLine等方法在Windows应用程序中不起作用。
System.Console类在控制台和GUI应用程序中的初始化方式不同。 您可以通过在每个应用程序types的debugging器中查看控制台类来validation这一点。 不知道是否有任何方法来重新初始化它。
演示:创build一个新的Windows窗体应用程序,然后用这个replaceMain方法:
static void Main(string[] args) { if (args.Length == 0) { Application.EnableVisualStyles(); Application.SetCompatibleTextRenderingDefault(false); Application.Run(new Form1()); } else { Console.WriteLine("Console!\r\n"); } }
这个想法是,任何命令行参数将打印到控制台并退出。 当你没有参数运行时,你会看到窗口。 但是当你用命令行参数运行它时,什么也不会发生。
然后select项目属性,将项目types更改为“控制台应用程序”,然后重新编译。 现在当你用参数运行它时,你会得到“Console!” 像你要的那样。 而当你运行它(从命令行)没有参数,你得到的窗口。 但是,直到退出程序,命令提示符才会返回。 如果你从资源pipe理器中运行程序,命令窗口会打开,然后你得到一个窗口。
我已经制定了一个方法来做到这一点,包括使用标准input,但我必须警告你,这不是很好。
从附加控制台使用stdin的问题是,shell也会从中读取。 这会导致input有时会转到您的应用程序,但有时会转到shell。
解决方法是在应用程序生命周期内阻止shell程序(尽pipe从技术上讲,只有在需要input时才可以尝试阻止它)。 我select这样做的方式是通过向shell发送按键来运行等待应用程序终止的powershell命令。
顺便说一句,这也解决了应用程序终止后提示不能回来的问题。
我已经简要地尝试从PowerShell控制台中使用它。 同样的原则适用,但我没有得到它执行我的命令。 这可能是PowerShell有一些安全检查,以防止从其他应用程序运行命令。 因为我没有使用PowerShell,我没有看到它。
[DllImport("kernel32.dll", SetLastError = true)] private static extern bool AllocConsole(); [DllImport("kernel32", SetLastError = true)] private static extern bool AttachConsole(int dwProcessId); private const uint STD_INPUT_HANDLE = 0xfffffff6; private const uint STD_OUTPUT_HANDLE = 0xfffffff5; private const uint STD_ERROR_HANDLE = 0xfffffff4; [DllImport("kernel32.dll")] private static extern IntPtr GetStdHandle(uint nStdHandle); [DllImport("Kernel32.dll", SetLastError = true)] public static extern int SetStdHandle(uint nStdHandle, IntPtr handle); [DllImport("kernel32.dll", SetLastError = true)] private static extern int GetConsoleProcessList(int[] ProcessList, int ProcessCount); [DllImport("user32.dll")] public static extern IntPtr SendMessage(IntPtr hWnd, uint Msg, IntPtr wParam, IntPtr lParam); [DllImport("user32.dll")] public static extern IntPtr PostMessage(IntPtr hWnd, uint Msg, IntPtr wParam, IntPtr lParam); /// <summary> /// Attach to existing console or create new. Must be called before using System.Console. /// </summary> /// <returns>Return true if console exists or is created.</returns> public static bool InitConsole(bool createConsole = false, bool suspendHost = true) { // first try to attach to an existing console if (AttachConsole(-1)) { if (suspendHost) { // to suspend the host first try to find the parent var processes = GetConsoleProcessList(); Process host = null; string blockingCommand = null; foreach (var proc in processes) { var netproc = Process.GetProcessById(proc); var processName = netproc.ProcessName; Console.WriteLine(processName); if (processName.Equals("cmd", StringComparison.OrdinalIgnoreCase)) { host = netproc; blockingCommand = $"powershell \"& wait-process -id {Process.GetCurrentProcess().Id}\""; } else if (processName.Equals("powershell", StringComparison.OrdinalIgnoreCase)) { host = netproc; blockingCommand = $"wait-process -id {Process.GetCurrentProcess().Id}"; } } if (host != null) { // if a parent is found send keystrokes to simulate a command var cmdWindow = host.MainWindowHandle; if (cmdWindow == IntPtr.Zero) Console.WriteLine("Main Window null"); foreach (char key in blockingCommand) { SendChar(cmdWindow, key); System.Threading.Thread.Sleep(1); // required for powershell } SendKeyDown(cmdWindow, Keys.Enter); // i haven't worked out how to get powershell to accept a command, it might be that this is a security feature of powershell if (host.ProcessName == "powershell") Console.WriteLine("\r\n *** PRESS ENTER ***"); } } return true; } else if (createConsole) { return AllocConsole(); } else { return false; } } private static void SendChar(IntPtr cmdWindow, char k) { const uint WM_CHAR = 0x0102; IntPtr result = PostMessage(cmdWindow, WM_CHAR, ((IntPtr)k), IntPtr.Zero); } private static void SendKeyDown(IntPtr cmdWindow, Keys k) { const uint WM_KEYDOWN = 0x100; const uint WM_KEYUP = 0x101; IntPtr result = SendMessage(cmdWindow, WM_KEYDOWN, ((IntPtr)k), IntPtr.Zero); System.Threading.Thread.Sleep(1); IntPtr result2 = SendMessage(cmdWindow, WM_KEYUP, ((IntPtr)k), IntPtr.Zero); } public static int[] GetConsoleProcessList() { int processCount = 16; int[] processList = new int[processCount]; // supposedly calling it with null/zero should return the count but it didn't work for me at the time // limiting it to a fixed number if fine for now processCount = GetConsoleProcessList(processList, processCount); if (processCount <= 0 || processCount >= processList.Length) return null; // some sanity checks return processList.Take(processCount).ToArray(); }