wake-up-neo.com

Kann eine ausführbare Datei sowohl eine Konsole als auch eine GUI-Anwendung sein?

Ich möchte ein C # - Programm erstellen, das als CLI- oder GUI-Anwendung ausgeführt werden kann, je nachdem, welche Flags an es übergeben werden. Kann das gemacht werden?

Ich habe diese verwandten Fragen gefunden, aber sie decken meine Situation nicht genau ab:

72
BCS

Antwort von Jdigital verweist auf Raymond Chens Blog , der erklärt, warum Sie keine Anwendung haben können, die sowohl ein Konsolenprogramm als auch ein Nicht-Konsolen-*-Programm ist: Das Betriebssystem muss wissen, bevor das Programm ausgeführt wird welches Subsystem verwendet werden soll. Sobald das Programm gestartet ist, ist es zu spät, um den anderen Modus abzufragen.

Cades Antwort zeigt auf einen Artikel über das Ausführen einer .Net WinForms-Anwendung mit einer Konsole . Es verwendet die Technik des Aufrufs von AttachConsole , nachdem das Programm gestartet wurde. Dies bewirkt, dass das Programm in das Konsolenfenster der Eingabeaufforderung zurückschreiben kann, mit der das Programm gestartet wurde. In den Kommentaren dieses Artikels wird jedoch darauf hingewiesen, was ich für einen schwerwiegenden Fehler halte: Der untergeordnete Prozess kontrolliert die Konsole nicht wirklich. Die Konsole akzeptiert weiterhin Eingaben im Namen des übergeordneten Prozesses, und der übergeordnete Prozess ist sich nicht bewusst, dass er warten sollte, bis das Kind die Ausführung beendet hat, bevor er die Konsole für andere Zwecke verwendet.

Chens Artikel verweist auf einen Artikel von Junfeng Zhang, der einige andere Techniken erläutert .

Die erste ist, was devenv verwendet. Es funktioniert, indem es tatsächlich zwei Programme hat. Eines ist devenv.exe, das ist das Haupt-GUI-Programm, und das andere ist devenv.com, das Aufgaben im Konsolenmodus ausführt. Wenn es jedoch nicht konsolenähnlich verwendet wird, ist dies der Fall leitet seine Aufgaben an devenv.exe weiter und beendet den Vorgang. Die Technik basiert auf der Win32-Regel, dass com -Dateien vor exe -Dateien ausgewählt werden, wenn Sie einen Befehl ohne die Dateierweiterung eingeben.

Es gibt eine einfachere Variante, die der Windows Script Host ausführt. Es enthält zwei vollständig separate Binärdateien, wscript.exe und cscript.exe. Ebenso bietet Java Java.exe für Konsolenprogramme und javaw.exe für Nichtkonsolenprogramme.

Junfengs zweite Technik ist, was ildasm verwendet. Er zitiert den Prozess, den der Autor von ildasm durchlaufen hat, als er in beiden Modi ausgeführt wurde. Letztendlich ist das, was es tut:

  1. Das Programm ist als Konsolenmodus-Binärdatei gekennzeichnet und beginnt daher immer mit einer Konsole. Dadurch kann die Eingabe- und Ausgabeumleitung normal funktionieren.
  2. Wenn das Programm keine Befehlszeilenparameter im Konsolenmodus hat, wird es erneut gestartet.

Es reicht nicht aus, einfach FreeConsole aufzurufen, damit die erste Instanz kein Konsolenprogramm mehr ist. Der Grund dafür ist, dass der Prozess, mit dem das Programm gestartet wurde (cmd.exe), "weiß", dass ein Konsolenmodusprogramm gestartet wurde und darauf wartet, dass das Programm nicht mehr ausgeführt wird. Durch den Aufruf von FreeConsole würde ildasm die Konsole nicht mehr verwenden, der übergeordnete Prozess start würde jedoch nicht über die Konsole aufgerufen.

Die erste Instanz startet sich also neu (mit einem zusätzlichen Befehlszeilenparameter, nehme ich an). Wenn Sie CreateProcess aufrufen, gibt es zwei verschiedene Flags, die Sie ausprobieren können, DETACHED_PROCESS UND CREATE_NEW_CONSOLE . Beide stellen sicher, dass die zweite Instanz nicht an die übergeordnete Konsole angehängt wird. Danach kann die erste Instanz den Befehl Prompt beenden, um die Verarbeitungsbefehle wieder aufzunehmen.

Der Nebeneffekt dieser Technik besteht darin, dass beim Starten des Programms über eine GUI-Schnittstelle immer noch eine Konsole vorhanden ist. Es blinkt kurz auf dem Bildschirm und verschwindet dann.

Der Teil in Junfengs Artikel über die Verwendung von editbin zum Ändern des Konsolenmodusflags des Programms ist, glaube ich, ein roter Hering. Ihr Compiler oder Ihre Entwicklungsumgebung sollte eine Einstellung oder Option zur Verfügung stellen, um zu steuern, welche Art von Binärdatei er erstellt. Es sollte keine Notwendigkeit bestehen, danach etwas zu ändern.

Die letzte Zeile lautet also: Sie können entweder zwei Binärdateien haben oder ein kurzes Flackern eines Konsolenfensters haben. Wenn Sie sich für das kleinere Übel entschieden haben, können Sie sich für Implementierungen entscheiden.

* Ich sage non-console anstelle von GUI, da es sich sonst um eine falsche Dichotomie handelt. Nur weil ein Programm keine Konsole hat, bedeutet das nicht, dass es eine GUI hat. Eine Dienstanwendung ist ein Paradebeispiel. Ein Programm kann auch ein Konsolenfenster und haben.

85
Rob Kennedy

Schauen Sie sich Raymond's Blog zu diesem Thema an:

http://blogs.msdn.com/oldnewthing/archive/2009/01/01/9259142.aspx

Sein erster Satz: "Sie können nicht, aber Sie können versuchen, es vorzutäuschen."

10
jdigital

http://www.csharp411.com/console-output-from-winforms-application/

Überprüfen Sie einfach die Befehlszeilenargumente vor dem WinForms Application.-Zeug.

Ich sollte hinzufügen, dass es in .NET RIDICULUS einfach ist, einfach Konsolen- und GUI-Projekte in derselben Lösung zu erstellen, die alle ihre Assemblys außer main verwenden. In diesem Fall können Sie die Befehlszeilenversion einfach starten, wenn die GUI-Version ohne Parameter gestartet wird. Sie würden eine blinkende Konsole bekommen.

6
Cade Roux

Es gibt eine einfache Möglichkeit, das zu tun, was Sie möchten. Ich verwende es immer, wenn ich Apps schreibe, die sowohl eine CLI als auch eine GUI haben sollten. Sie müssen Ihren "OutputType" auf "ConsoleApplication" setzen, damit dies funktioniert.

class Program {
  [DllImport("kernel32.dll", EntryPoint = "GetConsoleWindow")]
  private static extern IntPtr _GetConsoleWindow();

  /// <summary>
  /// The main entry point for the application.
  /// </summary>
  [STAThread]
  static void Main(string[] args) {
    Application.EnableVisualStyles();
    Application.SetCompatibleTextRenderingDefault(false);

    /*
     * This works as following:
     * First we look for command line parameters and if there are any of them present, we run the CLI version.
     * If there are no parameters, we try to find out if we are run inside a console and if so, we spawn a new copy of ourselves without a console.
     * If there is no console at all, we show the GUI.
     * We make an exception if we find out, that we're running inside visual studio to allow for easier debugging the GUI part.
     * This way we're both a CLI and a GUI.
     */
    if (args != null && args.Length > 0) {

      // execute CLI - at least this is what I call, passing the given args.
      // Change this call to match your program.
      CLI.ParseCommandLineArguments(args);

    } else {
      var consoleHandle = _GetConsoleWindow();

      // run GUI
      if (consoleHandle == IntPtr.Zero || AppDomain.CurrentDomain.FriendlyName.Contains(".vshost"))

        // we either have no console window or we're started from within visual studio
        // This is the form I usually run. Change it to match your code.
        Application.Run(new MainForm());
      else {

        // we found a console attached to us, so restart ourselves without one
        Process.Start(new ProcessStartInfo(Assembly.GetEntryAssembly().Location) {
          CreateNoWindow = true,
          UseShellExecute = false
        });
      }
    }
  }
5
user1566352

Ich glaube, die bevorzugte Technik ist die von Rob als devenv bezeichnete Technik, bei der zwei ausführbare Dateien verwendet werden: ein Launcher ".com" und die ursprüngliche ".exe". Dies ist nicht so schwierig zu verwenden, wenn Sie über den Boilerplate-Code verfügen (siehe Link unten).

Die Technik verwendet Tricks, damit ".com" ein Proxy für stdin/stdout/stderr ist und die .exe-Datei mit dem gleichen Namen startet. Dies gibt das Verhalten, wenn das Programm in einem Befehlszeilenmodus vorgeformt werden kann, wenn es von einer Konsole aufgerufen wird (möglicherweise nur, wenn bestimmte Befehlszeilenargumente erkannt werden) und es dennoch als GUI-Anwendung ohne Konsole starten kann.

Ich habe ein Projekt mit dem Namen dualsubsystem bei Google Code gehostet, das eine alte Codeguru-Lösung dieser Technik aktualisiert und den Quellcode und funktionale Beispiel-Binärdateien bereitstellt.

3
gabeiscoding

Hier ist meiner Meinung nach die einfache .NET C # -Lösung für das Problem. Um das Problem zu beheben: Wenn Sie die Konsolenversion der App über eine Befehlszeile mit einem Switch ausführen, wartet die Konsole weiter (sie kehrt nicht zum Eingabeaufforderungsbefehl zurück und der Prozess läuft weiter), auch wenn Sie eine Environment.Exit(0) am Ende Ihres Codes. Um dies zu beheben, rufen Sie kurz vor dem Aufruf von Environment.Exit(0) Folgendes auf:

SendKeys.SendWait("{ENTER}");

Dann erhält die Konsole die letzte Eingabetaste, die zur Befehlsaufforderung zurückkehren muss, und der Vorgang wird beendet. Hinweis: Rufen Sie nicht SendKeys.Send() auf, da sonst die App abstürzt.

Es ist immer noch erforderlich, AttachConsole() aufzurufen, wie in vielen Beiträgen erwähnt. Mit diesem Befehl kann ich jedoch beim Starten der WinForm-Version der App keine Befehlsfenster flackern.

Hier ist der gesamte Code in einer von mir erstellten Beispiel-App (ohne den WinForms-Code):

using System;
using System.Windows.Forms;
using System.Runtime.InteropServices;

namespace ConsoleWriter
{
    static class Program
    {
        [DllImport("kernel32.dll")]
        private static extern bool AttachConsole(int dwProcessId);
        private const int ATTACH_PARENT_PROCESS = -1;

        [STAThread]
        static void Main(string[] args)
        {
            if(args.Length > 0 && args[0].ToUpperInvariant() == "/NOGUI")
            {
                AttachConsole(ATTACH_PARENT_PROCESS);
                Console.WriteLine(Environment.NewLine + "This line prints on console.");

                Console.WriteLine("Exiting...");
                SendKeys.SendWait("{ENTER}");
                Environment.Exit(0);
            }
            else
            {
                Application.EnableVisualStyles();
                Application.SetCompatibleTextRenderingDefault(false);
                Application.Run(new Form1());
            }
        }
    }
}

Hoffe es hilft jemandem auch, Tage für dieses Problem zu verbringen. Danke für den Hinweis an @dantill.

3
LTDev
/*
** dual.c    Runs as both CONSOLE and GUI app in Windows.
**
** This solution is based on the "Momentary Flicker" solution that Robert Kennedy
** discusses in the highest-rated answer (as of Jan 2013), i.e. the one drawback
** is that the console window will briefly flash up when run as a GUI.  If you
** want to avoid this, you can create a shortcut to the executable and tell the
** short cut to run minimized.  That will minimize the console window (which then
** immediately quits), but not the GUI window.  If you want the GUI window to
** also run minimized, you have to also put -minimized on the command line.
**
** Tested under MinGW:  gcc -o dual.exe dual.c -lgdi32
**
*/
#include <windows.h>
#include <stdio.h>

static int my_win_main(HINSTANCE hInstance,int argc,char *argv[],int iCmdShow);
static LRESULT CALLBACK WndProc(HWND hwnd,UINT iMsg,WPARAM wParam,LPARAM lParam);
static int win_started_from_console(void);
static BOOL CALLBACK find_win_by_procid(HWND hwnd,LPARAM lp);

int main(int argc,char *argv[])

    {
    HINSTANCE hinst;
    int i,gui,relaunch,minimized,started_from_console;

    /*
    ** If not run from command-line, or if run with "-gui" option, then GUI mode
    ** Otherwise, CONSOLE app.
    */
    started_from_console = win_started_from_console();
    gui = !started_from_console;
    relaunch=0;
    minimized=0;
    /*
    ** Check command options for forced GUI and/or re-launch
    */
    for (i=1;i<argc;i++)
        {
        if (!strcmp(argv[i],"-minimized"))
            minimized=1;
        if (!strcmp(argv[i],"-gui"))
            gui=1;
        if (!strcmp(argv[i],"-gui-"))
            gui=0;
        if (!strcmp(argv[i],"-relaunch"))
            relaunch=1;
        }
    if (!gui && !relaunch)
        {
        /* RUN AS CONSOLE APP */
        printf("Console app only.\n");
        printf("Usage:  dual [-gui[-]] [-minimized].\n\n");
        if (!started_from_console)
            {
            char buf[16];
            printf("Press <Enter> to exit.\n");
            fgets(buf,15,stdin);
            }
        return(0);
        }

    /* GUI mode */
    /*
    ** If started from CONSOLE, but want to run in GUI mode, need to re-launch
    ** application to completely separate it from the console that started it.
    **
    ** Technically, we don't have to re-launch if we are not started from
    ** a console to begin with, but by re-launching we can avoid the flicker of
    ** the console window when we start if we start from a shortcut which tells
    ** us to run minimized.
    **
    ** If the user puts "-minimized" on the command-line, then there's
    ** no point to re-launching when double-clicked.
    */
    if (!relaunch && (started_from_console || !minimized))
        {
        char exename[256];
        char buf[512];
        STARTUPINFO si;
        PROCESS_INFORMATION pi;

        GetStartupInfo(&si);
        GetModuleFileNameA(NULL,exename,255);
        sprintf(buf,"\"%s\" -relaunch",exename);
        for (i=1;i<argc;i++)
            {
            if (strlen(argv[i])+3+strlen(buf) > 511)
                break;
            sprintf(&buf[strlen(buf)]," \"%s\"",argv[i]);
            }
        memset(&pi,0,sizeof(PROCESS_INFORMATION));
        memset(&si,0,sizeof(STARTUPINFO));
        si.cb = sizeof(STARTUPINFO);
        si.dwX = 0; /* Ignored unless si.dwFlags |= STARTF_USEPOSITION */
        si.dwY = 0;
        si.dwXSize = 0; /* Ignored unless si.dwFlags |= STARTF_USESIZE */
        si.dwYSize = 0;
        si.dwFlags = STARTF_USESHOWWINDOW;
        si.wShowWindow = SW_SHOWNORMAL;
        /*
        ** Note that launching ourselves from a console will NOT create new console.
        */
        CreateProcess(exename,buf,0,0,1,DETACHED_PROCESS,0,NULL,&si,&pi);
        return(10); /* Re-launched return code */
        }
    /*
    ** GUI code starts here
    */
    hinst=GetModuleHandle(NULL);
    /* Free the console that we started with */
    FreeConsole();
    /* GUI call with functionality of WinMain */
    return(my_win_main(hinst,argc,argv,minimized ? SW_MINIMIZE : SW_SHOWNORMAL));
    }


static int my_win_main(HINSTANCE hInstance,int argc,char *argv[],int iCmdShow)

    {
    HWND        hwnd;
    MSG         msg;
    WNDCLASSEX  wndclass;
    static char *wintitle="GUI Window";

    wndclass.cbSize        = sizeof (wndclass) ;
    wndclass.style         = CS_HREDRAW | CS_VREDRAW;
    wndclass.lpfnWndProc   = WndProc;
    wndclass.cbClsExtra    = 0 ;
    wndclass.cbWndExtra    = 0 ;
    wndclass.hInstance     = hInstance;
    wndclass.hIcon         = NULL;
    wndclass.hCursor       = NULL;
    wndclass.hbrBackground = NULL;
    wndclass.lpszMenuName  = NULL;
    wndclass.lpszClassName = wintitle;
    wndclass.hIconSm       = NULL;
    RegisterClassEx (&wndclass) ;

    hwnd = CreateWindowEx(WS_EX_OVERLAPPEDWINDOW,wintitle,0,
                          WS_VISIBLE|WS_OVERLAPPEDWINDOW,
                          100,100,400,200,NULL,NULL,hInstance,NULL);
    SetWindowText(hwnd,wintitle);
    ShowWindow(hwnd,iCmdShow);
    while (GetMessage(&msg,NULL,0,0))
        {
        TranslateMessage(&msg);
        DispatchMessage(&msg);
        }
    return(msg.wParam);
    }


static LRESULT CALLBACK WndProc (HWND hwnd,UINT iMsg,WPARAM wParam,LPARAM lParam)

    {
    if (iMsg==WM_DESTROY)
        {
        PostQuitMessage(0);
        return(0);
        }
    return(DefWindowProc(hwnd,iMsg,wParam,lParam));
    }


static int fwbp_pid;
static int fwbp_count;
static int win_started_from_console(void)

    {
    fwbp_pid=GetCurrentProcessId();
    if (fwbp_pid==0)
        return(0);
    fwbp_count=0;
    EnumWindows((WNDENUMPROC)find_win_by_procid,0L);
    return(fwbp_count==0);
    }


static BOOL CALLBACK find_win_by_procid(HWND hwnd,LPARAM lp)

    {
    int pid;

    GetWindowThreadProcessId(hwnd,(LPDWORD)&pid);
    if (pid==fwbp_pid)
        fwbp_count++;
    return(TRUE);
    }
2
willus

Ich habe einen alternativen Ansatz geschrieben, der den Konsolenflash vermeidet. Siehe So erstellen Sie ein Windows-Programm, das als GUI- und Konsolenanwendung fungiert.

1
dantill

Führen Sie AllocConsole () in einem statischen Konstruktor aus

0
Shaggy