我想制作一个可以作为CLI或GUI应用程序运行的C#程序,具体取决于传递给它的标志是什么。能做到吗?
我发现了以下相关问题,但它们并不能完全满足我的情况:
我想制作一个可以作为CLI或GUI应用程序运行的C#程序,具体取决于传递给它的标志是什么。能做到吗?
我发现了以下相关问题,但它们并不能完全满足我的情况:
Answers:
Jdigital的答案指向Raymond Chen的博客,该博客解释了为什么不能同时拥有控制台程序和非控制台*
程序的应用程序:操作系统需要在程序开始运行要使用的子系统之前就知道。程序开始运行后,现在返回并请求其他模式为时已晚。
Cade的答案指向有关使用控制台运行.Net WinForms应用程序的文章。它使用AttachConsole
程序开始运行后调用的技术。这样的效果是允许程序将其写回到启动程序的命令提示符的控制台窗口。但是该文章中的评论指出了我认为是致命的缺陷:子进程实际上并不控制控制台。控制台继续代表父进程接受输入,并且父进程不知道在使用控制台进行其他操作之前,它应等待子进程完成运行。
Chen的文章指向Zhang Junfeng Zhang的文章,它解释了其他两种技术。
首先是devenv的用途。它实际上有两个程序。一个是主界面GUI程序devenv.exe,另一个是处理控制台模式任务的devenv.com,但是如果以非控制台方式使用它,则它将其任务转发给devenv.exe和退出。该技术依赖于Win32规则,当您键入不带文件扩展名的命令时,将在exe文件之前选择com文件。
Windows脚本宿主对此有一个更简单的变化。它提供了两个完全独立的二进制文件wscript.exe和cscript.exe。同样,Java为控制台程序提供java.exe,为非控制台程序提供javaw.exe。
俊峰的第二种技术是ildasm使用的技术。他引用了ildasm的作者在两种模式下运行时所经历的过程。最终,这是它的作用:
仅仅通过调用FreeConsole
使第一个实例不再是控制台程序是不够的。这是因为启动程序cmd.exe的进程“知道”它已启动控制台模式程序,并且正在等待程序停止运行。调用FreeConsole
将使ildasm停止使用控制台,但不会使父进程开始使用控制台。
因此,第一个实例将自行重启(我想带有一个额外的命令行参数)。当您调用时CreateProcess
,有两个不同的标志可以尝试使用,分别是DETACHED_PROCESS
和CREATE_NEW_CONSOLE
,这两个标志将确保第二个实例不会附加到父控制台。此后,第一个实例可以终止并允许命令提示符恢复处理命令。
这种技术的副作用是,当您从GUI界面启动程序时,仍然会有一个控制台。它将在屏幕上短暂闪烁,然后消失。
我认为,Junfeng文章中有关使用editbin更改程序的控制台模式标志的部分是一个红色的鲱鱼。您的编译器或开发环境应提供一个设置或选项来控制它创建哪种二进制文件。此后无需再进行任何修改。
最重要的是,您可以具有两个二进制文件,也可以具有控制台窗口的瞬时闪烁。一旦确定哪个是较小的邪恶,就可以选择实现。
*
我说的是非控制台而不是GUI,因为这是错误的二分法。仅仅因为程序没有控制台并不意味着它具有GUI。服务应用程序就是一个很好的例子。另外,程序可以具有控制台和窗口。
WinMain
函数与适当的参数链接(使用进行编译/SUBSYSTEM:WINDOWS
),然后事后更改模式,因此加载程序将启动控制台主机。为了获得更多反馈,我尝试CREATE_NO_WINDOW
在CreateProcess中进行此操作,并GetConsoleWindow() == NULL
检查是否重新启动。这不能解决控制台闪烁问题,但确实意味着没有特殊的cmd参数。
查看有关该主题的Raymond博客:
https://devblogs.microsoft.com/oldnewthing/20090101-00/?p=19643
他的第一句话:“您不能,但您可以尝试伪造它。”
http://www.csharp411.com/console-output-from-winforms-application/
只需检查WinForms之前的命令行参数即可Application.
。
我应该补充一点,在.NET中,只需在同一解决方案中简单地创建一个控制台和GUI项目,即可共享主组件之外的所有组件,这非常容易。并且在这种情况下,如果不带参数启动命令行版本,则只需启动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上托管了一个名为dualsubsystem的项目,该项目更新了该技术的旧codeguru解决方案,并提供了源代码和有效的示例二进制文件。
我认为这是解决该问题的简单.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);
}
我已经写出了一种避免控制台刷新的替代方法。请参阅如何创建既可以用作GUI又可以用作控制台应用程序的Windows程序。