Как создать приложение на C #, которое решает, отображать ли его как консольное или оконное приложение? - PullRequest
46 голосов
/ 30 апреля 2009

Есть ли способ запустить приложение C # со следующими функциями?

  1. По параметрам командной строки определяет, является ли это оконным или консольным приложением
  2. Он не отображает консоль, когда ее просят открыть окно, и не отображает окно графического интерфейса при ее запуске из консоли.

Например,

myapp.exe /help
будет выводить на стандартный вывод на используемой вами консоли, но
myapp.exe
само по себе запустит мой пользовательский интерфейс Winforms или WPF.

Лучшие ответы, которые я знаю до сих пор, включают в себя наличие двух отдельных exe-файлов и использование IPC, но это выглядит очень странно.


Какие у меня есть варианты и какие компромиссы я могу сделать, чтобы получить поведение, описанное в примере выше? Я открыт для идей, относящихся к Winform или WPF.

Ответы [ 12 ]

57 голосов
/ 30 апреля 2009

Сделайте приложение обычным приложением для 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);

  }
}
14 голосов
/ 14 сентября 2012

Я делаю это, как показано в ответе Эрика, дополнительно отсоединяю консоль с помощью 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());

    }
7 голосов
/ 30 апреля 2009

Написать два приложения (одну консоль, одно окно), а затем написать другое приложение меньшего размера, которое на основе заданных параметров откроет одно из других приложений (а затем, предположительно, закроется само, так как оно больше не потребуется)?

5 голосов
/ 30 апреля 2009

Я сделал это, создав два отдельных приложения.

Создайте приложение WPF с таким именем: MyApp.exe. И создайте консольное приложение с таким именем: MyApp.com. При вводе имени приложения в командной строке, например, MyApp или MyApp /help (без расширения .exe), консольное приложение с расширением .com будет иметь приоритет. Вы можете заставить ваше консольное приложение вызывать MyApp.exe в соответствии с параметрами.

Именно так ведет себя devenv. Ввод devenv в командной строке запустит IDE Visual Studio. Если вы передадите параметры, такие как /build, они останутся в командной строке.

4 голосов
/ 30 апреля 2009

ПРИМЕЧАНИЕ: я не проверял это, но думаю, что это сработает ...

Вы можете сделать это:

Сделайте ваше приложение приложением Windows Forms. Если вы получили запрос на консоль, не показывайте свою основную форму. Вместо этого используйте платформу invoke для вызова функций консоли в Windows API и выделения консоли на лету.

(Альтернативно, используйте API, чтобы скрыть консоль в консольном приложении, но вы, вероятно, увидите «мерцание» консоли, как оно было создано в этом случае ...)

2 голосов
/ 30 апреля 2009

Я бы создал решение, которое представляет собой приложение Windows Form, так как есть две функции, которые вы можете вызвать, которые подключатся к текущей консоли. Таким образом, вы можете обращаться с программой как с консольной программой. или по умолчанию вы можете запустить графический интерфейс.

Функция 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 голосов
/ 30 апреля 2009

Насколько я знаю, в исполняемом файле есть флаг, который указывает, следует ли запускать как консольное или оконное приложение. Вы можете щелкнуть флаг с помощью инструментов, которые поставляются с Visual Studio, но вы не можете сделать это во время выполнения.

Если exe-файл скомпилирован как консоль, то он всегда будет открывать новую консоль, если она не запущена с одной. Если exe-приложение является приложением, оно не может выводиться на консоль. Вы можете создать отдельную консоль, но она не будет вести себя как консольное приложение.

В прошлом мы использовали 2 отдельных exe. Консоль, являющаяся тонкой оберткой над формой (вы можете ссылаться на exe, как если бы вы ссылались на dll, и вы можете использовать атрибут [assembly: InternalsVisibleTo ("cs_friend_assemblies_2")], чтобы доверять консоли, поэтому нужно выставить больше, чем нужно).

1 голос
/ 11 апреля 2015

Важно помнить, что нужно делать после 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 или без него. С выводом, отправляемым с System.Console.WriteLine или System.Console.out.WriteLine перед вызовом To AttachConsole или AllocConsole. Я включил мой метод ниже:

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 голос
/ 30 апреля 2009

Один из способов сделать это - написать приложение Window, которое не показывает окно, если аргументы командной строки указывают, что оно не должно.

Вы всегда можете получить аргументы командной строки и проверить их перед отображением первого окна.

1 голос
/ 30 апреля 2009

Может быть, эта ссылка даст некоторое представление о том, что вы хотите сделать.

...