하나의 실행 파일이 콘솔 및 GUI 응용 프로그램이 될 수 있습니까? 만들고 싶습니다

어떤 플래그가 전달되는지에 따라 CLI 또는 GUI 응용 프로그램으로 실행할 수 있는 C # 프로그램 을 만들고 싶습니다 . 할 수 있습니까?

이러한 관련 질문을 찾았지만 내 상황을 정확히 다루지는 않습니다.



답변

Jdigital의 답변Raymond Chen의 블로그가리키며 , 이는 콘솔 프로그램과 비 콘솔 프로그램 모두 인 애플리케이션을 가질 수없는 이유를 설명 합니다. 프로그램이 사용할 하위 시스템을 실행하기 전에* OS가 알아야 합니다. 프로그램이 실행되기 시작하면 돌아가서 다른 모드를 요청하기에는 너무 늦습니다.

Cade의 대답 은 콘솔로 .Net WinForms 응용 프로그램을 실행하는 방법대한 기사를 가리 킵니다 . AttachConsole프로그램 실행이 시작된 후 호출하는 기술을 사용합니다 . 이것은 프로그램이 프로그램을 시작한 명령 프롬프트의 콘솔 창에 다시 쓸 수 있도록하는 효과가 있습니다. 그러나 그 기사의 댓글은 내가 치명적인 결함이라고 생각하는 것을 지적합니다 . 자식 프로세스는 실제로 콘솔을 제어하지 않습니다. 콘솔은 부모 프로세스를 대신하여 계속 입력을 받아들이고 부모 프로세스는 다른 작업을 위해 콘솔을 사용하기 전에 자식이 실행을 마칠 때까지 기다려야한다는 것을 인식하지 못합니다.

Chen의 기사 는 몇 가지 다른 기술을 설명하는 Junfeng Zhang 의 기사를 가리 킵니다 .

첫 번째는 devenv가 사용하는 것입니다. 실제로 두 개의 프로그램을 가지고 작동합니다. 하나는 기본 GUI 프로그램 인 devenv.exe 이고 다른 하나는 콘솔 모드 작업을 처리하는 devenv.com 이지만 콘솔과 같은 방식이 아닌 방식으로 사용되는 경우 해당 작업을 devenv.exe로 전달 하고 출구. 이 기술은 파일 확장자없이 명령을 입력 할 때 com 파일이 exe 파일 보다 먼저 선택 된다는 Win32 규칙에 의존 합니다.

Windows 스크립트 호스트가하는 것보다 더 간단한 변형이 있습니다. 완전히 분리 된 두 바이너리, wscript.execscript.exe를 제공 합니다. 마찬가지로 Java는 콘솔 프로그램 에는 java.exe 를 , 비 콘솔 프로그램에는 javaw.exe 를 제공합니다.

Junfeng의 두 번째 기술은 ildasm이 사용하는 것입니다. 그는 ildasm 의 저자가 두 모드에서 실행될 때 겪은 과정을 인용합니다 . 궁극적으로 수행하는 작업은 다음과 같습니다.

  1. 프로그램은 콘솔 모드 바이너리로 표시되므로 항상 콘솔로 시작합니다. 이렇게하면 입력 및 출력 리디렉션이 정상적으로 작동합니다.
  2. 프로그램에 콘솔 모드 명령 줄 매개 변수가 없으면 자동으로 다시 시작됩니다.

FreeConsole첫 번째 인스턴스가 콘솔 프로그램 이되지 않도록 호출 하는 것만으로는 충분하지 않습니다 . 프로그램을 시작한 프로세스 cmd.exe 는 콘솔 모드 프로그램을 시작한 것을 “인식”하고 프로그램 실행이 중지되기를 기다리고 있기 때문입니다. 호출 FreeConsole하면 ildasm 이 콘솔 사용을 중지하지만 부모 프로세스 가 콘솔 사용을 시작 하지 않습니다 .

따라서 첫 번째 인스턴스는 자체적으로 다시 시작됩니다 (추가 명령 줄 매개 변수 사용). 를 호출 할 때 CreateProcess시도 할 두 가지 플래그가 DETACHED_PROCESS있으며CREATE_NEW_CONSOLE , 둘 중 하나는 두 번째 인스턴스가 상위 콘솔에 연결되지 않도록합니다. 그 후 첫 번째 인스턴스가 종료되고 명령 프롬프트가 명령 처리를 재개하도록 허용 할 수 있습니다.

이 기술의 부작용은 GUI 인터페이스에서 프로그램을 시작할 때 여전히 콘솔이 있다는 것입니다. 화면에서 잠시 깜박이다가 사라집니다.

프로그램의 콘솔 모드 플래그를 변경하기 위해 editbin 을 사용하는 것에 대한 Junfeng의 기사에서 부분은 red herring이라고 생각합니다. 컴파일러 또는 개발 환경은 생성되는 바이너리 유형을 제어하는 ​​설정 또는 옵션을 제공해야합니다. 나중에 수정할 필요가 없습니다.

결론 은 두 개의 바이너리를 가질 수 있거나 콘솔 창의 일시적인 깜박임이있을 수 있다는 것 입니다. 어느 것이 덜 악한 지 결정하면 구현을 선택할 수 있습니다.

*GUI 대신 콘솔이 아니라고 말합니다. 그렇지 않으면 잘못된 이분법이기 때문입니다. 프로그램에 콘솔이 없다고해서 GUI가있는 것은 아닙니다. 서비스 응용 프로그램이 대표적인 예입니다. 또한 프로그램은 콘솔 창을 가질 수 있습니다 .


답변

이 주제에 대한 Raymond의 블로그를 확인하십시오.

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

그의 첫 번째 문장 : “당신은 할 수 없지만 그것을 속일 수 있습니다.”


답변

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

WinForms 항목 전에 명령 줄 인수를 확인하십시오 Application..

.NET에서는 main을 제외한 모든 어셈블리를 공유하는 동일한 솔루션에서 콘솔 및 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 응용 프로그램으로 시작할 수 있습니다.

저는 이 기술의 오래된 codeguru 솔루션을 업데이트하고 소스 코드와 작업 예제 바이너리를 제공하는 Google 코드에서 dualsubsystem 이라는 프로젝트를 호스팅했습니다 .


답변

다음은 문제에 대한 간단한 .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로 이동하십시오.


답변

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