Я хочу создать программу С#, которая может быть запущена в качестве приложения CLI или GUI в зависимости от того, какие флаги переданы в него. Это можно сделать?
Я нашел эти связанные вопросы, но они не совсем соответствуют моей ситуации:
Я хочу создать программу С#, которая может быть запущена в качестве приложения CLI или GUI в зависимости от того, какие флаги переданы в него. Это можно сделать?
Я нашел эти связанные вопросы, но они не совсем соответствуют моей ситуации:
Ответ Jdigital указывает на блог Раймонда Чена, в котором объясняется, почему у вас не может быть приложения, в котором есть консольная и не консольная *
программа: ОС должна знать, прежде чем запускать программу, какую подсистему использовать. Как только программа запустилась, уже поздно возвращаться и запрашивать другой режим.
Ответ Cade указывает на статью о запуске приложения .Net WinForms с консолью. Он использует технику вызова AttachConsole
после запуска программы. Это позволяет программе писать обратно в консольное окно командной строки, которая запустила программу. Но комментарии в этой статье указывают на то, что я считаю фатальным недостатком: дочерний процесс на самом деле не контролирует консоль. Консоль продолжает принимать ввод от имени родительского процесса, и родительский процесс не знает, что ему следует дождаться завершения дочернего процесса, прежде чем использовать консоль для других целей.
Статья Чена указывает на статью Junfeng Zhang, которая объясняет несколько других техник.
Первое, что использует devenv. Это работает на самом деле две программы. Одним из них является devenv.exe, который является основной программой с графическим интерфейсом, а другим - devenv.com, который обрабатывает задачи в режиме консоли, но если он используется не по-консольному, он перенаправляет свои задачи в devenv.exe и выходы. Техника опирается на правило Win32, что com файлы выбираются впереди exe файлов при вводе команды без расширения файла.
Существует более простая вариация, чем у Windows Script Host. Он предоставляет два совершенно разных двоичных файла, wscript.exe и cscript.exe. Аналогично, Java предоставляет java.exe для консольных программ и javaw.exe для неконсольных программ.
Вторая техника Junfeng - это то, что использует ildasm. Он цитирует процесс, через который прошел автор ildasm при его запуске в обоих режимах. В конечном счете, вот что это делает:
Недостаточно просто вызвать FreeConsole
чтобы первый экземпляр перестал быть консольной программой. Это потому, что процесс, который запустил программу, cmd.exe, "знает", что он запустил программу в консольном режиме и ожидает остановки программы. Вызов FreeConsole
бы к остановке ildasm при использовании консоли, но не заставил бы родительский процесс начать работу с консолью.
Таким образом, первый экземпляр перезапускается сам (с дополнительным параметром командной строки, я полагаю). Когда вы вызываете CreateProcess
, необходимо попробовать два разных флага: DETACHED_PROCESS
и CREATE_NEW_CONSOLE
, каждый из которых гарантирует, что второй экземпляр не будет подключен к родительской консоли. После этого первый экземпляр может завершиться и позволить командной строке возобновить обработку команд.
Побочным эффектом этого метода является то, что при запуске программы из интерфейса графического интерфейса все равно будет консоль. Он будет мигать на экране на мгновение, а затем исчезнет.
Думаю, часть статьи в Junfeng об использовании editbin для изменения флага режима консоли программы - это красная сельдь. Ваш компилятор или среда разработки должны предоставить параметр или опцию для управления типом двоичного файла, который он создает. Там не должно быть необходимости что-либо изменить потом.
Суть в том, что у вас может быть два двоичных файла или кратковременное мерцание окна консоли. Как только вы решите, какое меньшее зло, у вас есть выбор реализации.
*
Я говорю не консольный вместо GUI, потому что в противном случае это ложная дихотомия. Тот факт, что программа не имеет консоли, не означает, что она имеет графический интерфейс. Приложение службы является ярким примером. Также программа может иметь консоль и окна.
Посмотрите блог Raymond на эту тему:
https://devblogs.microsoft.com/oldnewthing/20090101-00/?p=19643
Его первое предложение: "Вы не можете, но вы можете попытаться подделать это".
http://www.csharp411.com/console-output-from-winforms-application/
Просто проверьте аргументы командной строки перед файлами WinForms Application.
.
Я должен добавить, что в .NET очень легко просто создать консольные и графические проекты в том же решении, которые используют все свои сборки, кроме основного. И в этом случае вы можете сделать версию командной строки просто запустить версию GUI, если она запущена без параметров. Вы получите мигающую консоль.
Существует простой способ сделать то, что вы хотите. Я всегда использую его при написании приложений, которые должны иметь как CLI, так и графический интерфейс. Вы должны установить свой "OutputType" в "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
});
}
}
}
Я думаю, что предпочтительным методом является то, что Роб назвал метод devenv использованием двух исполняемых файлов: пусковую установку ".com" и оригинальный ".exe". Это не так сложно использовать, если у вас есть код шаблона для работы (см. Ссылку ниже).
В этой технике используются трюки для того, чтобы ".com" был прокси для stdin/stdout/stderr и запускал файл с одинаковым именем .exe. Это дает поведение, позволяющее программе преформировать в режиме командной строки при вызове формы из консоли (возможно, только при обнаружении определенных аргументов командной строки), при этом все еще можно запускать приложение GUI без консоли.
У меня был проект под названием dualsubsystem в Google Code, который обновляет старое решение для этого метода и обеспечивает источник кода и исполняемых файлов.
Вот что я считаю простым решением .NET С#. Просто для того, чтобы повторить проблему, когда вы запускаете консольную "версию" приложения из командной строки с помощью переключателя, консоль продолжает ждать (она не возвращается в командную строку и процесс продолжает работать), даже если у вас есть Environment.Exit(0)
в конце вашего кода. Чтобы исправить это, перед вызовом Environment.Exit(0)
вызовите это:
SendKeys.SendWait("{ENTER}");
Затем консоль получает окончательный ключ Enter, который нужно вернуть в командную строку, и процесс завершается. Примечание. Не вызывайте SendKeys.Send()
, или приложение выйдет из строя.
По-прежнему необходимо вызвать AttachConsole()
, как упоминалось во многих сообщениях, но при этом я не вижу мерцания командного окна при запуске версии приложения WinForm.
Здесь весь код в образце приложения, которое я создал (без кода 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());
}
}
}
}
Надеюсь, что это поможет кому-то и потратить дни на эту проблему. Спасибо за подсказку. @dantill.
/*
** 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);
}
Я написал альтернативный подход, который позволяет избежать вспышки консоли. См. Как создать программу Windows, которая работает как в графическом интерфейсе, так и в консольном приложении.
Запуск AllocConsole() в статическом конструкторе работает для меня