Ich möchte ein C # -Programm erstellen, das je nach den übergebenen Flags als CLI- oder GUI-Anwendung ausgeführt werden kann. Kann das gemacht werden?
Ich habe diese verwandten Fragen gefunden, aber sie decken meine Situation nicht genau ab:
Ich möchte ein C # -Programm erstellen, das je nach den übergebenen Flags als CLI- oder GUI-Anwendung ausgeführt werden kann. Kann das gemacht werden?
Ich habe diese verwandten Fragen gefunden, aber sie decken meine Situation nicht genau ab:
Antworten:
Die Antwort von Jdigital verweist auf Raymond Chens Blog , in dem erklärt wird, warum Sie keine Anwendung haben können, die sowohl ein Konsolenprogramm als auch ein Nicht-Konsolenprogramm ist *
: Das Betriebssystem muss wissen, bevor das Programm ausgeführt wird, welches Subsystem verwendet werden soll. Sobald das Programm gestartet wurde, ist es zu spät, um den anderen Modus anzufordern.
Die Antwort von Cade verweist auf einen Artikel über das Ausführen einer .NET WinForms-Anwendung mit einer Konsole . Es verwendet die Technik des Aufrufs, AttachConsole
nachdem das Programm gestartet wurde. Dies ermöglicht es dem Programm, in das Konsolenfenster der Eingabeaufforderung, die das Programm gestartet hat, zurückzuschreiben. Die Kommentare in diesem Artikel zeigen jedoch, was ich als schwerwiegenden Fehler betrachte: Der untergeordnete Prozess steuert die Konsole nicht wirklich. Die Konsole akzeptiert weiterhin Eingaben im Namen des übergeordneten Prozesses, und dem übergeordneten Prozess ist nicht bekannt, dass er warten sollte, bis das untergeordnete Element beendet ist, bevor die Konsole für andere Zwecke verwendet wird.
Chens Artikel verweist auf einen Artikel von Junfeng Zhang, der einige andere Techniken erklärt .
Das erste ist, was devenv verwendet. Es funktioniert, indem tatsächlich zwei Programme vorhanden sind. Eines ist devenv.exe , das Haupt-GUI-Programm, und das andere ist devenv.com , das Aufgaben im Konsolenmodus behandelt. Wenn es jedoch nicht konsolenähnlich verwendet wird, leitet es seine Aufgaben an devenv.exe und weiter Ausgänge. Die Technik basiert auf der Win32-Regel, dass COM- Dateien vor Exe- Dateien ausgewählt werden, wenn Sie einen Befehl ohne Dateierweiterung eingeben.
Es gibt eine einfachere Variante, die der Windows Script Host ausführt. Es bietet zwei vollständig separate Binärdateien, wscript.exe und cscript.exe . Ebenso bietet Java java.exe für Konsolenprogramme und javaw.exe für Nicht-Konsolenprogramme.
Junfengs zweite Technik ist die, die ildasm verwendet. Er zitiert den Prozess, den der Autor von ildasm durchlaufen hat, als er es in beiden Modi ausgeführt hat. Letztendlich ist hier, was es tut:
Es reicht nicht aus, einfach aufzurufen FreeConsole
, damit die erste Instanz kein Konsolenprogramm mehr ist. Dies liegt daran, dass der Prozess, der das Programm gestartet hat, cmd.exe , "weiß", dass er ein Programm im Konsolenmodus gestartet hat und darauf wartet, dass das Programm nicht mehr ausgeführt wird. Durch das Aufrufen FreeConsole
würde ildasm die Verwendung der Konsole beenden, aber der übergeordnete Prozess würde die Verwendung der Konsole nicht starten .
Die erste Instanz startet sich also neu (mit einem zusätzlichen Befehlszeilenparameter, nehme ich an). Wenn Sie anrufen CreateProcess
, gibt es zwei verschiedene Fahnen , um zu versuchen, DETACHED_PROCESS
undCREATE_NEW_CONSOLE
, von denen dafür sorgen, dass die zweite Instanz nicht an die übergeordnete Konsole angebracht werden. Danach kann die erste Instanz beendet werden und die Eingabeaufforderung kann die Verarbeitungsbefehle fortsetzen.
Der Nebeneffekt dieser Technik ist, dass beim Starten des Programms über eine GUI-Oberfläche 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 Konsolenmodus-Flags des Programms ist meiner Meinung nach ein roter Hering. Ihr Compiler oder Ihre Entwicklungsumgebung sollte eine Einstellung oder Option bereitstellen, um zu steuern, welche Art von Binärdatei erstellt wird. Es sollte nicht nötig sein, danach etwas zu ändern.
Die Quintessenz ist also, dass Sie entweder zwei Binärdateien oder ein kurzes Flackern eines Konsolenfensters haben können . Sobald Sie sich für das kleinere Übel entschieden haben, haben Sie die Wahl zwischen Implementierungen.
*
Ich sage Nicht-Konsole statt GUI, weil es sonst eine falsche Zweiteilung ist. Nur weil ein Programm keine Konsole hat, heißt das nicht, dass es eine GUI hat. Eine Dienstanwendung ist ein Paradebeispiel. Ein Programm kann auch eine Konsole und Fenster haben.
WinMain
Funktion mit geeigneten Parametern zu verknüpfen (also kompilieren mit /SUBSYSTEM:WINDOWS
) und dann den Modus nachträglich zu ändern Der Loader startet einen Konsolenhost. Für mehr Feedback habe ich dies mit CREATE_NO_WINDOW
in CreateProcess versucht und GetConsoleWindow() == NULL
als meine Überprüfung, ob ein Relaunch durchgeführt wurde oder nicht. Dies behebt das Flackern der Konsole nicht, bedeutet jedoch, dass kein spezielles cmd-Argument vorhanden ist.
Lesen Sie den Blog von Raymond zu diesem Thema:
https://devblogs.microsoft.com/oldnewthing/20090101-00/?p=19643
Sein erster Satz: "Sie können nicht, aber Sie können versuchen, es zu fälschen."
http://www.csharp411.com/console-output-from-winforms-application/
Überprüfen Sie einfach die Befehlszeilenargumente vor dem WinForms- Application.
Material.
Ich sollte hinzufügen, dass es in .NET lächerlich einfach ist, einfach eine Konsole und GUI-Projekte in derselben Lösung zu erstellen, die alle ihre Assemblys außer main gemeinsam nutzen. In diesem Fall können Sie festlegen, dass die Befehlszeilenversion einfach die GUI-Version startet, wenn sie ohne Parameter gestartet wird. Sie würden eine blinkende Konsole bekommen.
Es gibt eine einfache Möglichkeit, das zu tun, was Sie wollen. Ich benutze 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
});
}
}
}
Ich denke, 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 den Boilerplate-Code haben, mit dem Sie arbeiten können (siehe Link unten).
Die Technik verwendet Tricks, um ".com" als Proxy für stdin / stdout / stderr zu verwenden und die gleichnamige EXE-Datei zu starten. Dies gibt das Verhalten, dass das Programm beim Aufrufen über eine Konsole in einem Befehlszeilenmodus ausgeführt werden kann (möglicherweise nur, wenn bestimmte Befehlszeilenargumente erkannt werden), während es weiterhin als konsolenfreie GUI-Anwendung gestartet werden kann.
Ich habe ein Projekt namens dualsubsystem auf Google Code gehostet , das eine alte Codeguru-Lösung dieser Technik aktualisiert und den Quellcode und die funktionierenden Beispiel-Binärdateien bereitstellt.
Hier ist meiner Meinung nach die einfache .NET C # -Lösung für das Problem. Um das Problem noch einmal zu wiederholen: Wenn Sie die Konsolen- "Version" der App über eine Befehlszeile mit einem Schalter ausführen, wartet die Konsole weiter (sie kehrt nicht zur Eingabeaufforderung zurück und der Prozess läuft weiter), selbst wenn Sie eine haben Environment.Exit(0)
am Ende Ihres Codes. Um dies zu beheben Environment.Exit(0)
, rufen Sie kurz vor dem Anruf Folgendes auf:
SendKeys.SendWait("{ENTER}");
Dann erhält die Konsole die letzte Eingabetaste, die sie benötigt, um zur Eingabeaufforderung zurückzukehren, und der Vorgang wird beendet. Hinweis: Rufen Sie nicht an SendKeys.Send()
, da die App sonst abstürzt.
Es ist immer noch notwendig, AttachConsole()
wie in vielen Beiträgen erwähnt anzurufen , aber damit bekomme ich kein Startfensterflimmern, wenn ich die WinForm-Version der App starte.
Hier ist der gesamte Code in einer Beispiel-App, die ich erstellt habe (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 mit diesem Problem zu verbringen. Danke für den Hinweis, gehe zu @dantill.
Console.WriteLine
Textcursor der (übergeordneten) Konsole nicht vorschiebt. Wenn Sie die App beenden, befindet sich die Cursorposition an der falschen Stelle und Sie müssen einige Male die Eingabetaste drücken, um zu einer "sauberen" Eingabeaufforderung zurückzukehren.
/*
** 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);
}
Ich habe einen alternativen Ansatz geschrieben, der den Konsolenblitz vermeidet. Siehe So erstellen Sie ein Windows-Programm, das sowohl als GUI- als auch als Konsolenanwendung funktioniert .