Chcę utworzyć program C # , który można uruchomić jako aplikację CLI lub GUI w zależności od tego, jakie flagi są do niego przekazywane. Czy można to zrobić?
Znalazłem te powiązane pytania, ale nie obejmują one dokładnie mojej sytuacji:
Chcę utworzyć program C # , który można uruchomić jako aplikację CLI lub GUI w zależności od tego, jakie flagi są do niego przekazywane. Czy można to zrobić?
Znalazłem te powiązane pytania, ale nie obejmują one dokładnie mojej sytuacji:
Odpowiedzi:
Odpowiedź Jdigital wskazuje na blog Raymonda Chena , który wyjaśnia, dlaczego nie można mieć aplikacji, która jest jednocześnie programem konsolowym i innym niż konsolowy *
: system operacyjny musi wiedzieć, zanim program zacznie działać, którego podsystemu ma używać. Po uruchomieniu programu jest już za późno, aby wrócić i zażądać innego trybu.
Odpowiedź Cade'a wskazuje na artykuł o uruchamianiu aplikacji .Net WinForms z konsolą . Używa techniki wywoływania AttachConsole
po uruchomieniu programu. W efekcie program może zapisywać z powrotem w oknie konsoli wiersza polecenia, który uruchomił program. Ale komentarze w tym artykule wskazują na to, co uważam za fatalną wadę: proces potomny tak naprawdę nie kontroluje konsoli. Konsola nadal przyjmuje dane wejściowe w imieniu procesu nadrzędnego, a proces nadrzędny nie jest świadomy tego, że powinien czekać na zakończenie działania dziecka, zanim użyje konsoli do innych celów.
Artykuł Chena wskazuje na artykuł Junfeng Zhang, który wyjaśnia kilka innych technik .
Pierwszym jest to, czego używa devenv . Działa poprzez faktyczne posiadanie dwóch programów. Jeden to devenv.exe , który jest głównym programem graficznym, a drugi to devenv.com , który obsługuje zadania w trybie konsoli, ale jeśli jest używany w sposób inny niż konsola, przekazuje swoje zadania do devenv.exe i wyjścia. Technika ta opiera się na regule Win32, zgodnie z którą pliki com są wybierane przed plikami exe po wpisaniu polecenia bez rozszerzenia pliku.
Istnieje prostsza odmiana tego, którą robi host skryptów systemu Windows. Udostępnia dwa całkowicie oddzielne pliki binarne, wscript.exe i cscript.exe . Podobnie Java udostępnia java.exe dla programów konsolowych i javaw.exe dla programów innych niż konsole.
Drugą techniką Junfenga jest ildasm . Cytuje proces, przez który przeszedł autor ildasm , uruchamiając go w obu trybach. Ostatecznie, oto, co robi:
Nie wystarczy po prostu wywołać, FreeConsole
aby pierwsza instancja przestała być programem konsolowym. Dzieje się tak, ponieważ proces, który uruchomił program, cmd.exe , „wie”, że uruchomił program w trybie konsoli i czeka, aż program przestanie działać. Wywołanie FreeConsole
uczyniłoby ildasm zaprzestać korzystania z konsoli, ale nie może sprawić, że proces macierzysty uruchomić za pomocą konsoli.
Więc pierwsza instancja uruchamia się ponownie (jak przypuszczam z dodatkowym parametrem wiersza polecenia). Podczas wywołania CreateProcess
są dwie różne flagi do wypróbowania, DETACHED_PROCESS
aCREATE_NEW_CONSOLE
każda z nich zapewni, że druga instancja nie zostanie podłączona do konsoli nadrzędnej. Następnie pierwsza instancja może zakończyć się i pozwolić wierszowi polecenia na wznowienie przetwarzania poleceń.
Efektem ubocznym tej techniki jest to, że po uruchomieniu programu z interfejsu GUI konsola nadal będzie dostępna. Zacznie migać na ekranie przez chwilę, a następnie zniknie.
Część artykułu Junfenga o używaniu editbin do zmiany flagi trybu konsoli programu jest, jak sądzę, czerwonym śledziem. Twój kompilator lub środowisko programistyczne powinno zapewniać ustawienie lub opcję kontrolowania, jaki rodzaj pliku binarnego tworzy. Nie powinno być potrzeby późniejszych modyfikacji.
Najważniejsze jest więc to, że możesz mieć dwa pliki binarne lub możesz mieć chwilowe migotanie okna konsoli . Kiedy już zdecydujesz, co jest mniejszym złem, masz wybór implementacji.
*
Mówię, że nie jest to konsola zamiast GUI, ponieważ w przeciwnym razie jest to fałszywa dychotomia. To, że program nie ma konsoli, nie oznacza, że ma GUI. Najlepszym przykładem jest aplikacja usługowa. Ponadto program może mieć konsolę i okna.
WinMain
funkcji z odpowiednimi parametrami (tak skompiluj z /SUBSYSTEM:WINDOWS
), a następnie zmiana trybu ex post facto, więc moduł ładujący uruchamia hosta konsoli. Aby uzyskać więcej informacji zwrotnych, wypróbowałem to CREATE_NO_WINDOW
w programie CreateProcess i GetConsoleWindow() == NULL
jako mój sprawdzanie, czy ponownie uruchomiono lub nie. Nie naprawia to migotania konsoli, ale oznacza brak specjalnego argumentu cmd.
Zajrzyj na blog Raymonda na ten temat:
https://devblogs.microsoft.com/oldnewthing/20090101-00/?p=19643
Jego pierwsze zdanie: „Nie możesz, ale możesz spróbować to udawać”.
http://www.csharp411.com/console-output-from-winforms-application/
Po prostu sprawdź argumenty wiersza poleceń przed elementami WinForms Application.
.
Powinienem dodać, że w .NET jest NIEZBĘDNIE łatwo po prostu stworzyć konsolę i projekty GUI w tym samym rozwiązaniu, które współdzielą wszystkie swoje zestawy oprócz main. W takim przypadku możesz sprawić, aby wersja wiersza poleceń po prostu uruchomiła wersję GUI, jeśli jest uruchomiona bez parametrów. Otrzymasz migającą konsolę.
Jest łatwy sposób na robienie tego, co chcesz. Zawsze go używam podczas pisania aplikacji, które powinny mieć zarówno CLI, jak i GUI. Aby to zadziałało, musisz ustawić „OutputType” na „ConsoleApplication”.
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
});
}
}
}
Myślę, że preferowaną techniką jest to, co Rob nazwał techniką devenv , polegającą na użyciu dwóch plików wykonywalnych: programu uruchamiającego „.com” i oryginalnego „.exe”. Nie jest to trudne w użyciu, jeśli masz standardowy kod do pracy (patrz poniższy link).
Ta technika wykorzystuje sztuczki, aby ten „.com” był proxy dla stdin / stdout / stderr i uruchomił plik .exe o tej samej nazwie. Daje to zachowanie pozwalające programowi na wykonywanie wstępnych w trybie wiersza poleceń po wywołaniu z konsoli (potencjalnie tylko wtedy, gdy wykryte zostaną określone argumenty wiersza poleceń), podczas gdy nadal można uruchomić jako aplikację GUI wolną od konsoli.
Hostowałem projekt o nazwie dualsubsystem w Google Code, który aktualizuje stare rozwiązanie codeguru tej techniki i dostarcza kod źródłowy i działające przykładowe pliki binarne.
Oto, co uważam za proste rozwiązanie problemu w języku C # .NET. Aby powtórzyć problem, kiedy uruchamiasz konsolową „wersję” aplikacji z wiersza poleceń z przełącznikiem, konsola czeka (nie powraca do wiersza poleceń, a proces działa dalej), nawet jeśli masz Environment.Exit(0)
na końcu twojego kodu. Aby to naprawić, tuż przed zadzwonieniem Environment.Exit(0)
, zadzwoń:
SendKeys.SendWait("{ENTER}");
Następnie konsola otrzymuje ostatni klawisz Enter, którego potrzebuje, aby powrócić do wiersza poleceń, i proces się kończy. Uwaga: nie dzwoń SendKeys.Send()
, bo aplikacja ulegnie awarii.
Nadal trzeba dzwonić, AttachConsole()
jak wspomniano w wielu postach, ale dzięki temu okno poleceń nie migocze podczas uruchamiania wersji aplikacji WinForm.
Oto cały kod w utworzonej przeze mnie przykładowej aplikacji (bez kodu WinForms):
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());
}
}
}
}
Mam nadzieję, że pomoże to komuś również spędzać dni nad tym problemem. Dzięki za podpowiedź przejdź do @dantill.
Console.WriteLine
, nie przesuwa kursora tekstowego konsoli (nadrzędnej). Dlatego po zamknięciu aplikacji kursor znajduje się w niewłaściwym miejscu i trzeba kilka razy nacisnąć klawisz Enter, aby powrócić do monitu o „wyczyszczenie”.
/*
** 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);
}
Napisałem alternatywne podejście, które pozwala uniknąć flashowania konsoli. Zobacz, jak utworzyć program Windows, który działa zarówno jako graficzny interfejs użytkownika, jak i aplikacja konsoli .