一个可执行文件可以是控制台和GUI应用程序吗?
我想创build一个C#程序,可以作为CLI或GUI应用程序运行,具体取决于传入的标志。 可以这样做吗?
我发现了这些相关的问题,但他们并不完全覆盖我的情况:
- 如何在GUI应用程序中写入控制台
- 如何使用Windows程序在C ++中获取控制台输出?
Jdigital的回答指向Raymond Chen的博客 ,这就解释了为什么你不能拥有既是控制台程序又是非控制台程序的应用程序:操作系统在程序开始运行之前需要知道哪个子系统使用。 一旦程序开始运行,返回并请求其他模式为时已晚。
凯德的回答指向一个关于运行一个.Net WinForms应用程序与控制台的文章 。 它使用在程序开始运行后调用AttachConsole
的技术。 这具有允许程序回写到启动程序的命令提示符的控制台窗口的效果。 但是这篇文章中的评论指出了我认为是一个致命的缺陷: subprocess并不真正控制控制台。 控制台继续代表父进程接受input,并且父进程没有意识到在使用控制台用于其他事情之前,它应该等待孩子完成运行。
陈的文章指出张俊峰的一篇文章解释了其他一些技巧 。
首先是devenv使用。 它实际上有两个程序的作品。 一个是devenv.exe ,它是主要的GUI程序,另一个是处理控制台模式任务的devenv.com ,但是如果以非控制台方式使用,它会将其任务转发到devenv.exe和退出。 该技术依赖于Win32规则,当您input没有文件扩展名的命令时, com文件会在exe文件之前被选中。
Windows Script Host有一个更简单的变化。 它提供了两个完全独立的二进制文件, wscript.exe和cscript.exe 。 同样,Java为控制台程序提供java.exe ,为非控制台程序提供javaw.exe 。
俊峰的第二个技巧是ildasm使用的。 他引用ildasm的作者在两种模式下运行的过程。 最终,它的function如下:
- 该程序被标记为控制台模式二进制文件,所以它总是从一个控制台开始。 这允许input和输出redirect正常工作。
- 如果程序没有控制台模式的命令行参数,它会重新启动。
仅仅调用FreeConsole
来使第一个实例不再是一个控制台程序是不够的。 这是因为启动程序cmd.exe的进程“知道”它启动了一个控制台模式程序,正在等待程序停止运行。 调用FreeConsole
将使ildasm停止使用控制台,但不会使父进程开始使用控制台。
所以第一个实例自己重新启动(用一个额外的命令行参数,我想)。 当您调用CreateProcess
,有两个不同的标志可以尝试, DETACHED_PROCESS
和CREATE_NEW_CONSOLE
,其中任何一个都可以确保第二个实例不会连接到父控制台。 之后,第一个实例可以终止并允许命令提示符恢复处理命令。
这种技术的副作用是当你从GUI界面启动程序时,仍然会有一个控制台。 它会瞬间在屏幕上闪烁,然后消失。
俊丰关于使用editbin来改变程序的控制台模式标志的文章中的一部分,我认为是一个红色的鲱鱼。 您的编译器或开发环境应该提供一个设置或选项来控制它创build的二进制types。 之后不需要修改任何东西。
底线是, 你可以有两个二进制文件,或者你可以有一个控制台窗口瞬间闪烁 。 一旦你决定哪一个是较小的罪恶,你有你的select的实现。
*
我说非控制台而不是GUI,因为否则这是一个错误的二分法。 仅仅因为一个程序没有控制台并不意味着它有一个GUI。 服务应用程序就是一个很好的例子。 另外,一个程序可以有一个控制台和窗口。
查看雷蒙德的博客关于这个话题:
http://blogs.msdn.com/oldnewthing/archive/2009/01/01/9259142.aspx
他的第一句话:“你不能,但你可以试图伪造它。”
http://www.csharp411.com/console-output-from-winforms-application/
只需在WinForms Application.
之前检查命令行参数Application.
东东。
我应该补充一点,在.NET中,只需简单地制作一个控制台和GUI项目,就可以在共享除了main之外的所有程序集的相同解决scheme中使用。 在这种情况下,如果启动时没有参数,则可以使命令行版本启动GUI版本。 你会得到一个闪烁的控制台。
有一个简单的方法来做你想做的事情。 编写应该同时具有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 }); } } }
我认为首选的技术是Rob称之为使用两个可执行文件的devenv技术:启动器“.com”和原始的“.exe”。 如果您使用样板代码(见下面的链接),这不是那么棘手。
该技术使用技巧来使“.com”成为stdin / stdout / stderr的代理,并启动相同名称的.exe文件。 这给出了允许程序在从控制台调用时(可能只有当检测到某些命令行参数时)以命令行模式执行的行为,同时仍然能够作为不具有控制台的GUI应用程序启动的行为。
我在Google Code上托pipe了一个名为dualsubsystem的项目,它更新了这种技术的旧的codeguru解决scheme,并提供了源代码和工作示例二进制文件。
这是我认为是简单的.NET C#解决scheme的问题。 只是为了重申这个问题,当你用一个交换机从一个命令行上运行应用程序的控制台“版本”时,控制台会一直等待(它不会返回到命令提示符,并且进程继续运行),即使你有一个Environment.Exit(0)
在您的代码结束。 为了解决这个问题,在调用Environment.Exit(0)
,调用这个:
SendKeys.SendWait("{ENTER}");
然后控制台得到最后的回车键,它需要返回到命令提示符,过程结束。 注意:不要调用SendKeys.Send()
,否则应用程序将崩溃。
仍然有必要像在很多post中提到的那样调用AttachConsole()
,但是在启动WinForm版本的应用程序时,我没有得到命令窗口闪烁。
以下是我创build的示例应用程序中的全部代码(不包括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), ie 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); }
我已经写了一个避免控制台闪光的替代方法。 请参阅如何创build可同时用作GUI和控制台应用程序的Windows程序 。
在静态构造函数中运行AllocConsole()可以为我工作