может ли один исполняемый файл быть одновременно консольным и графическим приложением?

<p>я хочу сделать <a href="http://en.wikipedia.org/wiki/c\_sharp\_%28programming\_language%29">c #</a> программу, которая может быть запущена как приложение cli или gui в зависимости от того, какие флаги передаются в него. можно ли это сделать?</p><p>я нашел эти связанные вопросы, но они не совсем охватывают мою ситуацию:</p><ul> <li> <a href="https://stackoverflow.com/questions/192294/how-to-write-to-the-console-in-a-gui-application">как написать на консоль в приложении с графическим интерфейсом</a> </li> <li> <a href="https://stackoverflow.com/questions/191842/how-do-i-get-console-output-in-c-with-a-windows-program">как я могу получить консольный вывод в c++ с программой windows?</a> </li> </ul>

<p> <a href="https://stackoverflow.com/questions/493536/can-one-executable-be-both-a-console-and-gui-app/493568#493568">ответ jdigital</a> указывает на <a href="http://blogs.msdn.com/oldnewthing/archive/2009/01/01/9259142.aspx">блог раймонда чена</a> , в котором объясняется, почему у вас не может быть приложения, которое является одновременно консольной и не консольной <code>*</code>-программой: ос должна знать <em>до запуска программы) выполняется</em> какую подсистему использовать. как только программа запустилась, уже поздно возвращаться и запрашивать другой режим.</p><p> <a href="https://stackoverflow.com/questions/493536/can-one-executable-be-both-a-console-and-gui-app/493556#493556">ответ кейда</a> указывает на <a href="http://www.csharp411.com/console-output-from-winforms-application/">статью о запуске приложения .net winforms с консолью</a> . он использует технику вызова <a href="http://msdn2.microsoft.com/en-us/library/ms681952.aspx"><code>attachconsole</code></a> после запуска программы. это позволяет программе писать обратно в консольное окно командной строки, которая запустила программу. но комментарии в этой статье указывают на то, что я считаю фатальным недостатком: <em>дочерний процесс на самом деле не контролирует консоль.</em> консоль продолжает принимать входные данные от имени родительского процесса и родительского процесса. не знает, что ему следует дождаться, пока ребенок закончит бег, прежде чем использовать консоль для других целей.</p><p>статья чена указывает на <a href="http://blogs.msdn.com/junfeng/archive/2004/02/06/68531.aspx">статью junfeng zhang, которая объясняет несколько других техник</a> .</p><p>первый - то, что использует <em>devenv</em>. это работает на самом деле две программы. одним из них является <em>devenv.exe</em>, который является основной программой с графическим интерфейсом, а другим является <em>devenv.com</em>, который обрабатывает задачи в режиме консоли, но если он используется в не консольном режиме. таким же образом он передает свои задачи <em>devenv.exe</em> и завершает работу. этот метод основан на правиле win32, согласно которому файлы <em>com</em> выбираются перед файлами <em>exe</em> при вводе команды без расширения файла.</p><p>существует более простой вариант, чем windows script host. он предоставляет два совершенно разных двоичных файла: <em>wscript.exe</em> и <em>cscript.exe</em>. аналогично, java предоставляет <em>java.exe</em> для консольных программ и <em>javaw.exe</em> для неконсольных программ.</p><p>второй метод junfeng - это то, что использует <em>ildasm</em>. он цитирует процесс, через который прошел автор <em>ildasm</em> при его запуске в обоих режимах. в конечном счете, вот что он делает:</p><ol> <li>программа помечена как двоичный файл в режиме консоли, поэтому она всегда начинается с консоли. это позволяет перенаправлению ввода и вывода работать как обычно.</li> <li>если программа не имеет параметров командной строки в режиме консоли, она перезапускается сама.</li> </ol><p>недостаточно просто вызвать <code>freeconsole</code>, чтобы первый экземпляр перестал быть консольной программой. это связано с тем, что процесс, который запустил программу, <em>cmd.exe</em>, «знает», что он запустил программу в режиме консоли и ожидает остановки программы. вызов <code>freeconsole</code> заставил бы <em>ildasm</em> прекратить использование консоли, но это не заставило бы родительский процесс <em>start</em> использовать консоль.</p><p>таким образом, первый экземпляр перезапускается сам (с дополнительным параметром командной строки, я полагаю). когда вы вызываете <code>createprocess</code>, нужно попробовать два разных флага: <a href="http://msdn.microsoft.com/en-us/library/ms684863.aspx"><code>detached\_process</code> и <code>create\_new\_console</code></a> , каждый из которых гарантирует, что второй экземпляр не будет подключен к родительской консоли. после этого первый экземпляр может завершиться и разрешить команде prompt возобновить обработку команд.</p><p>побочным эффектом этого метода является то, что при запуске программы из интерфейса графического интерфейса все равно будет консоль. он будет мигать на экране на мгновение, а затем исчезнет.</p><p>часть в статье junfeng об использовании <em>editbin</em> для изменения флага режима консоли программы, я думаю, представляет собой красную сельдь. ваш компилятор или среда разработки должны предоставить параметр или опцию для управления типом двоичного файла, который он создает. там не должно быть необходимости что-либо изменить потом.</p><p>суть в том, что <strong>у вас может быть два двоичных файла или кратковременное мерцание окна консоли</strong>. как только вы решите, какое меньшее зло, у вас есть выбор реализации.</p><p><code>*</code> я говорю <em>не консольный</em> вместо <em>gui</em>, потому что в противном случае это ложная дихотомия. тот факт, что программа не имеет консоли, не означает, что она имеет графический интерфейс. приложение службы является ярким примером. также программа может иметь консольные <em>и</em> окна.</p>

<p>посмотрите блог раймонда на эту тему:</p><p> <a href="http://blogs.msdn.com/oldnewthing/archive/2009/01/01/9259142.aspx">http://blogs.msdn.com/oldnewthing/archive/2009/01/01/9259142.aspx</a> </p><p>его первое предложение: «вы не можете, но вы можете попытаться подделать это».</p>

<p> <a href="http://www.csharp411.com/console-output-from-winforms-application/">http://www.csharp411.com/console-output-from-winforms-application/</a> </p><p>просто проверьте аргументы командной строки перед winforms <code>application.</code>.</p><p>я должен добавить, что в .net просто смешно создавать консоль и проекты с графическим интерфейсом в одном решении, которые совместно используют все свои сборки, кроме основной. и в этом случае вы можете заставить версию командной строки просто запустить версию gui, если она запускается без параметров. вы бы получили мигающую консоль.</p>

<p>существует простой способ сделать то, что вы хотите. я всегда использую его при написании приложений, которые должны иметь как cli, так и gui. вы должны установить свой «outputtype» в «consoleapplication», чтобы это работало.</p><pre><code>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 }); } } } </code></pre>

<p>я думаю, что предпочтительной техникой является то, что роб назвал техникой <em>devenv</em> с использованием двух исполняемых файлов: средства запуска «.com» и оригинального «.exe». это не так сложно использовать, если у вас есть стандартный код для работы (см. ссылку ниже).</p><p>техника использует трюки, чтобы этот «.com» был прокси для stdin/stdout/stderr и запускал файл с тем же именем .exe. это дает поведение, позволяющее программе выполнять преформу в режиме командной строки при вызове из консоли (возможно, только при обнаружении определенных аргументов командной строки), при этом все еще имея возможность запускаться как приложение с графическим интерфейсом без консоли.</p><p>я разместил проект под названием <a href="http://code.google.com/p/dualsubsystem/">dualsubsystem в google code</a> , который обновляет старое решение codeguru для этой техники и предоставляет исходный код и рабочие примеры двоичных файлов.</p>

<p>вот что я считаю простым решением проблемы .net c #. просто для повторения проблемы, когда вы запускаете консольную «версию» приложения из командной строки с переключателем, консоль продолжает ждать (она не возвращается к командной строке и процесс продолжает работать), даже если у вас есть <code>environment.exit(0)</code> в конце вашего кода. чтобы это исправить, перед вызовом <code>environment.exit(0)</code>, вызовите это:</p><pre><code>sendkeys.sendwait("{enter}"); </code></pre><p>затем консоль получает последний ключ enter, который необходимо вернуть в командную строку, и процесс завершается. примечание: не вызывайте <code>sendkeys.send()</code>, иначе приложение вылетит.</p><p>по-прежнему необходимо вызывать <code>attachconsole()</code>, как упоминалось во многих постах, но при этом я не получаю мерцания окна команды при запуске версии приложения winform.</p><p>вот весь код в образце приложения, которое я создал (без кода winforms):</p><pre><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()); } } } } </code></pre><p>надеюсь, что это поможет кому-то также потратить дни на эту проблему. спасибо за подсказку, перейдите на @dantill.</p>

<pre><code>/* ** 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); } </code></pre>

<p>я написал альтернативный подход, который избегает вспышки консоли. см. <em> <a href="http://www.tillett.info/2013/05/13/how-to-create-a-windows-program-that-works-as-both-as-a-gui-and-console-application/">как создать программу для windows, которая работает как с графическим интерфейсом, так и с консольным приложением</a> </em>.</p>

<p>запуск allocconsole () в статическом конструкторе у меня работает</p>