Я хочу створити програму на C #, яку можна запускати як програму CLI або графічний інтерфейс, залежно від того, які прапорці в неї передаються. Чи можна це зробити?
Я знайшов ці відповідні запитання, але вони точно не охоплюють мою ситуацію:
Я хочу створити програму на C #, яку можна запускати як програму CLI або графічний інтерфейс, залежно від того, які прапорці в неї передаються. Чи можна це зробити?
Я знайшов ці відповідні запитання, але вони точно не охоплюють мою ситуацію:
Відповіді:
Відповідь Jdigital вказує на блог Реймонда Чена , який пояснює, чому у вас не може бути програми, яка є одночасно консольною та неконсольною *
програмою: ОС повинна знати, перш ніж програма почне працювати, яку підсистему використовувати. Після запуску програми пізно повертатися назад і запитувати інший режим.
Відповідь Кейда вказує на статтю про запуск програми .Net WinForms із консоллю . Він використовує техніку виклику AttachConsole
після запуску програми. Це дає змогу програмі писати назад у вікно консолі командного рядка, який запускав програму. Але коментарі до цієї статті вказують на те, що я вважаю фатальною вадою: дочірній процес насправді не керує консоллю.Консоль продовжує приймати вхідні дані від імені батьківського процесу, і батьківський процес не знає, що йому слід чекати, поки дитина закінчить роботу, перш ніж використовувати консоль для інших речей.
У статті Чена вказується на статтю Цзюнфенга Чжана, яка пояснює пару інших прийомів .
Перший - це те, що використовує devenv . Це працює, насправді маючи дві програми. Одним з них є devenv.exe , який є основною програмою графічного інтерфейсу, а іншим - devenv.com , який обробляє завдання в консольному режимі, але якщо він використовується не в консолі, він перенаправляє свої завдання на devenv.exe та виходи. Методика спирається на правило Win32, згідно з яким файли com вибираються раніше, ніж файли exe, коли ви вводите команду без розширення файлу.
Існує простіший варіант цього, що робить хост сценаріїв Windows. Він надає два повністю окремі двійкові файли, wscript.exe та cscript.exe . Подібним чином Java надає java.exe для консольних програм та javaw.exe для програм.
Другий прийом Юнфенга - це те, що використовує ільдазм . Він цитує процес, який пройшов автор ildasm , працюючи в обох режимах. Зрештою, ось що це робить:
Недостатньо просто зателефонувати, FreeConsole
щоб перший екземпляр перестав бути консольною програмою. Це тому, що процес, який запустив програму, cmd.exe , "знає", що він запустив програму в консольному режимі і чекає, поки програма перестане працювати. Виклик FreeConsole
призведе до того, що ildasm припинить використання консолі, але батьківський процес не почне використовувати консоль.
Отже, перший екземпляр перезапускається сам (з додатковим параметром командного рядка, я гадаю). Коли ви телефонуєте CreateProcess
, є два різні прапори, які потрібно спробувати, DETACHED_PROCESS
іCREATE_NEW_CONSOLE
кожен з них забезпечить, що другий екземпляр не буде приєднаний до батьківської консолі. Після цього перший екземпляр може завершити роботу і дозволити командному рядку відновити обробку команд.
Побічним ефектом цієї техніки є те, що при запуску програми з інтерфейсу графічного інтерфейсу все одно буде консоль. На мить він блиматиме на екрані, а потім зникне.
Частина статті Юнфенга про використання editbin для зміни прапора в консольному режимі програми - це, на мою думку, червоний оселедець. Ваш компілятор або середовище розробки повинні забезпечити параметр або опцію для контролю того, який тип двійкового файлу він створює. Після цього не повинно бути необхідності щось змінювати.
Суть в тому, що у вас може бути два двійкові файли, або ви можете миттєво мерехтіти у вікні консолі . Коли ви вирішите, що є меншим злом, у вас є вибір варіантів реалізації.
*
Я кажу неконсольний замість графічного інтерфейсу, оскільки в іншому випадку це помилкова дихотомія. Те, що програма не має консолі, не означає, що вона має графічний інтерфейс. Службовий додаток є яскравим прикладом. Також програма може мати консоль і вікна.
WinMain
функцію з відповідними параметрами (так скомпілювати /SUBSYSTEM:WINDOWS
), а потім змінити режим постфактум так завантажувач запускає консольний хост. Щоб отримати додаткові відгуки, я спробував це CREATE_NO_WINDOW
в CreateProcess і GetConsoleWindow() == NULL
як свою перевірку, перезапустив чи ні. Це не виправляє мерехтіння консолі, але це означає відсутність спеціального аргументу cmd.
Перегляньте щоденник Реймонда на цю тему:
https://devblogs.microsoft.com/oldnewthing/20090101-00/?p=19643
Перше його речення: "Не можна, але можна спробувати підробити".
http://www.csharp411.com/console-output-from-winforms-application/
Просто перевірте аргументи командного рядка перед Application.
матеріалами WinForms .
Слід додати, що в .NET СПИСОЧНО просто зробити консоль та проекти графічного інтерфейсу в одному і тому ж рішенні, які мають спільний доступ до всіх своїх збірок, крім основного. І в цьому випадку ви можете змусити версію командного рядка просто запустити версію графічного інтерфейсу, якщо вона запускається без параметрів. Ви отримаєте миготливу консоль.
Існує простий спосіб зробити те, що ти хочеш. Я завжди використовую його, коли пишу програми, які повинні мати як CLI, так і GUI. Вам потрібно встановити для параметра "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. Це дає поведінку дозволу програмі здійснювати попередню підготовку в режимі командного рядка при виклику з форми консолі (потенційно лише тоді, коли виявляються певні аргументи командного рядка), але при цьому все ще може запускатися як програма графічного інтерфейсу, вільна від консолі.
Я розмістив проект під назвою dualsubsystem в Google Code, який оновлює старе рішення коду-гуру цієї техніки та надає вихідний код та робочі прикладні двійкові файли.
Ось я вважаю простим рішенням проблеми .NET C #. Просто для того, щоб повторити проблему, коли ви запускаєте консольну "версію" програми з командного рядка за допомогою перемикача, консоль продовжує чекати (вона не повертається до командного рядка, і процес продовжує працювати), навіть якщо у вас є 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.
Console.WriteLine
, не висуває текстовий курсор (батьківської) консолі. Отже, коли ви виходите з програми, позиція курсора знаходиться в неправильному місці, і вам потрібно натиснути клавішу Enter кілька разів, щоб повернути її до "чистого" запиту.
/*
** 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, яка працює як графічний інтерфейс, так і як консольний додаток .