2009-04-30 3 views
44

Gibt es eine Möglichkeit, eine C# -Anwendung mit den folgenden Funktionen zu starten?Wie erstelle ich eine C# -App, die selbst entscheidet, ob sie als Konsole oder gefensterte App angezeigt wird?

  1. Er bestimmt durch Befehlszeilenparameter, ob es sich um eine Fenster oder Konsolenanwendung
  2. Es zeigt nicht eine Konsole, wenn es um sein Fenster gefragt wird, und zeigt keine Fenster GUI, wenn es ausgeführt wird von der Konsole.

Zum Beispiel

myapp.exe /help
würde Ausgabe auf der Konsole stdout Sie verwenden, aber
myapp.exe
selbst würde meinen WinForms oder WPF Benutzeroberfläche starten.

Die besten Antworten, die ich bis jetzt kenne, beinhalten zwei separate exe und IPC zu verwenden, aber das fühlt sich wirklich hacky an.


Welche Optionen habe ich und Trade-offs kann ich machen, um das im obigen Beispiel beschriebene Verhalten zu erhalten? Ich bin offen für Ideen, die Winform-spezifisch oder WPF-spezifisch sind.

Antwort

57

Machen Sie die App zu einer normalen Windows-App und erstellen Sie bei Bedarf eine Konsole.

Weitere Details unter this link (Code unten von dort)

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

    } 
} 
+1

Erstellt dies nicht ein neues Konsolenfenster? Ich dachte, das ursprüngliche Problem war die Ausgabe auf die "Konsole, die Sie verwendet haben", wenn der Benutzer MyApp.exe/help eingibt. – MichaC

+0

Sie haben recht: Um an eine vorhandene Konsole anzuhängen, würden Sie AttachConsole (-1) verwenden. Aktualisierter Code, um dies zu berücksichtigen. –

+9

Der größte Fehler bei diesem Ansatz scheint zu sein, dass der Prozess sofort zur Startkonsole zurückkehrt, wenn Sie von einer Shell starten. In diesem Fall beginnt das stdout, den Rest des Textes zu schreiben, während Sie diese Shell/Konsole benutzen. Wäre sehr praktisch, wenn es einen Weg gibt, das zu vermeiden, aber ich habe noch nichts gefunden. – Matthew

7

Schreibe zwei Apps (eine Konsole, ein Fenster) und schreibe dann eine weitere kleinere App, die basierend auf den angegebenen Parametern eine der anderen Apps öffnet (und sich dann vermutlich selbst schließt, da sie nicht mehr benötigt wird)?

+0

Dieser Ansatz scheint mir am wenigsten hacky. Sie haben eine klare Trennung der Sorgen und halten die Dinge schlank und einfach. – AndyM

1

Vielleicht wird diese link einen Einblick in das, was Sie suchen, geben.

4

Anmerkung: Ich habe nicht getestet, aber ich glaube, es würde funktionieren ...

Sie dies tun könnte:

Machen Sie Ihre App ein Windows Forms-Anwendung. Wenn Sie eine Anfrage für die Konsole erhalten, zeigen Sie Ihr Hauptformular nicht an. Verwenden Sie stattdessen platform invoke, um die Console Functions in der Windows-API aufzurufen und eine Konsole während des Betriebs zuzuweisen.

(Alternativ können Sie die API, um die Konsole in einer Konsolenanwendung verstecken, aber Sie würden wahrscheinlich die Konsole „Flimmern“ sehen, wie es in diesem Fall erstellt wurde ...)

+0

+1, und Eric P hat ein Codebeispiel – Lucas

1

Eine Möglichkeit, dies zu tun ist, eine Window-App zu schreiben, die kein Fenster anzeigt, wenn die Befehlszeilenargumente dies nicht anzeigen.

Sie können die Befehlszeilenargumente immer abrufen und überprüfen, bevor das erste Fenster angezeigt wird.

+0

hinzugefügt, aber was ist mit dem Öffnen des Konsolenfensters, wenn kein Fenster angezeigt wird? – Lucas

+0

öffnen cmd.exe? –

2

Soweit mir bekannt ist, gibt es ein Flag in der exe, dass es als Konsole oder gefensterten App ausgeführt wird. Sie können die Markierung mit Tools, die mit Visual Studio geliefert werden, aktivieren, aber Sie können dies nicht zur Laufzeit tun.

Wenn die EXE als Konsole kompiliert wird, wird immer eine neue Konsole geöffnet, wenn sie nicht von einer gestartet wird. Wenn die EXE eine Anwendung ist, kann sie nicht auf der Konsole ausgegeben werden. Sie können eine separate Konsole erstellen, die sich jedoch nicht wie eine Konsolenanwendung verhält.

Ich die Vergangenheit haben wir 2 separate exe verwendet. Die Konsole ist eine dünne Wrapper über die Formen einer (Sie können eine EXE referenzieren, wie Sie eine DLL verweisen würden, und Sie können das [assembly: InternalsVisibleTo ("cs_friend_assemblies_2")] Attribut verwenden, um der Konsole zu vertrauen, so dass Sie nicht ' t müssen mehr exponieren als Sie brauchen).

5

Ich habe dies getan, indem zwei separate Anwendungen zu schaffen. Erstellen Sie die WPF-App mit diesem Namen: MyApp.exe. Und erstellen Sie die Konsolenanwendung mit diesem Namen: MyApp.com. Wenn Sie den Namen Ihrer App in der Befehlszeile eingeben: MyApp oder MyApp /help (ohne die Erweiterung .exe), hat die Konsolenanwendung mit der Erweiterung .com Vorrang. Sie können Ihre Konsolenanwendung veranlassen, die MyApp.exe entsprechend den Parametern aufzurufen.

Genau so verhält sich Devenv. Wenn Sie in der Befehlszeile devenv eingeben, wird die IDE von Visual Studio gestartet. Wenn Sie Parameter wie /build übergeben, bleibt es in der Befehlszeile.

+0

Ja, das ist die alte Schule, die ich in der Vergangenheit benutzt habe. Ich habe auf einen anderen Ansatz gehofft. Trotzdem danke. – Matthew

+0

Ich konnte nicht finden, wie man die 'com' Erweiterung einstellt. Wenn ich 'Assembly name' in den Projekteinstellungen ändere, wird' MyApp.com' generiert.exe' – itsho

+0

Wie erhalten Sie die MyApp.com? – FlyingMaverick

0

Nr. 1 ist einfach.

Nr. 2 kann nicht gemacht werden, denke ich nicht.

Die docs sagen:

Aufrufe von Methoden wie dem Schreiben und Console.WriteLine keine Auswirkung in Windows-Anwendungen haben.

Die System.Console-Klasse wird in Konsolen- und GUI-Anwendungen unterschiedlich initialisiert. Sie können dies überprüfen, indem Sie die Konsole-Klasse im Debugger in jedem Anwendungstyp betrachten. Nicht sicher, ob es eine Möglichkeit gibt, es neu zu initialisieren.

Demo: Erstelle ein neues Windows-Anwendung Formulare, dann ersetzen Sie die Main-Methode mit diesem:

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

Die Idee ist, dass alle Befehlszeilenparameter an die Konsole und Ausgang gedruckt wird. Wenn Sie es ohne Argumente ausführen, erhalten Sie das Fenster. Aber wenn Sie es mit einem Befehlszeilenargument ausführen, passiert nichts.

Wählen Sie dann die Projekteigenschaften, ändern Sie den Projekttyp in "Konsolenanwendung" und kompilieren Sie neu. Wenn Sie es jetzt mit einem Argument ausführen, erhalten Sie "Konsole!" wie Sie möchten. Und wenn Sie es (von der Befehlszeile) ohne Argumente ausführen, erhalten Sie das Fenster. Die Eingabeaufforderung wird jedoch erst wieder angezeigt, wenn Sie das Programm beenden. Und wenn Sie das Programm von Explorer aus ausführen, wird ein Befehlsfenster geöffnet und Sie erhalten ein Fenster.

2

Ich würde eine Lösung erstellen, die eine Windows Form App ist, da es zwei Funktionen gibt, die Sie aufrufen können, die in der aktuellen Konsole einhaken. So können Sie das Programm wie ein Konsolenprogramm behandeln. oder standardmäßig können Sie die GUI starten.

Die AttachConsole-Funktion erstellt keine neue Konsole. Weitere Informationen zu AttachConsole finden Sie unter PInvoke: AttachConsole

Im Folgenden ein Beispielprogramm zur Verwendung.

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

ich im Grunde, dass die Art und Weise in Erics Antwort dargestellt, zusätzlich ich die Konsole mit FreeConsole lösen und verwende die Tastaturbefehle Befehl zurück, um die Eingabeaufforderung zu erhalten.

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

    } 
+3

Dies sollte die Antwort sein. Die SendKeys sind wichtig. – derFunk

1

Die wichtige Sache zu erinnern, nach AttachConsole() oder AllocConsole() Anrufen zu tun, um es in allen Fällen an der Arbeit ist:

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

ich gefunden habe, die mit oder ohne VS-Hosting-Prozess funktioniert. Wenn die Ausgabe mit System.Console.WriteLine oder System.Console.out.WriteLine vor dem Anruf an AttachConsole oder AllocConsole gesendet wird. Ich habe meine Methode unten enthalten:

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

ich dies auch aufgerufen, wenn ich fertig war, falls ich Eingabeaufforderung benötigen erneut anzuzeigen, wenn ich fertig war Ausgabe zu tun.

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

Hope this helps ...

0

Ich habe einen Weg erarbeitet diese einschließlich der Verwendung von stdin zu tun, aber ich muss Sie warnen, dass es nicht schön ist.

Das Problem mit stdin von einer angeschlossenen Konsole ist, dass die Shell auch daraus lesen wird. Dies führt dazu, dass die Eingabe manchmal zu Ihrer App, aber manchmal zur Shell geht.

Die Lösung ist, die Shell für die Lebensdauer der Apps zu blockieren (obwohl Sie technisch versuchen könnten, es nur zu blockieren, wenn Sie eine Eingabe benötigen). Ich entscheide mich dafür, indem ich Tastenanschläge an die Shell sende, um einen Powershell-Befehl auszuführen, der darauf wartet, dass die App beendet wird.

Übrigens behebt dies auch das Problem, dass die Aufforderung nicht zurückkommt, nachdem die App beendet wurde.

Ich habe kurz versucht, es von der Powershell-Konsole zu arbeiten. Es gelten die gleichen Prinzipien, aber ich habe es nicht bekommen, um meinen Befehl auszuführen. Es kann sein, dass Powershell einige Sicherheitschecks hat, um das Ausführen von Befehlen von anderen Anwendungen zu verhindern. Da ich Powershell nicht viel verwende, habe ich mich nicht damit befasst.

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