Microsoft MVP성태의 닷넷 이야기
글쓴 사람
정성태 (seongtaejeong at gmail.com)
홈페이지
첨부 파일

(시리즈 글이 2개 있습니다.)
Windows: 274. Windows 7부터 도입한 conhost.exe
; https://www.sysnet.pe.kr/2/0/13832

Windows: 275. C# - CUI 애플리케이션과 Console 윈도우 (Windows 10 미만의 Classic Console 모드인 경우)
; https://www.sysnet.pe.kr/2/0/13834




C# - CUI 애플리케이션과 Console 윈도우 (Windows 10 미만의 Classic Console 모드인 경우)

이번 글은 Windows 7과 2012 R2 환경에서 테스트했지만, Windows 10의 새로운 터미널 방식이 아닌, 레거시(?) 환경에 모두 적용될 수 있습니다.




Console App은 비록 (직접 코드 내에서) 윈도우 자원을 생성하지는 않지만, 결국 그 화면은 윈도우입니다.

console_win7_1.png

실제로 Console App에서 저 윈도우의 핸들 값을 Win32 API를 이용해 구할 수도 있는데요,

GetConsoleWindow function
; https://learn.microsoft.com/en-us/windows/console/getconsolewindow

닷넷의 경우 p/invoke로 다음과 같이 코딩할 수 있습니다.

using System.Runtime.InteropServices;

internal class Program
{
    [DllImport("kernel32.dll")]
    static extern uint GetCurrentThreadId();

    [DllImport("kernel32.dll")]
    static extern IntPtr GetConsoleWindow();

    static void Main(string[] args)
    {
        IntPtr hWnd = GetConsoleWindow();
        int pid = Environment.ProcessId;
        uint tid = GetCurrentThreadId();

        Console.WriteLine($"Process ID: {pid} (0x{pid:x})");
        Console.WriteLine($"Thread ID: {tid} (0x{tid:x})");
        Console.WriteLine($"HWND: {hWnd} (0x{hWnd:x})");

        Console.WriteLine($"{Environment.NewLine}Press any key to exit...");
        Console.ReadLine();
    }
}

빌드 후, 윈도우 탐색기에서 EXE 파일을 직접 더블 클릭해 실행하면 아래와 같은 유형의 출력 결과가 나올 텐데요,

Process ID: 1460 (0x5b4)
Thread ID: 3492 (0xda4)
HWND: 1573526 (0x180296)

Press any key to exit...

이 상태에서 Spy++을 실행해 윈도우와 그것을 생성한 Process ID, Thread ID를 살펴보면,

console_win7_2.png

위와 같이 "Console" 창이 곧 ConsoleApp3.exe 프로세스에서 생성한 윈도우임을 확인할 수 있습니다.




Console 프로그램(위의 경우 ConsoleApp3.exe)을 만약 탐색기가 아닌, 미리 실행해 둔 cmd.exe 명령행 창에서 실행하면 어떻게 될까요?

엄밀히 말해서 cmd.exe도, 우리가 만든 ConsoleApp3.exe도 모두 "Windows CUI" 유형에 속합니다.

c:\temp> dumpbin /HEADERS c:\windows\system32\cmd.exe

...[생략]...
OPTIONAL HEADER VALUES
             20B magic # (PE32+)
           ...[생략]...
           5360A checksum
               3 subsystem (Windows CUI)
...[생략]...

즉, cmd.exe를 실행하면 위에서 살펴 본 ConsoleApp3.exe 실행과 동일하게 그 프로세스 내에 Console을 위한 윈도우가 할당됩니다. 그리고, 바로 그 cmd.exe 창 내에서 ConsoleApp3.exe를 실행하면,

console_win7_3.png

[Windows 7에서 실행하는 경우]

csrss.exe
  ㄴ conhost.exe

cmd.exe (PID: 416(0x1a0)) 
  |- 윈도우 - 0x380278 (ClassName: ConsoleWindowClass)
  ㄴ ConsoleApp3.exe (PID: 3532(0xdcc))

ConsoleApp3 코드에서 실행한 GetConsoleWindow 함수는 부모 프로세스(위의 경우 cmd.exe)가 생성한 Console 창의 윈도우 핸들(0x380278)을 반환합니다. 또한 저 속성 창을 보면 윈도우 위치와 크기를 나타내는 Rectangle 정보가 현재 Console Window의 위치와 일치하는 것을 확인할 수 있습니다.

그런데 과연 ConsoleApp3.exe는 어떻게 부모 윈도우의 콘솔 윈도우와 I/O 연동을 할 수 있을까요? 간단합니다. cmd.exe는 표준 입력, 표준 출력, 표준 에러 출력을 ConsoleApp3.exe로 상속해 주기 때문입니다.

정말 그런지 직접 확인을 해볼까요? ^^ 우선, 콘솔 앱은 자신에게 할당된 표준 I/O에 대한 핸들 값을 이렇게 구할 수 있습니다.

const int STD_INPUT_HANDLE = -10;
const int STD_OUTPUT_HANDLE = -11;
const int STD_ERROR_HANDLE = -12;

[DllImport("kernel32.dll", SetLastError = true)]
static extern IntPtr GetStdHandle(int nStdHandle);

private static void PrintStandardHandle()
{
    PrintHandle("StandardOutputHandle", STD_INPUT_HANDLE);
    PrintHandle("StandardOutputHandle", STD_OUTPUT_HANDLE);
    PrintHandle("StandardOutputHandle", STD_ERROR_HANDLE);
}

static void PrintHandle(string title, int handleType)
{
    IntPtr pHandle = GetStdHandle(handleType);
    Console.WriteLine($"{title}: {pHandle} (0x{pHandle:x})");   
}

(아래에서 테스트할 코드로 인해 Windows 7이 아닌) Windows 2012 R2 환경에서 코드를 실행하니 이런 결과가 나옵니다.

StandardInputHandle : 28 (0x1c)
StandardOutputHandle: 32 (0x20)
StandardErrortHandle: 36 (0x24)

이제 저 핸들이 상속 유형인지 알아내면 되는데요, 이를 위해 예전에 만들어 둔 윈도우 핸들을 열거하는 코드를 사용해,

C# - 프로세스의 모든 핸들을 열람 - 세 번째 이야기
; https://www.sysnet.pe.kr/2/0/12195

이렇게 코드를 추가할 수 있습니다.

// Install-Package KernelStructOffset

int processId = Environment.ProcessId;

using (ProcessHandleInfo phi = new ProcessHandleInfo(processId)) // 이 코드에서 사용하는 NtQueryInformationProcess API가 Windows 8/2012 R2 이상에서만 동작
{
    for (int i = 0; i < phi.HandleCount; i++)
    {
        _PROCESS_HANDLE_TABLE_ENTRY_INFO phe = phi[i];

        // PROCESS_HANDLE_TABLE_ENTRY_INFO - NtDoc
        // ; https://ntdoc.m417z.com/process_handle_table_entry_info
        // HandleAttributes
        // A bit mask containing attributes of the handle/object:
        // 
        // OBJ_PROTECT_CLOSE - the handle is protected from closing.
        // OBJ_INHERIT - the handle is inheritable.
        // OBJ_PERMANENT - object has permanent lifetime.
        // OBJ_EXCLUSIVE - the handle/object is exclusive and prevents other handles from being open to the object.

        if ((phe.HandleAttributes & OBJ_INHERIT) != OBJ_INHERIT)
        {
            continue;
        }

        string objName = phe.GetName(processId, out string handleTypeName);
        if (string.IsNullOrEmpty(handleTypeName) == true)
        {
            continue;
        }

        if (string.IsNullOrEmpty(objName) == true)
        {
            continue;
        }

        Console.WriteLine($"{phe.HandleValue} {handleTypeName} {objName}");
    }
}

위의 코드는 현재 프로세스의 모든 핸들을 열거한 후 OBJ_INHERIT 플래그가 설정된 것만 출력합니다. 그래서 이걸 실행하면 이제 다음과 같은 출력을 얻을 수 있습니다.

StandardInputHandle : 28 (0x1c)
StandardOutputHandle: 32 (0x20)
StandardErrortHandle: 36 (0x24)

28 File \Device\ConDrv // == 상속받은 핸들만 출력
32 File \Device\ConDrv
36 File \Device\ConDrv

보는 바와 같이 동일한 핸들에 대해, 즉, 표준 입력, 표준 출력, 표준 에러 출력에 대해 상속 플래그가 존재합니다. 그렇다면, 이 프로세스에서 자식으로 또다시 Console Application을 실행하면 어떻게 될까요?

역시나 그 자식 프로세스도 부모로부터 표준 I/O 및 에러 출력 핸들을 상속받게 되므로 위와 동일한 결과가 나올 것입니다. 그리고 이 테스트를 실제로 다음과 같이 할 수 있습니다.

using KernelStructOffset;
using System.Diagnostics;
using System.Runtime.InteropServices;

namespace ConsoleApp2;

internal class Program
{
    const int STD_INPUT_HANDLE = -10;
    const int STD_OUTPUT_HANDLE = -11;
    const int STD_ERROR_HANDLE = -12;

    public const uint OBJ_INHERIT = 0x00000002;

    [DllImport("kernel32.dll", SetLastError = true)]
    static extern IntPtr GetStdHandle(int nStdHandle);

    static void Main(string[] args)
    {
        PrintStandardHandle();
        Console.WriteLine();

        int processId = Environment.ProcessId;

        using (ProcessHandleInfo phi = new ProcessHandleInfo(processId))
        {
            for (int i = 0; i < phi.HandleCount; i++)
            {
                _PROCESS_HANDLE_TABLE_ENTRY_INFO phe = phi[i];

                if ((phe.HandleAttributes & OBJ_INHERIT) != OBJ_INHERIT)
                {
                    continue;
                }

                string objName = phe.GetName(processId, out string handleTypeName);
                if (string.IsNullOrEmpty(handleTypeName) == true)
                {
                    continue;
                }

                if (string.IsNullOrEmpty(objName) == true)
                {
                    continue;
                }

                Console.WriteLine($"{phe.HandleValue} {handleTypeName} {objName}");
            }
        }

        if (args.Length > 0 && args[0] == "/child")
        {
            return;
        }

        Console.WriteLine("----------------------");

        ProcessStartInfo psi = new ProcessStartInfo();
        psi.FileName = Process.GetCurrentProcess().ProcessName;
        psi.Arguments = "/child";
        psi.UseShellExecute = false;
        Process.Start(psi)?.WaitForExit();

        Console.WriteLine($"{Environment.NewLine}Press any key to exit...");
        Console.ReadLine();
    }

    private static void PrintStandardHandle()
    {
        PrintHandle("StandardInputHandle ", STD_INPUT_HANDLE);
        PrintHandle("StandardOutputHandle", STD_OUTPUT_HANDLE);
        PrintHandle("StandardErrortHandle", STD_ERROR_HANDLE);
    }

    static void PrintHandle(string title, int handleType)
    {
        IntPtr pHandle = GetStdHandle(handleType);
        Console.WriteLine($"{title}: {pHandle} (0x{pHandle:x})");   
    }
}

위의 코드를 실행하면, 이렇게 출력이 나옵니다.

StandardOutputHandle: 28 (0x1c)     // == 부모 프로세스의 출력
StandardOutputHandle: 32 (0x20)
StandardOutputHandle: 36 (0x24)

28 File \Device\ConDrv
32 File \Device\ConDrv
36 File \Device\ConDrv
----------------------
StandardOutputHandle: 28 (0x1c)     // == 자식 프로세스의 출력
StandardOutputHandle: 32 (0x20)
StandardOutputHandle: 36 (0x24)

28 File \Device\ConDrv
32 File \Device\ConDrv
36 File \Device\ConDrv

동일한 핸들이 상속되면서 콘솔 타입의 프로그램들은 같은 콘솔 창의 표준 입출력을 공유하는 것입니다. 그렇기 때문에, 원한다면 해당 핸들을 상속하지 않음으로써, 설령 자식 프로세스가 콘솔 타입이라고 해도 화면 출력을 막는 것이 가능합니다. 위의 예제에서는 이러한 테스트를 다음과 같이 할 수 있습니다.

public const uint HANDLE_FLAG_INHERIT = 0x01;

[DllImport("kernel32.dll", SetLastError = true)]
static extern bool SetHandleInformation(IntPtr hObject, uint dwMask, uint dwFlags);

// ...[생략]...

Console.WriteLine("----------------------");

SetHandleInformation(GetStdHandle(STD_INPUT_HANDLE), HANDLE_FLAG_INHERIT, 0); // 상속 금지
SetHandleInformation(GetStdHandle(STD_OUTPUT_HANDLE), HANDLE_FLAG_INHERIT, 0);
SetHandleInformation(GetStdHandle(STD_ERROR_HANDLE), HANDLE_FLAG_INHERIT, 0);

// ...[생략]...

다시 실행하면, 자식 프로세스의 출력 정보가 나오지 않게 됩니다.

StandardInputHandle : 28 (0x1c)
StandardOutputHandle: 32 (0x20)
StandardErrortHandle: 36 (0x24)

28 File \Device\ConDrv
32 File \Device\ConDrv
36 File \Device\ConDrv
----------------------  // 자식 프로세스의 출력이 없음

참고로, Windows Forms/WPF 유형이라면 어떨까요? 이런 프로그램들은 모듈의 헤더에 Windows GUI라고 이미 설정돼 있어,

c:\temp> dumpbin /headers WinFormsApp1.exe
...[생략]...

OPTIONAL HEADER VALUES
...[생략]...
               2 subsystem (Windows GUI)
...[생략]...

윈도우 운영체제는 해당 앱들의 경우 실행 시 Console 연결을 하지 않습니다. 그래서 표준 입/출력, 에러 출력도 없고,

// Windows Forms에서 실행한 경우

IntPtr h1 = GetStdHandle(STD_INPUT_HANDLE); // h1 == IntPtr.Zero
IntPtr h2 = GetStdHandle(STD_OUTPUT_HANDLE); // h2 == IntPtr.Zero
IntPtr h3 = GetStdHandle(STD_ERROR_HANDLE); // h3 == IntPtr.Zero

당연히 그 핸들의 상속도 없습니다. 그러니까, GUI 유형의 앱들은 스스로 윈도우를 생성하고 그려야만 하는 것입니다. 대신 WPF/Windows Forms 유형의 앱들도 AllocConsole API를 이용하면 별도의 콘솔 윈도우를 연결할 수는 있습니다.

AllocConsole function
; https://learn.microsoft.com/en-us/windows/console/allocconsole

위의 API 문서에도 이와 관련된 설명이 나옵니다.

This function is primarily used by a graphical user interface (GUI) application to create a console window. GUI applications are initialized without a console. Console applications are initialized with a console, unless they are created as detached processes (by calling the CreateProcess function with the DETACHED_PROCESS flag).





콘솔의 이런 특징 때문에 재미있는, 때로는 약간 혼란스러운 동작을 보게 됩니다. 일례로, Visual Studio에서 Console App을 실행할 때가 그런 경우입니다.

Visual Studio의 Ctrl + F5 실행 동작
; https://www.sysnet.pe.kr/2/0/10848

예전에는 위의 글처럼, F5와 Ctrl+F5 간의 동작이 달랐는데요, 근래의 Visual Studio 2022에서는 2가지 실행 모두 동일하게 닫히지 않는 유형으로 바뀌었습니다. 그래도 약간의 차이가 있다면, Ctrl+F5 실행 시에는 exit code만 출력해 주는 반면,

console_win7_4.png

Hello World!

C:\temp\ConsoleApplication1\x64\Debug\ConsoleApplication1.exe (process 52988) exited with code 0.
Press any key to close this window . . .

F5 디버깅으로 종료한 경우에는 옵션을 통해 바꿀 수 있다는 문구까지 추가합니다.

Hello World!

C:\temp\ConsoleApplication1\x64\Debug\ConsoleApplication1.exe (process 53820) exited with code 0.
To automatically close the console when debugging stops, enable Tools->Options->Debugging->Automatically close the console when debugging stops.
Press any key to close this window . . .

이 외에도, 눈에 띄지 않게 달라진 점이 하나 있는데요, 예전에는 cmd.exe를 부모로 콘솔 응용 프로그램을 실행했지만,

"C:\WINDOWS\system32\cmd.exe" /c ""C:\ConsoleApplication1\bin\Debug\ConsoleApplication1.exe"  & pause""

cmd.exe
   |- conhost.exe (Windows 2012 R2 이후의 환경)
   ㄴ ConsoleApp1.exe

지금의 Visual Studio 2022는 (제가 테스트한 Windows 11 환경에서) 그 부모를 VsDebugConsole.exe 전용으로 바꾼다는 점입니다.

C:\Program Files\Microsoft Visual Studio\2022\Enterprise\Common7\IDE\CommonExtensions\Platform\Debugger\VsDebugConsole.exe

VsDebugConsole.exe
   |- conhost.exe (Windows 2012 R2 이후의 환경)
   ㄴ ConsoleApp1.exe

(첨부 파일은 이 글의 예제 코드를 포함합니다.)




전에도 conhost에 대해 설명했지만, 이쯤에서 conhost와 우리가 만든 ConsoleApp1의 관계를 정리해 볼까요? ^^ 이에 대해서는 아래의 글에서 잘 설명하고 있습니다.

Windows Command-Line: Inside the Windows Console
; https://devblogs.microsoft.com/commandline/windows-command-line-inside-the-windows-console/

On Windows, however, things work differently: Windows users never launch the Console (conhost.exe) itself: Users launch Command-Line shells and apps, not the Console itself!


그러니까, 엄밀한 구분의 의미로 "Console"은 conhost.exe를 의미하고 콘솔 윈도우의 관리 및 입출력은 그 프로세스가 합니다. 사용자가 실행한 CUI 응용 프로그램은 사실 Console 자체는 아닌 것입니다. 이에 대해 또다시 설명이 이어지는데요,

Because users run Cmd.exe or PowerShell.exe and see a Console window appear, they labor under the common misunderstanding that Cmd and PowerShell are, themselves, "Consoles" … they’re not! Cmd.exe and PowerShell.exe are "headless" Command-Line applications that need to be attached to a Console (conhost.exe) instance from which they receive user input and to which they emit text output to be displayed to the user.

Also, many people say "Command-Line apps run in the Console". This is misleading and contributes additional confusion about how Consoles and Command-Line apps actually work!

...[생략]... "Command-Line tools/apps run connected to a Console"


즉, cmd.exe나 powershell.exe 및 기타 우리가 만든 CUI 프로그램들은 "Headless" 응용 프로그램입니다. 단지 conhost.exe가 관리하는 Console에 (그 내부에서 실행되는 것이 아니라) 연결돼 입/출력을 할 수 있게 되는 프로그램으로 동작하는 것입니다.

이에 비춰서 Windows GUI 프로그램도 "Headless" 응용 프로그램으로 볼 수 있습니다. 단지 그것은 윈도우 운영체제가 conhost.exe의 Console로 연결하는 작업을 하지 않는 차이가 있는 것입니다.




개인적으로 해석이 안 되는 결과가 있는데요, 위의 내용에서 Windows Server 2012에서 실행했을 때 표준 입출력 핸들이 이런 식으로 출력된다고 했는데요,

// Windows Server 2012 이상에서 실행한 경우

StandardInputHandle : 28 (0x1c)
StandardOutputHandle: 32 (0x20)
StandardErrortHandle: 36 (0x24)

저걸 Windows 7 환경에서 실행하면 이런 결과가 나옵니다.

// Windows 7 이하에서 실행한 경우

StandardInputHandle : 3 (0x3)
StandardOutputHandle: 7 (0x7)
StandardErrortHandle: 11 (0xb)

핸들 값이 뭔가 이상하죠? ^^; 원래는 4의 배수로 나와야 하는데 그렇지 않다는 것도 이상하고, 실제로 procexp를 이용해 확인해 봐도,

console_win7_5.png

설령 -1을 취한 값이라고 해도 4, 8, C에 있는 핸들은 이미 다른 목적으로 열려 있는 것들입니다. 잘은 모르겠지만, 아마도 이때까지만 해도 윈도우는 표준 입출력에 대한 핸들을 내부적으로 하드 코딩해 관리했던 것이 아닐까 싶습니다. 그러던 것이, Windows 8/2012 이후부터는 conhost.exe를 개선하면서, 그리고 해당 파일들을 \Device\ConDrv device driver가 관리하면서 (*NIX 계열처럼) 본래의 자리를 차지하게 된 것이 아닐까 추측해 봅니다.




참고로, Command Prompt 창 스스로 process id를 알아내는 재미있는 방법이 있는데요, (리눅스 환경에서는 "echo $$"로 쉽게 알 수 있는 것이지만!)

How to get own process pid from the command prompt in Windows
; https://serverfault.com/questions/126502/how-to-get-own-process-pid-from-the-command-prompt-in-windows

그래서 다음과 같이 실행해 보면,

// Windows 7에서 실행한 경우

c:\temp> title mycmd
c:\temp> tasklist /v /fo csv | findstr /i "mycmd"
"cmd.exe","3736","Console","2","4,392 K","Running","testpc\testusr","0:00:00","mycmd"

위와 같이 cmd.exe의 PID가 3736임을 알 수 있습니다.





[이 글에 대해서 여러분들과 의견을 공유하고 싶습니다. 틀리거나 미흡한 부분 또는 의문 사항이 있으시면 언제든 댓글 남겨주십시오.]







[최초 등록일: ]
[최종 수정일: 11/29/2024]

Creative Commons License
이 저작물은 크리에이티브 커먼즈 코리아 저작자표시-비영리-변경금지 2.0 대한민국 라이센스에 따라 이용하실 수 있습니다.
by SeongTae Jeong, mailto:techsharer at outlook.com

비밀번호

댓글 작성자
 




[1]  2  3  4  5  6  7  8  9  10  11  12  13  14  15  ...
NoWriterDateCnt.TitleFile(s)
13863정성태1/14/2025325Linux: 113. Linux - 프로세스를 위한 전용 SELinux 보안 문맥 지정
13862정성태1/13/2025233Linux: 112. Linux - 데몬을 위한 SELinux 보안 정책 설정
13861정성태1/11/2025672Windows: 276. 명령행에서 원격 서비스를 동기/비동기로 시작/중지
13860정성태1/10/2025740디버깅 기술: 216. WinDbg - 2가지 유형의 식 평가 방법(MASM, C++)
13859정성태1/9/2025820디버깅 기술: 215. Windbg - syscall 이후 실행되는 KiSystemCall64 함수 및 SSDT 디버깅
13858정성태1/8/2025854개발 환경 구성: 738. PowerShell - 원격 호출 시 "powershell.exe"가 아닌 "pwsh.exe" 환경으로 명령어를 실행하는 방법
13857정성태1/7/2025872C/C++: 187. Golang - 콘솔 응용 프로그램을 Linux 데몬 서비스를 지원하도록 변경파일 다운로드1
13856정성태1/6/2025923디버깅 기술: 214. Windbg - syscall 단계까지의 Win32 API 호출 (예: Sleep)
13855정성태12/28/20241440오류 유형: 941. Golang - os.StartProcess() 사용 시 오류 정리
13854정성태12/27/20241421C/C++: 186. Golang - 콘솔 응용 프로그램을 NT 서비스를 지원하도록 변경파일 다운로드1
13853정성태12/26/20241657디버깅 기술: 213. Windbg - swapgs 명령어와 (Ring 0 커널 모드의) FS, GS Segment 레지스터
13852정성태12/25/20241603디버깅 기술: 212. Windbg - (Ring 3 사용자 모드의) FS, GS Segment 레지스터파일 다운로드1
13851정성태12/23/20241515디버깅 기술: 211. Windbg - 커널 모드 디버깅 상태에서 사용자 프로그램을 디버깅하는 방법
13850정성태12/23/20241546오류 유형: 940. "Application Information" 서비스를 중지한 경우, "This file does not have an app associated with it for performing this action."
13849정성태12/20/20241562디버깅 기술: 210. Windbg - 논리(가상) 주소를 Segmentation을 거쳐 선형 주소로 변경
13848정성태12/18/20241797디버깅 기술: 209. Windbg로 알아보는 Prototype PTE파일 다운로드2
13847정성태12/18/20241699오류 유형: 939. golang - 빌드 시 "unknown directive: toolchain" 오류 빌드 시 이런 오류가 발생한다면?
13846정성태12/17/20241789디버깅 기술: 208. Windbg로 알아보는 Trans/Soft PTE와 2가지 Page Fault 유형파일 다운로드1
13845정성태12/16/20241985디버깅 기술: 207. Windbg로 알아보는 PTE (_MMPTE)
13844정성태12/14/20241860디버깅 기술: 206. Windbg로 알아보는 PFN (_MMPFN)파일 다운로드1
13843정성태12/13/20241877오류 유형: 938. Docker container 내에서 빌드 시 error MSB3021: Unable to copy file "..." to "...". Access to the path '...' is denied.
13842정성태12/12/20242306디버깅 기술: 205. Windbg - KPCR, KPRCB
13841정성태12/11/20242245오류 유형: 937. error MSB4044: The "ValidateValidArchitecture" task was not given a value for the required parameter "RemoteTarget"
13840정성태12/11/20242064오류 유형: 936. msbuild - Your project file doesn't list 'win' as a "RuntimeIdentifier"
13839정성태12/11/20242018오류 유형: 936. msbuild - error CS1617: Invalid option '12.0' for /langversion. Use '/langversion:?' to list supported values.
[1]  2  3  4  5  6  7  8  9  10  11  12  13  14  15  ...