一个可执行文件可以是控制台和GUI应用程序吗?

时间:2009-01-29 21:32:08

标签: c# user-interface command-line-interface

我想创建一个C#程序,可以作为CLI或GUI应用程序运行,具体取决于传递给它的标志。可以这样做吗?

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

9 个答案:

答案 0 :(得分:85)

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

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

陈的文章指出an article by Junfeng Zhang that explains a couple of other techniques

首先是 devenv 使用的内容。它实际上有两个程序。一个是 devenv.exe ,它是主要的GUI程序,另一个是 devenv.com ,它处理控制台模式任务,但如果它用于非类似于控制台的方式,它将其任务转发到 devenv.exe 并退出。该技术依赖于Win32规则,当您键入没有文件扩展名的命令时, com 文件会在 exe 文件之前被选中。

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

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

  1. 程序被标记为控制台模式二进制文件,因此它始终以控制台开始。这允许输入和输出重定向正常工作。
  2. 如果程序没有控制台模式命令行参数,则会重新启动。
  3. 仅仅调用FreeConsole使第一个实例不再是控制台程序是不够的。这是因为启动程序的进程 cmd.exe ,“知道”它启动了一个控制台模式程序,并且正在等待程序停止运行。调用FreeConsole会使 ildasm 停止使用控制台,但它不会使父进程启动使用控制台。

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

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

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

    然后,底线是您可以有两个二进制文件,或者您可以暂时忽略控制台窗口。一旦你决定哪个是较小的邪恶,你可以选择实施。

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

答案 1 :(得分:10)

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

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

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

答案 2 :(得分:6)

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

只需检查WinForms Application.之前的命令行参数。

我应该在.NET中添加它是非常容易的,只需在同一个解决方案中创建一个控制台和GUI项目,它们共享除main之外的所有程序集。在这种情况下,如果没有参数启动,您可以使命令行版本只启动GUI版本。你会得到一个闪烁的控制台。

答案 3 :(得分:5)

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

答案 4 :(得分:3)

我认为首选技术是Rob称之为使用两个可执行文件的 devenv 技术:启动器“.com”和原始“.exe”。如果您有要使用的样板代码,那么使用它并不是那么棘手(参见下面的链接)。

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

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

答案 5 :(得分:3)

我认为这是解决问题的简单.NET C#解决方案。只是重述问题,当你运行控制台&#34;版本&#34;对于带有开关的命令行中的应用程序,即使您的代码末尾有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。

答案 6 :(得分:2)

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

答案 7 :(得分:1)

我已经编写了一种避免控制台闪存的替代方法。请参阅 How to create a Windows program that works both as a GUI and console application

答案 8 :(得分:0)

在静态构造函数中运行AllocConsole()为我工作