web-dev-qa-db-ja.com

コンソールまたはウィンドウアプリのどちらとして表示するかを決定するC#アプリを作成するにはどうすればよいですか?

次の機能を備えたC#アプリケーションを起動する方法はありますか?

  1. コマンドラインパラメータによって、ウィンドウアプリかコンソールアプリかを判断します
  2. ウィンドウ化を要求されたときにコンソールを表示せず、コンソールから実行しているときにGUIウィンドウを表示しません。

例えば、

myapp.exe/help
myapp.exe



上記の例で説明されている動作を実現するために、どのようなオプションとトレードオフを行うことができますか? Winform固有またはWPF固有のアイデアも受け入れています。

46
Matthew

アプリを通常のWindowsアプリにし、必要に応じてその場でコンソールを作成します。

詳細については このリンク (そこから下のコード)

using System;
using System.Windows.Forms;

namespace WindowsApplication1 {
  static class Program {
    [STAThread]
    static void Main(string[] args) {
      if (args.Length > 0) {
        // Command line given, display console
        if ( !AttachConsole(-1) ) { // Attach to an parent process console
           AllocConsole(); // Alloc a new console
        }

        ConsoleMain(args);
      }
      else {
        Application.EnableVisualStyles();
        Application.SetCompatibleTextRenderingDefault(false);
        Application.Run(new Form1());
      }
    }
    private static void ConsoleMain(string[] args) {
      Console.WriteLine("Command line = {0}", Environment.CommandLine);
      for (int ix = 0; ix < args.Length; ++ix)
        Console.WriteLine("Argument{0} = {1}", ix + 1, args[ix]);
      Console.ReadLine();
    }

    [System.Runtime.InteropServices.DllImport("kernel32.dll")]
    private static extern bool AllocConsole();

    [System.Runtime.InteropServices.DllImport("kernel32.dll")]
    private static extern bool AttachConsole(int pid);

  }
}
57
Eric Petroelje

基本的には、Ericの回答に示されている方法で行います。さらに、FreeConsoleを使用してコンソールをデタッチし、SendKeysコマンドを使用してコマンドプロンプトを元に戻します。

    [DllImport("kernel32.dll")]
    private static extern bool AllocConsole();

    [DllImport("kernel32.dll")]
    private static extern bool AttachConsole(int pid);

    [DllImport("kernel32.dll", SetLastError = true)]
    private static extern bool FreeConsole();

    [STAThread]
    static void Main(string[] args)
    {
        if (args.Length > 0 && (args[0].Equals("/?") || args[0].Equals("/help", StringComparison.OrdinalIgnoreCase)))
        {
            // get console output
            if (!AttachConsole(-1))
                AllocConsole();

            ShowHelp(); // show help output with Console.WriteLine
            FreeConsole(); // detach console

            // get command Prompt back
            System.Windows.Forms.SendKeys.SendWait("{ENTER}"); 

            return;
        }

        // normal winforms code
        Application.EnableVisualStyles();
        Application.SetCompatibleTextRenderingDefault(false);
        Application.Run(new MainForm());

    }
14
Mike Fuchs

私は2つの別々のアプリを作成することによってこれを行いました。

MyApp.exeという名前でWPFアプリを作成します。そして、次の名前でコンソールアプリを作成します:MyApp.com。このMyAppまたはMyApp /help.exe拡張子なし)のようにコマンドラインにアプリ名を入力すると、.com拡張子の付いたコンソールアプリが優先されます。パラメータに応じて、コンソールアプリケーションにMyApp.exeを呼び出させることができます。

これはまさにdevenvの動作です。コマンドラインでdevenvと入力すると、VisualStudioのIDEが起動します。 /buildのようなパラメーターを渡すと、コマンドラインに残ります。

5
Anthony Brien

注:私はこれをテストしていませんが、うまくいくと思います...

あなたはこれを行うことができます:

アプリをWindowsフォームアプリケーションにします。コンソールのリクエストを受け取った場合は、メインフォームを表示しないでください。代わりに、platforminvokeを使用してWindowsAPIの Console Functions を呼び出し、その場でコンソールを割り当てます。

(または、APIを使用してコンソールアプリでコンソールを非表示にしますが、この場合に作成されたコンソールの「ちらつき」が表示される可能性があります...)

4
Reed Copsey

現在のコンソールにフックする2つの関数を呼び出すことができるため、Windowsフォームアプリであるソリューションを作成します。したがって、プログラムをコンソールプログラムのように扱うことができます。または、デフォルトでGUIを起動できます。

AttachConsole関数は、新しいコンソールを作成しません。 AttachConsoleの詳細については、 PInvoke:AttachConsole を確認してください。

それを使用する方法のサンプルプログラムの下。

using System.Runtime.InteropServices;

namespace Test
{
    /// <summary>
    /// This function will attach to the console given a specific ProcessID for that Console, or
    /// the program will attach to the console it was launched if -1 is passed in.
    /// </summary>
    [DllImport("kernel32.dll", SetLastError = true)]
    private static extern bool AttachConsole(int dwProcessId);

    [DllImport("kernel32.dll", SetLastError = true)]
    private static extern bool FreeConsole();


    [STAThread]
    public static void Main() 
    {   
        Application.ApplicationExit +=new EventHandler(Application_ApplicationExit);
        string[] commandLineArgs = System.Environment.GetCommandLineArgs();

        if(commandLineArgs[0] == "-cmd")
        {
            //attaches the program to the running console to map the output
            AttachConsole(-1);
        }
        else
        {
            //Open new form and do UI stuff
            Form f = new Form();
            f.ShowDialog();
        }

    }

    /// <summary>
    /// Handles the cleaning up of resources after the application has been closed
    /// </summary>
    /// <param name="sender"></param>
    public static void Application_ApplicationExit(object sender, System.EventArgs e)
    {
        FreeConsole();
    }
}
2
Richard R

私の知る限り、exeには、コンソールとして実行するかウィンドウ化されたアプリとして実行するかを示すフラグがあります。 Visual Studioに付属のツールを使用して旗をはじくことができますが、実行時にこれを行うことはできません。

Exeがコンソールとしてコンパイルされている場合、コンソールから起動されていなければ、常に新しいコンソールが開きます。 exeファイルがアプリケーションの場合、コンソールに出力できません。別のコンソールを生成できますが、コンソールアプリのようには動作しません。

私は過去に2つの別々のexeファイルを使用しました。コンソールのものはフォームのものの薄いラッパーです(dllを参照するのと同じようにexeを参照でき、[Assembly:InternalsVisibleTo( "cs_friend_assemblies_2")]属性を使用してコンソールのものを信頼できるので、必要以上に公開する必要があります)。

2
Colin

AttachConsole()またはAllocConsole()を呼び出した後、すべての場合にそれを機能させるために覚えておくべき重要なことは次のとおりです。

if (AttachConsole(ATTACH_PARENT_PROCESS))
  {
    System.IO.StreamWriter sw =
      new System.IO.StreamWriter(System.Console.OpenStandardOutput());
    sw.AutoFlush = true;
    System.Console.SetOut(sw);
    System.Console.SetError(sw);
  }

私はそれがVSホスティングプロセスの有無にかかわらず機能することを発見しました。 AttachConsoleまたはAllocConsoleを呼び出す前に、出力がSystem.Console.WriteLineまたはSystem.Console.out.WriteLineで送信されます。私は以下に私の方法を含めました:

public static bool DoConsoleSetep(bool ClearLineIfParentConsole)
{
  if (GetConsoleWindow() != System.IntPtr.Zero)
  {
    return true;
  }
  if (AttachConsole(ATTACH_PARENT_PROCESS))
  {
    System.IO.StreamWriter sw = new System.IO.StreamWriter(System.Console.OpenStandardOutput());
    sw.AutoFlush = true;
    System.Console.SetOut(sw);
    System.Console.SetError(sw);
    ConsoleSetupWasParentConsole = true;
    if (ClearLineIfParentConsole)
    {
      // Clear command Prompt since windows thinks we are a windowing app
      System.Console.CursorLeft = 0;
      char[] bl = System.Linq.Enumerable.ToArray<char>(System.Linq.Enumerable.Repeat<char>(' ', System.Console.WindowWidth - 1));
      System.Console.Write(bl);
      System.Console.CursorLeft = 0;
    }
    return true;
  }
  int Error = System.Runtime.InteropServices.Marshal.GetLastWin32Error();
  if (Error == ERROR_ACCESS_DENIED)
  {
    if (log.IsDebugEnabled) log.Debug("AttachConsole(ATTACH_PARENT_PROCESS) returned ERROR_ACCESS_DENIED");
    return true;
  }
  if (Error == ERROR_INVALID_HANDLE)
  {
    if (AllocConsole())
    {
      System.IO.StreamWriter sw = new System.IO.StreamWriter(System.Console.OpenStandardOutput());
      sw.AutoFlush = true;
      System.Console.SetOut(sw);
      System.Console.SetError(sw);
      return true;
    }
  }
  return false;
}

また、出力の実行が終了したときにコマンドプロンプトを再表示する必要がある場合に備えて、完了時にこれを呼び出しました。

public static void SendConsoleInputCR(bool UseConsoleSetupWasParentConsole)
{
  if (UseConsoleSetupWasParentConsole && !ConsoleSetupWasParentConsole)
  {
    return;
  }
  long LongNegOne = -1;
  System.IntPtr NegOne = new System.IntPtr(LongNegOne);
  System.IntPtr StdIn = GetStdHandle(STD_INPUT_HANDLE);
  if (StdIn == NegOne)
  {
    return;
  }
  INPUT_RECORD[] ira = new INPUT_RECORD[2];
  ira[0].EventType = KEY_EVENT;
  ira[0].KeyEvent.bKeyDown = true;
  ira[0].KeyEvent.wRepeatCount = 1;
  ira[0].KeyEvent.wVirtualKeyCode = 0;
  ira[0].KeyEvent.wVirtualScanCode = 0;
  ira[0].KeyEvent.UnicodeChar = '\r';
  ira[0].KeyEvent.dwControlKeyState = 0;
  ira[1].EventType = KEY_EVENT;
  ira[1].KeyEvent.bKeyDown = false;
  ira[1].KeyEvent.wRepeatCount = 1;
  ira[1].KeyEvent.wVirtualKeyCode = 0;
  ira[1].KeyEvent.wVirtualScanCode = 0;
  ira[1].KeyEvent.UnicodeChar = '\r';
  ira[1].KeyEvent.dwControlKeyState = 0;
  uint recs = 2;
  uint zero = 0;
  WriteConsoleInput(StdIn, ira, recs, out zero);
}

お役に立てれば...

多分これ リンク はあなたが何をしようとしているのかについての洞察を提供するでしょう。

1

これを行う1つの方法は、コマンドライン引数でウィンドウが表示されるべきではないことを示している場合にウィンドウを表示しないウィンドウアプリを作成することです。

最初のウィンドウを表示する前に、いつでもコマンドライン引数を取得して確認できます。

1
Arnshea

Stdinを使用するなど、これを行う方法を考え出しましたが、きれいではないことを警告する必要があります。

接続されたコンソールからstdinを使用する場合の問題は、シェルがstdinからも読み取ることです。これにより、入力がアプリに送られることもあれば、シェルに送られることもあります。

解決策は、アプリの存続期間中シェルをブロックすることです(ただし、技術的には、入力が必要な場合にのみシェルをブロックすることができます)。これを行う方法は、キーストロークをシェルに送信して、アプリの終了を待機するPowerShellコマンドを実行することです。

ちなみに、これにより、アプリの終了後にプロンプ​​トが戻らないという問題も修正されます。

私は、PowerShellコンソールからもそれを機能させることを簡単に試みました。同じ原則が適用されますが、コマンドを実行することができませんでした。 PowerShellには、他のアプリケーションからのコマンドの実行を防ぐためのセキュリティチェックがいくつかある可能性があります。私はPowerShellをあまり使用しないため、調査しませんでした。

    [DllImport("kernel32.dll", SetLastError = true)]
    private static extern bool AllocConsole();

    [DllImport("kernel32", SetLastError = true)]
    private static extern bool AttachConsole(int dwProcessId);

    private const uint STD_INPUT_HANDLE = 0xfffffff6;
    private const uint STD_OUTPUT_HANDLE = 0xfffffff5;
    private const uint STD_ERROR_HANDLE = 0xfffffff4;

    [DllImport("kernel32.dll")]
    private static extern IntPtr GetStdHandle(uint nStdHandle);
    [DllImport("Kernel32.dll", SetLastError = true)]
    public static extern int SetStdHandle(uint nStdHandle, IntPtr handle);

    [DllImport("kernel32.dll", SetLastError = true)]
    private static extern int GetConsoleProcessList(int[] ProcessList, int ProcessCount);

    [DllImport("user32.dll")]
    public static extern IntPtr SendMessage(IntPtr hWnd, uint Msg, IntPtr wParam, IntPtr lParam);
    [DllImport("user32.dll")]
    public static extern IntPtr PostMessage(IntPtr hWnd, uint Msg, IntPtr wParam, IntPtr lParam);

    /// <summary>
    /// Attach to existing console or create new. Must be called before using System.Console.
    /// </summary>
    /// <returns>Return true if console exists or is created.</returns>
    public static bool InitConsole(bool createConsole = false, bool suspendHost = true) {

        // first try to attach to an existing console
        if (AttachConsole(-1)) {
            if (suspendHost) {
                // to suspend the Host first try to find the parent
                var processes = GetConsoleProcessList();

                Process Host = null;
                string blockingCommand = null;

                foreach (var proc in processes) {
                    var netproc = Process.GetProcessById(proc);
                    var processName = netproc.ProcessName;
                    Console.WriteLine(processName);
                    if (processName.Equals("cmd", StringComparison.OrdinalIgnoreCase)) {
                        Host = netproc;
                        blockingCommand = $"powershell \"& wait-process -id {Process.GetCurrentProcess().Id}\"";
                    } else if (processName.Equals("powershell", StringComparison.OrdinalIgnoreCase)) {
                        Host = netproc;
                        blockingCommand = $"wait-process -id {Process.GetCurrentProcess().Id}";
                    }
                }

                if (Host != null) {
                    // if a parent is found send keystrokes to simulate a command
                    var cmdWindow = Host.MainWindowHandle;
                    if (cmdWindow == IntPtr.Zero) Console.WriteLine("Main Window null");

                    foreach (char key in blockingCommand) {
                        SendChar(cmdWindow, key);
                        System.Threading.Thread.Sleep(1); // required for powershell
                    }

                    SendKeyDown(cmdWindow, Keys.Enter);

                    // i haven't worked out how to get powershell to accept a command, it might be that this is a security feature of powershell
                    if (Host.ProcessName == "powershell") Console.WriteLine("\r\n *** PRESS ENTER ***");
                }
            }

            return true;
        } else if (createConsole) {
            return AllocConsole();
        } else {
            return false;
        }
    }

    private static void SendChar(IntPtr cmdWindow, char k) {
        const uint WM_CHAR = 0x0102;

        IntPtr result = PostMessage(cmdWindow, WM_CHAR, ((IntPtr)k), IntPtr.Zero);
    }

    private static void SendKeyDown(IntPtr cmdWindow, Keys k) {
        const uint WM_KEYDOWN = 0x100;
        const uint WM_KEYUP = 0x101;

        IntPtr result = SendMessage(cmdWindow, WM_KEYDOWN, ((IntPtr)k), IntPtr.Zero);
        System.Threading.Thread.Sleep(1);
        IntPtr result2 = SendMessage(cmdWindow, WM_KEYUP, ((IntPtr)k), IntPtr.Zero);
    }

    public static int[] GetConsoleProcessList() {
        int processCount = 16;
        int[] processList = new int[processCount];

        // supposedly calling it with null/zero should return the count but it didn't work for me at the time
        // limiting it to a fixed number if fine for now
        processCount = GetConsoleProcessList(processList, processCount);
        if (processCount <= 0 || processCount >= processList.Length) return null; // some sanity checks

        return processList.Take(processCount).ToArray();
    }
0
Herman

いいえ1は簡単です。

いいえ2はできないと思います。

ドキュメントによると:

WriteやWriteLineなどのメソッドの呼び出しは、Windowsアプリケーションでは効果がありません。

System.Consoleクラスは、コンソールアプリケーションとGUIアプリケーションで異なる方法で初期化されます。これは、各アプリケーションタイプのデバッガーでコンソールクラスを確認することで確認できます。再初期化する方法があるかどうかわからない。

デモ:新しいWindowsフォームアプリを作成し、Mainメソッドを次のように置き換えます。

    static void Main(string[] args)
    {
        if (args.Length == 0)
        {
            Application.EnableVisualStyles();
            Application.SetCompatibleTextRenderingDefault(false);
            Application.Run(new Form1());
        }
        else
        {
            Console.WriteLine("Console!\r\n");
        }
    }

コマンドラインパラメータがコンソールに出力されて終了するという考え方です。引数なしで実行すると、ウィンドウが表示されます。ただし、コマンドライン引数を指定して実行すると、何も起こりません。

次に、プロジェクトのプロパティを選択し、プロジェクトの種類を「コンソールアプリケーション」に変更して、再コンパイルします。これで、引数を指定して実行すると、「コンソール」が表示されます。あなたが望むように。そして、引数なしで(コマンドラインから)実行すると、ウィンドウが表示されます。ただし、コマンドプロンプトは、プログラムを終了するまで戻りません。また、エクスプローラーからプログラムを実行すると、コマンドウィンドウが開き、ウィンドウが表示されます。

0
user71950