¿Cómo creo una aplicación C # que decide si mostrarse como consola o aplicación de ventana?

¿Hay alguna manera de lanzar una aplicación C # con las siguientes características?

  1. Determina por parámetros de línea de comandos si se trata de una aplicación de ventana o consola
  2. No muestra una consola cuando se solicita su ventana y no muestra una ventana de GUI cuando se ejecuta desde la consola.

Por ejemplo,

  myapp.exe / help 

saldría a stdout en la consola que usaste, pero

  myapp.exe 

por sí solo lanzaría mi interfaz de usuario Winforms o WPF.

Las mejores respuestas que conozco hasta ahora implican tener dos ejecuciones separadas y usar IPC, pero eso se siente muy raro.

¿Qué opciones tengo y las compensaciones puedo hacer para obtener el comportamiento descrito en el ejemplo anterior? También estoy abierto a ideas que sean específicas de Winform o específicas de WPF.

Convierta la aplicación en una aplicación de Windows normal y cree una consola sobre la marcha si es necesario.

Más detalles en este enlace (código debajo de allí)

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

Básicamente, lo hago de la forma que se muestra en la respuesta de Eric, y además desconecto la consola con FreeConsole y uso el comando SendKeys para recuperar el comando.

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

Escriba dos aplicaciones (una consola, una ventana) y luego escriba otra aplicación más pequeña que, en función de los parámetros proporcionados, abra una de las otras aplicaciones (y luego se cerraría, ya que ya no sería necesaria).

Lo hice creando dos aplicaciones separadas.

Crea la aplicación WPF con este nombre: MyApp.exe . Y crea la aplicación de consola con este nombre: MyApp.com . Cuando escriba el nombre de su aplicación en la línea de comandos como esta MyApp o MyApp /help (sin la extensión .exe ), la aplicación de la consola con la extensión .com tendrá prioridad. Puede hacer que su aplicación de consola invoque MyApp.exe según los parámetros.

Así es exactamente como se comporta devenv. Al escribir devenv en la línea de comando se devenv el IDE de Visual Studio. Si pasa parámetros como /build , permanecerá en la línea de comando.

NOTA: No lo he probado, pero creo que funcionaría …

Podrías hacer esto:

Convierta su aplicación en una aplicación de formularios de Windows. Si recibe una solicitud de consola, no muestre su formulario principal. En su lugar, use la invocación de la plataforma para llamar a las funciones de la consola en la API de Windows y asigne una consola sobre la marcha.

(Alternativamente, use la API para ocultar la consola en una aplicación de consola, pero probablemente vea la consola “parpadear” como se creó en este caso …)

Por lo que sé, hay una bandera en el exe que dice si se ejecuta como consola o aplicación de ventana. Puede pasar la bandera con herramientas que vienen con Visual Studio, pero no puede hacerlo en tiempo de ejecución.

Si el exe se comstack como una consola, siempre abrirá una nueva consola si no se inicia desde una. Si el exe es una aplicación, no puede enviar a la consola. Puede generar una consola separada, pero no se comportará como una aplicación de consola.

Yo el pasado hemos usado 2 exe’s separados. La consola es una envoltura delgada sobre los formularios uno (puede hacer referencia a un exe como haría referencia a un dll, y puede usar el atributo [assembly: InternalsVisibleTo (“cs_friend_assemblies_2”)] para confiar en la consola uno, así que no lo hace t tiene que exponer más de lo necesario).

Crearía una solución que es una aplicación de Windows Form ya que hay dos funciones a las que puede llamar que enlazarán con la consola actual. Entonces puede tratar el progtwig como un progtwig de consola. o de forma predeterminada, puede iniciar la GUI.

La función AttachConsole no creará una nueva consola. Para obtener más información acerca de AttachConsole, consulte PInvoke: AttachConsole

Debajo de un progtwig de muestra de cómo usarlo.

 using System.Runtime.InteropServices; namespace Test { ///  /// 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. ///  [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(); } } ///  /// Handles the cleaning up of resources after the application has been closed ///  ///  public static void Application_ApplicationExit(object sender, System.EventArgs e) { FreeConsole(); } } 

Tal vez este enlace proporcione alguna idea de lo que estás buscando hacer.

Una forma de hacerlo es escribir una aplicación Window que no muestre una ventana si los argumentos de la línea de comando indican que no debería mostrarse.

Siempre puede obtener los argumentos de la línea de comando y verificarlos antes de mostrar la primera ventana.

Lo importante que debe recordar después de las AttachConsole() o AllocConsole() para que funcione en todos los casos es:

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

He encontrado que funciona con o sin proceso de alojamiento VS. Con la salida que se envía con System.Console.WriteLine o System.Console.out.WriteLine antes de llamar a AttachConsole o AllocConsole . He incluido mi método a continuación:

 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(System.Linq.Enumerable.Repeat(' ', 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; } 

También llamé a esto cuando terminé en caso de que necesitara un símbolo del sistema para volver a mostrar cuando terminé de hacer la salida.

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

Espero que esto ayude…

No 1 es fácil.

No 2 no se puede hacer, no creo.

Los documentos dicen:

Las llamadas a métodos como Write y WriteLine no tienen ningún efecto en las aplicaciones de Windows.

La clase System.Console se inicializa de manera diferente en las aplicaciones de consola y GUI. Puede verificar esto mirando la clase de la consola en el depurador en cada tipo de aplicación. No estoy seguro de si hay alguna forma de reiniciarlo.

Demostración: cree una nueva aplicación de Windows Forms, luego reemplace el método Principal con esto:

  static void Main(string[] args) { if (args.Length == 0) { Application.EnableVisualStyles(); Application.SetCompatibleTextRenderingDefault(false); Application.Run(new Form1()); } else { Console.WriteLine("Console!\r\n"); } } 

La idea es que los parámetros de línea de comandos se imprimirán en la consola y saldrán. Cuando lo ejecutas sin argumentos, obtienes la ventana. Pero cuando lo ejecuta con un argumento de línea de comando, no pasa nada.

Luego seleccione las propiedades del proyecto, cambie el tipo de proyecto a “Aplicación de consola” y vuelva a comstackr. Ahora cuando lo ejecutas con un argumento, obtienes “Console!” como quieras. Y cuando lo ejecuta (desde la línea de comando) sin argumentos, obtiene la ventana. Pero el símbolo del sistema no volverá hasta que salga del progtwig. Y si ejecuta el progtwig desde Explorer, se abrirá una ventana de comando y luego obtendrá una ventana.

He encontrado una forma de hacerlo, incluido el uso de stdin, pero debo advertirte que no es bonito.

El problema con el uso de stdin desde una consola conectada es que el shell también leerá de él. Esto hace que la entrada a veces vaya a su aplicación pero a veces al shell.

La solución es bloquear el shell durante la vida útil de las aplicaciones (aunque técnicamente podría intentar bloquearlo solo cuando lo necesite). La forma en que elijo hacer esto es enviando las teclas al intérprete de comandos para ejecutar un comando de PowerShell que espera a que finalice la aplicación.

Por cierto, esto también soluciona el problema de que el aviso no regrese después de que la aplicación finaliza.

Intenté brevemente hacer que funcione desde la consola de powershell también. Se aplican los mismos principios, pero no pude ejecutar mi comando. Es posible que powershell tenga algunas comprobaciones de seguridad para evitar la ejecución de comandos desde otras aplicaciones. Debido a que no uso mucho Powell, no lo investigué.

  [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); ///  /// Attach to existing console or create new. Must be called before using System.Console. ///  /// Return true if console exists or is created. 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(); }