programing

콘솔 앱으로 표시할 것인지 윈도우 앱으로 표시할 것인지를 스스로 결정하는 C# 앱을 만들려면 어떻게 해야 합니까?

newstyles 2023. 4. 19. 22:35

콘솔 앱으로 표시할 것인지 윈도우 앱으로 표시할 것인지를 스스로 결정하는 C# 앱을 만들려면 어떻게 해야 합니까?

다음 기능을 사용하여 C# 어플리케이션을 실행하는 방법이 있습니까?

  1. 명령줄 파라미터에 따라 윈도 어플리케이션인지 콘솔 어플리케이션인지를 판별합니다.
  2. 창을 열도록 요구받았을 때 콘솔이 표시되지 않으며 콘솔에서 실행 중일 때 GUI 창이 표시되지 않습니다.

예를들면,

myapp.exe / 도움말
would output to stdout on the console you used, but
myapp.exe
by itself would launch my Winforms or WPF user interface.

The best answers I know of so far involve having two separate exe and use IPC, but that feels really hacky.


위의 예에서 설명한 동작을 얻기 위해 어떤 옵션과 트레이드오프를 사용할 수 있습니까?Winform 고유의 아이디어나 WPF 고유의 아이디어도 받아들일 수 있습니다.

앱을 일반 윈도 앱으로 만들고 필요에 따라 콘솔을 즉시 만듭니다.

자세한 내용은 이 링크를 참조하십시오(아래 코드 참조).

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

  }
}

기본적으로는 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());

    }

2개의 앱(콘솔 1개, 윈도 1개)을 작성한 후 주어진 파라미터에 따라 다른 앱 중 하나를 여는 작은 앱을 작성합니다(그리고 더 이상 필요하지 않기 때문에 스스로 닫는다고 생각됨).

저는 두 개의 앱을 만들어서 이 작업을 수행했습니다.

WPF 앱을 하려면 , 다음의 이름을 사용합니다.MyApp.exe 콘솔 때 이MyApp.com앱을 이렇게 하면 앱 할 수 있습니다MyApp ★★★★★★★★★★★★★★★★★」MyApp /help).exe(내선번호 )을 합니다..com내선번호가 우선됩니다.에서 이 명령어를 할 수 .MyApp.exe매개 변수에 따라.

데벤브「 」를 합니다.devenvVisual Studio IDE입니다. paramet paramet paramet paramet paramet paramet paramet paramet paramet paramet 등의 파라미터를 /build명령줄에 남아 있습니다.

메모: 테스트해 본 적은 없습니다만, 동작할 수 있을 것 같습니다.

다음과 같이 할 수 있습니다.

앱을 Windows 폼 어플리케이션으로 만듭니다.콘솔에 대한 요청을 받은 경우 기본 양식을 표시하지 마십시오.대신 플랫폼 호출을 사용하여 Windows API의 콘솔 기능을 호출하고 콘솔을 즉시 할당합니다.

(또한 API를 사용하여 콘솔 앱에서 콘솔을 숨깁니다만, 이 경우 콘솔이 작성되었을 때 콘솔이 "깜빡"하게 됩니다.)

제가 알기로는 exe에 콘솔 또는 윈도우 앱 중 어느 것으로 실행할지 알려주는 플래그가 있습니다.Visual Studio와 함께 제공되는 도구를 사용하여 플래그를 깜박일 수 있지만 런타임에는 이 작업을 수행할 수 없습니다.

exe가 콘솔로 컴파일된 경우 콘솔이 시작되지 않으면 항상 새 콘솔이 열립니다.exe가 응용 프로그램인 경우 콘솔에 출력할 수 없습니다.별도의 콘솔을 생성할 수 있지만 콘솔 앱처럼 작동하지 않습니다.

이전에는 2개의 exe를 사용했습니다.콘솔은 폼 위에 얇은 래퍼입니다(dll을 참조하듯이 exe를 참조할 수 있으며 [assembly:InternalsVisibleTo("cs_friend_assemblys_2"]) 속성을 사용하여 콘솔 속성을 신뢰하므로 필요 이상으로 노출할 필요가 없습니다.

현재 콘솔에 연결할 수 있는 두 가지 기능이 있기 때문에 Windows Form App이라는 솔루션을 만듭니다.따라서 프로그램을 콘솔 프로그램처럼 취급할 수 있습니다.또는 디폴트로 GUI 를 기동할 수 있습니다.

Attach Console 함수는 새 콘솔을 생성하지 않습니다.Attach Console에 대한 자세한 내용은 PInvoke를 참조하십시오. Attach Console(첨부 콘솔)

다음은 사용 방법의 샘플 프로그램입니다.

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

이를 위한 한 가지 방법은 명령줄 인수에 창이 표시되지 않아야 하는 경우 창이 표시되지 않는 Window 앱을 작성하는 것입니다.

명령줄 인수를 항상 가져와 첫 번째 창을 표시하기 전에 확인할 수 있습니다.

후에 꼭 할 한 일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번은 쉬워요.

2개는 불가능할 것 같아. 난 그렇게 생각하지 않아.

의사는 다음과 같이 말합니다.

Write 및 WriteLine 등의 메서드에 대한 호출은 Windows 응용 프로그램에서는 효과가 없습니다.

시스템콘솔 클래스는 콘솔 및 GUI 응용 프로그램에서 다르게 초기화됩니다.이를 확인하려면 각 응용 프로그램 유형의 디버거에 있는 콘솔 클래스를 확인합니다.다시 초기화할 방법이 있을지 모르겠네요.

데모: 새 Windows Forms 앱을 만든 후 기본 메서드를 다음과 같이 바꿉니다.

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

이 개념에서는 명령줄 파라미터가 콘솔에 출력되어 종료됩니다.인수 없이 실행하면 창이 나타납니다.그러나 명령줄 인수를 사용하여 실행하면 아무 일도 일어나지 않습니다.

그런 다음 프로젝트 속성을 선택하고 프로젝트 유형을 "콘솔 응용 프로그램"으로 변경한 후 다시 컴파일하십시오.인수와 함께 실행하면 원하는 대로 "콘솔!"이 표시됩니다.(커맨드 라인에서) 인수를 지정하지 않고 실행하면 창이 나타납니다.그러나 프로그램을 종료할 때까지 명령 프롬프트는 돌아오지 않습니다.탐색기에서 프로그램을 실행하면 명령 창이 열리고 창이 나타납니다.

stdin을 사용하는 것도 포함해서 방법을 찾아냈지만, 이것은 예쁘지 않다는 것을 경고해야 합니다.

연결된 콘솔에서 stdin을 사용할 때의 문제는 셸에서도 stdin을 읽을 수 있다는 것입니다.이로 인해 입력이 앱으로 이동하기도 하지만 셸로 이동하기도 합니다.

해결책은 앱 수명 동안 셸을 차단하는 것입니다(기술적으로는 입력이 필요한 경우에만 차단할 수 있습니다).이를 위해 셸에 키 스트로크를 전송하여 앱이 종료되기를 기다리는 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();
    }

언급URL : https://stackoverflow.com/questions/807998/how-do-i-create-a-c-sharp-app-that-decides-itself-whether-to-show-as-a-console-o