C# 一个可执行文件可以既是控制台又是 GUI 应用程序吗?

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/493536/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-04 05:34:22  来源:igfitidea点击:

Can one executable be both a console and GUI application?

c#user-interfacecommand-line-interface

提问by BCS

I want to make a C#program that can be run as a CLI or GUI application depending on what flags are passed into it. Can this be done?

我想制作一个C#程序,它可以根据传入的标志作为 CLI 或 GUI 应用程序运行。这能做到吗?

I have found these related questions, but they don't exactly cover my situation:

我发现了这些相关的问题,但它们并没有完全涵盖我的情况:

采纳答案by Rob Kennedy

Jdigital's answerpoints to Raymond Chen's blog, which explains why you can't have an application that's both a console program and a non-console*program: The OS needs to know before the program starts runningwhich subsystem to use. Once the program has started running, it's too late to go back and request the other mode.

Jdigital 的回答指向Raymond Chen 的博客,这解释了为什么您不能拥有既是控制台程序又是非控制台*程序的应用程序:操作系统需要在程序开始运行之前知道要使用哪个子系统。一旦程序开始运行,返回并请求其他模式为时已晚。

Cade's answerpoints to an article about running a .Net WinForms application with a console. It uses the technique of calling AttachConsoleafter the program starts running. This has the effect of allowing the program to write back to the console window of the command prompt that started the program. But the comments in that article point out what I consider to be a fatal flaw: The child process doesn't really control the console.The console continues accepting input on behalf of the parent process, and the parent process is not aware that it should wait for the child to finish running before using the console for other things.

Cade 的回答指向了一篇关于使用控制台运行 .Net WinForms 应用程序的文章。它使用AttachConsole程序开始运行后调用的技术。这具有允许程序写回启动程序的命令提示符的控制台窗口的效果。但那篇文章中的评论指出了我认为的一个致命缺陷:子进程并没有真正控制控制台。控制台继续代表父进程接受输入,父进程不知道它应该等待子进程完成运行,然后再使用控制台做其他事情。

Chen's article points to an article by Junfeng Zhang that explains a couple of other techniques.

Chen 的文章指向Junfeng Zhang的一篇文章,该文章解释了其他一些技术

The first is what devenvuses. It works by actually having two programs. One is devenv.exe, which is the main GUI program, and the other is devenv.com, which handles console-mode tasks, but if it's used in a non-console-like manner, it forwards its tasks to devenv.exeand exits. The technique relies on the Win32 rule that comfiles get chosen ahead of exefiles when you type a command without the file extension.

第一个是devenv使用的。它的工作原理是实际上有两个程序。一个是devenv.exe,它是主要的 GUI 程序,另一个是devenv.com,它处理控制台模式的任务,但如果它以非控制台方式使用,它会将其任务转发到devenv.exe和退出。该技术依赖于 Win32 规则,即当您键入不带文件扩展名的命令时,com文件会在exe文件之前被选择。

There's a simpler variation on this that the Windows Script Host does. It provides two completely separate binaries, wscript.exeand cscript.exe. Likewise, Java provides java.exefor console programs and javaw.exefor non-console programs.

Windows Script Host 在这方面有一个更简单的变体。它提供了两个完全独立的二进制文件,wscript.execscript.exe。同样,Java为控制台程序提供java.exe,为非控制台程序提供javaw.exe

Junfeng's second technique is what ildasmuses. He quotes the process that ildasm's author went through when making it run in both modes. Ultimately, here's what the it does:

俊峰的第二个技巧是ildasm使用的。他引用了ildasm的作者在使其在两种模式下运行时所经历的过程。最终,这是它的作用:

  1. The program is marked as a console-mode binary, so it always starts out with a console. This allows input and output redirection to work as normal.
  2. If the program has no console-mode command-line parameters, it re-launches itself.
  1. 该程序被标记为控制台模式二进制文件,因此它始终以控制台开头。这允许输入和输出重定向正常工作。
  2. 如果程序没有控制台模式命令行参数,它会重新启动自己。

It's not enough to simply call FreeConsoleto make the first instance cease to be a console program. That's because the process that started the program, cmd.exe, "knows" that it started a console-mode program and is waiting for the program to stop running. Calling FreeConsolewould make ildasmstop using the console, but it wouldn't make the parent process startusing the console.

仅仅调用FreeConsole使第一个实例不再是控制台程序是不够的。这是因为启动程序cmd.exe的进程“知道”它启动了一个控制台模式程序并正在等待程序停止运行。调用FreeConsole会使ildasm停止使用控制台,但不会使父进程开始使用控制台。

So the first instance restarts itself (with an extra command-line parameter, I suppose). When you call CreateProcess, there are two different flags to try, DETACHED_PROCESSand CREATE_NEW_CONSOLE, either of which will ensure that the second instance will not be attached to the parent console. After that, the first instance can terminate and allow the command prompt to resume processing commands.

所以第一个实例会自动重启(我想有一个额外的命令行参数)。当您调用 时CreateProcess,有两个不同的标志可以尝试,DETACHED_PROCESS并且CREATE_NEW_CONSOLE,其中任何一个都将确保第二个实例不会附加到父控制台。之后,第一个实例可以终止并允许命令提示符恢复处理命令。

The side effect of this technique is that when you start the program from a GUI interface, there will still be a console. It will flash on the screen momentarily and then disappear.

这种技术的副作用是当你从 GUI 界面启动程序时,仍然会有一个控制台。它会在屏幕上短暂闪烁然后消失。

The part in Junfeng's article about using editbinto change the program's console-mode flag is a red herring, I think. Your compiler or development environment should provide a setting or option to control which kind of binary it creates. There should be no need to modify anything afterward.

我认为Junfeng的文章中关于使用editbin更改程序的控制台模式标志的部分是一个红鲱鱼。您的编译器或开发环境应该提供一个设置或选项来控制它创建的二进制文件类型。之后应该不需要修改任何东西。

The bottom line, then, is that you can either have two binaries, or you can have a momentary flicker of a console window. Once you decide which is the lesser evil, you have your choice of implementations.

最重要的是,您可以拥有两个二进制文件,也可以让控制台窗口瞬间闪烁。一旦你决定哪个是较小的邪恶,你就可以选择实现。

*I say non-consoleinstead of GUIbecause otherwise it's a false dichotomy. Just because a program doesn't have a console doesn't mean it has a GUI. A service application is a prime example. Also, a program can have a console andwindows.

*我说非控制台而不是GUI,因为否则它是一个错误的二分法。仅仅因为程序没有控制台并不意味着它有 GUI。服务应用程序就是一个典型的例子。此外,一个程序可以有一个控制台窗口。

回答by Cade Roux

http://www.csharp411.com/console-output-from-winforms-application/

http://www.csharp411.com/console-output-from-winforms-application/

Just check the command line arguments before the WinForms Application.stuff.

只需在 WinFormsApplication.内容之前检查命令行参数即可。

I should add that in .NET it is RIDICULOUSLY easy to simply make a console and GUI projects in the same solution which share all their assemblies except main. And in this case, you could make the command line version simply launch the GUI version if it is launched with no parameters. You would get a flashing console.

我应该补充一点,在 .NET 中,在同一个解决方案中简单地制作控制台和 GUI 项目是非常容易的,这些项目共享除 main 之外的所有程序集。在这种情况下,如果不带参数启动,您可以使命令行版本简单地启动 GUI 版本。你会得到一个闪烁的控制台。

回答by jdigital

Check out Raymond's blog on this topic:

查看 Raymond 关于此主题的博客:

https://devblogs.microsoft.com/oldnewthing/20090101-00/?p=19643

https://devblogs.microsoft.com/oldnewthing/20090101-00/?p=19643

His first sentence: "You can't, but you can try to fake it."

他的第一句话:“你不能,但你可以试着伪造它。”

回答by gabeiscoding

I think the preferred technique is what Rob called the devenvtechnique of using two executables: a launcher ".com" and the original ".exe". This is not that tricky to use if you have the boilerplate code to work with (see below link).

我认为首选技术是 Rob 所说的使用两个可执行文件的devenv技术:启动器“.com”和原始“.exe”。如果您有样板代码可以使用,这并不难使用(见下面的链接)。

The technique uses tricks to have that ".com" be a proxy for the stdin/stdout/stderr and launch the same-named .exe file. This give the behavior of allowing the program to preform in a command line mode when called form a console (potentially only when certain command-line arguments are detected) while still being able to launch as a GUI application free of a console.

该技术使用技巧让“.com”成为 stdin/stdout/stderr 的代理并启动同名的 .exe 文件。这提供了允许程序在从控制台调用时以命令行模式执行的行为(可能仅当检测到某些命令行参数时),同时仍然能够作为没有控制台的 GUI 应用程序启动。

I hosted a project called dualsubsystem on Google Codethat updates an old codeguru solution of this technique and provides the source code and working example binaries.

在 Google Code 上托管了一个名为dualsubsystem的项目,该项目更新了该技术的旧codeguru解决方案,并提供了源代码和工作示例二进制文件。

回答by willus

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

回答by user1566352

There is an easy way to do what you want. I'm always using it when writing apps that should have both a CLI and a GUI. You have to set your "OutputType" to "ConsoleApplication" for this to work.

有一种简单的方法可以做你想做的事。在编写应具有 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
        });
      }
    }
  }

回答by dantill

I have written up an alternative approach which avoids the console flash. See How to create a Windows program that works both as a GUI and console application.

我已经编写了一种避免控制台闪存的替代方法。请参阅如何创建既可用作 GUI 又可用作控制台应用程序的 Windows 程序

回答by Shaggy

Run AllocConsole() in a static constructor works for me

在静态构造函数中运行 AllocConsole() 对我有用

回答by LTDev

Here is what I believe to be the simple .NET C# solution to the problem. Just to restate the problem, when you run the console "version" of the app from a command line with a switch, the console keeps waiting (it doesn't return to the command prompt and the process keeps running) even if you have an Environment.Exit(0)at the end of your code. To fix this, just before calling Environment.Exit(0), call this:

这是我认为是解决问题的简单 .NET C# 解决方案。只是重申这个问题,当您从带有开关的命令行运行应用程序的控制台“版本”时,控制台会一直等待(它不会返回到命令提示符并且进程继续运行),即使您有Environment.Exit(0)在您的代码末尾。要解决此问题,请在调用之前调用Environment.Exit(0)

SendKeys.SendWait("{ENTER}");

Then the console gets the final Enter key it needs to return to the command prompt and the process ends. Note: Don't call SendKeys.Send(), or the app will crash.

然后控制台获取返回命令提示符所需的最终 Enter 键,过程结束。注意:不要调用SendKeys.Send(),否则应用程序会崩溃。

It's still necessary to call AttachConsole()as mentioned in many posts, but with this I get no command window flicker when launching the WinForm version of the app.

仍然需要AttachConsole()像许多帖子中提到的那样调用,但是在启动应用程序的 WinForm 版本时,我没有看到命令窗口闪烁。

Here's the entire code in a sample app I created (without the WinForms code):

这是我创建的示例应用程序中的完整代码(没有 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());
            }
        }
    }
}

Hope it helps someone from also spending days on this problem. Thanks for the hint go to @dantill.

希望它可以帮助某人在这个问题上花费数天时间。感谢您的提示,请转至@dantill。