1つの実行可能ファイルをコンソールとGUIアプリケーションの両方にすることはできますか?


81

渡されるフラグに応じて、CLIまたはGUIアプリケーションとして実行できるC#プログラムを作成したいと思います。これはできますか?

私はこれらの関連する質問を見つけましたが、それらは私の状況を正確にカバーしていません:



1
念のために言っておきますが、これは実際にはCLRではなくオペレーティングシステムに関連しています。たとえば、Linux上のMonoでは、このようなアプリケーションの作成に問題はありません(実際、すべてのアプリケーションはコンソールですが、Windowsでも何でもできます)-Javaやその他の* nixプログラムと同じです。また、一般的なパターンは、ユーザーのGUIを使用しているときにコンソールにログオンすることです。
konrad.kruczynski 2012年

回答:


99

Jdigitalの回答はRaymond Chenのブログを指しています。このブログでは、コンソールプログラムと非コンソールプログラムの両方のアプリケーションを使用できない理由を説明しています*。OSは、プログラムが使用するサブシステムの実行開始する前に知る必要があります。プログラムの実行が開始されると、戻って他のモードを要求するには遅すぎます。

ケイドの回答にポイントコンソールでの.NET WinFormsのアプリケーションを実行する方法についての記事AttachConsoleプログラムの実行開始後に呼び出す手法を使用します。これには、プログラムを開始したコマンドプロンプトのコンソールウィンドウにプログラムが書き戻すことができるようにする効果があります。しかし、その記事のコメントは、私が致命的な欠陥であると考えるものを指摘しています。子プロセスは実際にはコンソールを制御していません。コンソールは親プロセスに代わって入力を受け入れ続け、親プロセスは、他の目的でコンソールを使用する前に、子が実行を終了するのを待つ必要があることを認識していません。

Chenの記事は、他のいくつかのテクニックを説明しているJunfengZhangの記事を指しています

1つ目は、devenvが使用するものです。それは実際に2つのプログラムを持つことによって機能します。一つはDEVENV.EXEメインGUIプログラムである、もう一方はあるdevenv.com、それは非コンソール状に使われている場合、それがために、そのタスクを転送するハンドルコンソールモードのタスクが、DEVENV.EXEと終了します。この手法は、ファイル拡張子なしでコマンドを入力すると、exeファイルよりも先にcomファイルが選択されるというWin32ルールに依存しています。

これには、Windows ScriptHostよりも単純なバリエーションがあります。これは、2つの完全に独立したバイナリが、提供wscript.exeとしてます。cscript.exeを。同様に、Javaはコンソールプログラム用にjava.exeを提供し、非コンソールプログラム用にjavaw.exeを提供します。

Junfengの2番目の手法は、ildasmが使用する手法です。彼は、ildasmの作者が両方のモードで実行するときに行ったプロセスを引用しています。最終的には、次のようになります。

  1. プログラムはコンソールモードのバイナリとしてマークされているため、常にコンソールから始まります。これにより、入力と出力のリダイレクトが通常どおり機能します。
  2. プログラムにコンソールモードのコマンドラインパラメータがない場合は、プログラムが再起動します。

FreeConsole最初のインスタンスがコンソールプログラムでなくなるように呼び出すだけでは十分ではありません。これは、プログラムを開始したプロセスcmd.exeが、コンソールモードプログラムを開始したことを「認識」しており、プログラムの実行が停止するのを待機しているためです。呼び出すFreeConsoleと、ildasmはコンソールの使用を停止しますが、親プロセスがコンソールの使用を開始することはありません。

したがって、最初のインスタンスは自動的に再起動します(追加のコマンドラインパラメーターを使用すると思います)。を呼び出すときCreateProcess、試行する2つの異なるフラグがDETACHED_PROCESSありCREATE_NEW_CONSOLE、どちらも2番目のインスタンスが親コンソールに接続されないようにします。その後、最初のインスタンスが終了し、コマンドプロンプトでコマンドの処理を再開できるようになります。

この手法の副作用は、GUIインターフェイスからプログラムを起動したときに、コンソールが残っていることです。画面上で一瞬点滅してから消えます。

editbinを使用してプログラムのコンソールモードフラグを変更することに関するJunfengの記事の一部は、赤いニシンだと思います。コンパイラまたは開発環境は、作成するバイナリの種類を制御するための設定またはオプションを提供する必要があります。後で何も変更する必要はありません。

つまり、2つのバイナリを使用することも、コンソールウィンドウを瞬間的にちらつくこともできます。どちらがそれほど悪ではないかを決定したら、実装を選択できます。

*GUIではなく非コンソールと言います。そうでなければ誤った二分法だからです。プログラムにコンソールがないからといって、GUIがあるとは限りません。サービスアプリケーションはその代表的な例です。また、プログラムはコンソールウィンドウを持つことができます。


私はこれが古い答えであることを知っていますが、editbinについての厄介な点で、そのトリックの目的は、CRTにWinMain関数を適切なパラメーターにリンクさせ(したがってコンパイルして/SUBSYSTEM:WINDOWS)、事後モードを変更することであると信じていますローダーはコンソールホストを起動します。さらにフィードバックが必要な場合はCREATE_NO_WINDOW、CreateProcessでこれを試し、GetConsoleWindow() == NULL再起動したかどうかを確認しました。これはコンソールのちらつきを修正しませんが、特別なcmd引数がないことを意味します。

これは素晴らしい答えですが、完全を期すために、コンソールプログラムと「非コンソール」プログラムの主な違いを説明する価値があります(ここでの誤解は、以下の誤った答えの多くにつながるようです)。つまり、コンソールから起動されたコンソールアプリは、完了するまで親コンソールに制御を戻しませんが、GUIアプリはフォークして、すぐに戻ります。不明な場合は、DUMPBIN / headersを使用し、SUBSYSTEM行を探して、どのフレーバーを使用しているかを正確に確認できます。
piers7 2014

これは廃止されたベストアンサーです。少なくともC / C ++の観点からは。Win32については、以下のdantillのソリューションを参照してください。これは、おそらく誰かがC#に適合させることができます。
B. Nadolson

1
私はこの答えが時代遅れだとは思いません。この方法はうまく機能し、回答の評価はそれ自体を物語っています。Dantillのアプローチは、stdinをコンソールアプリから切断します。別の回答として、ケネディの「瞬間的なちらつき」アプローチのCバージョンを以下に示します(はい、私は知っています、OPはC#について投稿しました)。何度か使ってみて、とても満足しています。
willus 2015年

これはJavaで行うことができます..)
Antoniossss19年


6

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

WinFormsを使用する前に、コマンドライン引数を確認してくださいApplication.

.NETでは、メインを除くすべてのアセンブリを共有する同じソリューションでコンソールとGUIプロジェクトを作成するのは非常に簡単です。この場合、コマンドラインバージョンをパラメーターなしで起動すると、GUIバージョンを起動するだけで済みます。コンソールが点滅します。


コマンドラインパラメータの存在は、確実な発火の兆候ではありません。多くのWindowsアプリがコマンドラインパラメータを使用できます
Neil N

3
私のポイントは、何もない場合は、GUIバージョンを起動することでした。GUIバージョンをパラメーター付きで起動したい場合は、おそらくそのためのパラメーターを持つことができます。
ケイドルー

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開発マシンで正常に動作しますが、(仮想)Windows XPマシンを使用しているため、再起動されたプロセスは常にコンソールを取得するため、それ自体を再起動する無限のループで消えます。何か案は?
サイモンヒューイット

1
これには十分注意してください。WindowsXPでは、これは実際に無制限のリスポーンループにつながり、殺すのは非常に困難です。
ユーザー

3

推奨される手法は、Robが2つの実行可能ファイル(ランチャー「.com」と元の「.exe」)を使用するdevenv手法と呼んだものだと思います。ボイラープレートコードを使用する場合、これを使用するのはそれほど難しいことではありません(以下のリンクを参照)。

この手法では、トリックを使用して、「。com」をstdin / stdout / stderrのプロキシにし、同じ名前の.exeファイルを起動します。これにより、コンソールから呼び出されたときに(特定のコマンドライン引数が検出された場合にのみ)プログラムをコマンドラインモードで実行できるようにすると同時に、コンソールなしでGUIアプリケーションとして起動できるようになります。

私はGoogleCodeでdualsubsystemというプロジェクトをホストしました。このプロジェクトは、この手法の古いcodeguruソリューションを更新し、ソースコードと実用的なサンプルバイナリを提供します。


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 2015

B.ナドルソンに同意します。これは(C ++の場合)、プロセスを再起動せず、複数のEXEなしで機能します。
GravityWell 2016年

2
この方法の欠点:(1)完了時に追加のキーストロークをコンソールに送信する必要がある、(2)コンソール出力をファイルにリダイレクトできない、(3)添付のstdinでテストされていないようです(ファイルからリダイレクトすることもできないと思います)。私にとって、コンソールウィンドウが一時的に点滅するのを避けるには、それはあまりにも多くの取引です。再起動方法は、少なくとも真のデュアルコンソール/ GUIを提供します。私はそのようなアプリを何万人ものユーザーに配布しましたが、コンソールウィンドウが瞬間的に点滅することについての苦情やコメントは1つもありません。
willus 2017

0

静的コンストラクターでAllocConsole()を実行するとうまくいきます

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.