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


81

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

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



1
기록을 위해 : CLR이 아닌 운영 체제와 실제로 관련이 있습니다. 예를 들어, Linux의 Mono를 사용하면 이러한 응용 프로그램을 만드는 데 문제가 없습니다 (사실 모든 응용 프로그램은 콘솔이지만 Windows에서도 수행 할 수 있음). Java 또는 다른 * nix 프로그램과 마찬가지로. 그리고 일반적인 패턴은 사용자를 위해 GUI를 사용하면서 콘솔에 로그인하는 것입니다.
konrad.kruczynski

답변:


99

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가있는 것은 아닙니다. 서비스 응용 프로그램이 대표적인 예입니다. 또한 프로그램은 콘솔 창을 가질 수 있습니다 .


나는 이것이 오래된 대답이라는 것을 알고 있지만 editbin에 대한 red-herring 요점에서 그 트릭의 목적은 CRT WinMain가 적절한 매개 변수와 함께 함수 를 연결하고 (그래서 컴파일 /SUBSYSTEM:WINDOWS) 사후에 모드를 변경하는 것이라고 생각합니다. 로더는 콘솔 호스트를 시작합니다. 더 많은 피드백을 위해 CREATE_NO_WINDOWCreateProcess에서 GetConsoleWindow() == NULL다시 실행 여부를 확인하면서 이것을 시도했습니다 . 이것은 콘솔 깜박임을 수정하지는 않지만 특별한 cmd 인수가 없음을 의미합니다.

이것은 훌륭한 대답이지만 완전성을 위해 콘솔과 '콘솔이 아닌'프로그램의 주요 차이점이 무엇인지 언급 할 가치가있을 것입니다 (여기에서 오해하면 아래에 많은 잘못된 답변으로 이어질 것 같습니다). 즉, 콘솔에서 시작된 콘솔 앱은 완료 될 때까지 상위 콘솔로 제어를 반환하지 않는 반면 GUI 앱은 포크하고 즉시 반환합니다. 확실하지 않은 경우 DUMPBIN / headers를 사용하고 SUBSYSTEM 줄을 찾아서 어떤 맛이 있는지 정확히 확인할 수 있습니다.
piers7 2014-08-04

이것은 쓸모없는 최고의 답변입니다. 적어도 C / C ++ 관점에서. 누군가가 C #에 적용 할 수있는 Win32에 대한 아래의 dantill의 솔루션을 참조하십시오.
B. Nadolson

1
이 답변이 쓸모 없다고 생각하지 않습니다. 이 방법은 잘 작동하며 답변의 등급은 그 자체로 말합니다. Dantill의 접근 방식은 콘솔 앱에서 stdin의 연결을 끊습니다. 아래에 Kennedy의 "순간 깜박임"접근 방식의 C 버전을 별도의 답변으로 제공했습니다 (예, C #에 대해 게시 한 OP). 나는 그것을 여러 번 사용했고 그것에 매우 만족합니다.
willus 2015

당신은 자바에서이 작업을 수행 할 수 있습니다 ..)
Antoniossss


6

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

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

.NET에서는 main을 제외한 모든 어셈블리를 공유하는 동일한 솔루션에서 콘솔 및 GUI 프로젝트를 간단하게 만드는 것이 매우 쉽습니다. 이 경우 매개 변수없이 실행되는 경우 명령 행 버전이 GUI 버전을 실행하도록 만들 수 있습니다. 당신은 깜박이는 콘솔을 얻을 것입니다.


명령 줄 매개 변수의 존재는 확실한 화재 표시가 아닙니다. 많은 Windows 앱이 명령 줄 매개 변수를 사용할 수 있습니다
Neil N

3
내 요점은 없으면 GUI 버전을 시작하는 것입니다. 매개 변수를 사용하여 GUI 버전을 시작하려면 아마도 해당 매개 변수를 가질 수 있습니다.
Cade Roux

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

1
나는 이것을 좋아하고 내 Windows 7 dev 컴퓨터에서 잘 작동하지만 (가상) Windows XP 컴퓨터가 있고 다시 시작된 프로세스가 항상 콘솔을 가져 와서 자체적으로 다시 시작되는 끝없는 루프에서 사라지는 것처럼 보입니다. 어떤 아이디어?
사이먼 휴이트

1
이것에 매우 조심하십시오. Windows XP에서 이것은 실제로 죽이기 매우 어려운 무제한 리스폰 루프로 이어집니다.
사용자

3

선호하는 기술은 Rob이 두 개의 실행 파일을 사용 하는 devenv 기술 이라고 부르는 것입니다. 실행 프로그램 ".com"과 원래 ".exe"입니다. 작업 할 상용구 코드가있는 경우 사용하기가 그리 까다 롭지 않습니다 (아래 링크 참조).

이 기술은 ".com"이 stdin / stdout / stderr에 대한 프록시가되도록하고 동일한 이름의 .exe 파일을 실행하는 트릭을 사용합니다. 이것은 프로그램이 콘솔에서 호출 될 때 (잠재적으로 특정 명령 줄 인수가 감지 된 경우에만) 명령 줄 모드에서 수행 할 수있는 동작을 제공하는 동시에 콘솔없이 GUI 응용 프로그램으로 시작할 수 있습니다.

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


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로 이동하십시오.


나는 이것을 시도했고 문제는 사용하여 작성된 모든 것이 Console.WriteLine(부모) 콘솔의 텍스트 커서를 진행시키지 않는다는 것입니다 . 따라서 앱을 종료 할 때 커서 위치가 잘못된 위치에 있으며 Enter 키를 몇 번 눌러 "깨끗한"프롬프트로 돌아 가야합니다.
Tahir Hassan

@TahirHassan 여기에 설명 된대로 프롬프트 캡처 및 정리를 자동화 할 수 있지만 여전히 완벽한 솔루션은 아닙니다. stackoverflow.com/questions/1305257/…
rkagerer

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

1

콘솔 플래시를 피하는 대체 접근 방식을 작성했습니다. GUI 및 콘솔 응용 프로그램으로 작동하는 Windows 프로그램을 만드는 방법을 참조하십시오 .


1
나는 회의적 이었지만 완벽하게 작동합니다. 정말, 정말 완벽하게. 훌륭합니다! 내가 본 문제에 대한 첫 번째 진정한 해결책. (그것은 C / C ++ 코드하지 C # 코드..)
B. Nadolson

B. Nadolson에 동의합니다. 이것은 (C ++의 경우) 프로세스를 다시 시작하지 않고 여러 EXE없이 작동합니다.
GravityWell 2016

2
이 방법의 단점 : (1) 완료되면 콘솔에 추가 키 입력을 보내야합니다. (2) 콘솔 출력을 파일로 리디렉션 할 수 없습니다. (3) 첨부 된 stdin으로 테스트하지 않은 것 같습니다. 나는 또한 파일에서 리디렉션 될 수 없다고 생각합니다). 나에게 그것은 순간적으로 콘솔 창을 깜박이는 것을 피하기 위해 너무 많은 거래입니다. 재실행 방법은 적어도 진정한 듀얼 콘솔 / GUI를 제공합니다. 저는 이러한 앱을 수만 명의 사용자에게 배포했으며 일시적으로 깜박이는 콘솔 창에 대한 단 한 건의 불만이나 의견을받지 못했습니다.
willus 2017-04-02

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.