88

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

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

4

9 回答 9

108

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

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

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

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

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

君峰的第二招,就是ildasm使用的。他引用了il​​dasm的作者在两种模式下运行时所经历的过程。最终,这就是它的作用:

  1. 该程序被标记为控制台模式二进制文件,因此它始终以控制台开始。这允许输入和输出重定向正常工作。
  2. 如果程序没有控制台模式命令行参数,它会重新启动自己。

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

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

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

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

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

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

于 2009-01-29T23:54:32.357 回答
11

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

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

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

于 2009-01-29T21:40:04.140 回答
6

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

只需在 WinForms 内容之前检查命令行参数Application.

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

于 2009-01-29T21:36:57.203 回答
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
        });
      }
    }
  }
于 2013-03-16T11:45:15.953 回答
3

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

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

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

于 2009-07-05T17:40:18.957 回答
3

这是我认为解决问题的简单 .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。

于 2015-09-18T08:52:52.453 回答
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);
    }
于 2013-01-27T17:50:40.557 回答
2

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

于 2013-05-12T20:54:30.980 回答
0

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

于 2014-11-15T09:51:09.897 回答