Microsoft MVP성태의 닷넷 이야기
글쓴 사람
정성태 (techsharer at outlook.com)
홈페이지
첨부 파일
(연관된 글이 1개 있습니다.)
(시리즈 글이 11개 있습니다.)
.NET Framework: 475. ETW(Event Tracing for Windows)를 C#에서 사용하는 방법
; https://www.sysnet.pe.kr/2/0/1804

.NET Framework: 483. 코드로 살펴 보는 ETW의 활성화 시점
; https://www.sysnet.pe.kr/2/0/1815

.NET Framework: 915. ETW(Event Tracing for Windows)를 이용한 닷넷 프로그램의 내부 이벤트 활용
; https://www.sysnet.pe.kr/2/0/12244

.NET Framework: 923. C# - ETW(Event Tracing for Windows)를 이용한 Finalizer 실행 감시
; https://www.sysnet.pe.kr/2/0/12255

.NET Framework: 932. C# - ETW 관련 Win32 API 사용 예제 코드 (1)
; https://www.sysnet.pe.kr/2/0/12292

.NET Framework: 933. C# - ETW 관련 Win32 API 사용 예제 코드 (2) NT Kernel Logger
; https://www.sysnet.pe.kr/2/0/12296

.NET Framework: 934. C# - ETW 관련 Win32 API 사용 예제 코드 (3) ETW Consumer 구현
; https://www.sysnet.pe.kr/2/0/12299

.NET Framework: 935. C# - ETW 관련 Win32 API 사용 예제 코드 (4) CLR ETW Consumer
; https://www.sysnet.pe.kr/2/0/12300

.NET Framework: 936. C# - ETW 관련 Win32 API 사용 예제 코드 (5) - Private Logger
; https://www.sysnet.pe.kr/2/0/12302

개발 환경 구성: 504. ETW - 닷넷 프레임워크 기반의 응용 프로그램을 위한 명령행 도구 etrace 소개
; https://www.sysnet.pe.kr/2/0/12303

.NET Framework: 994. C# - (.NET Core 2.2부터 가능한) 프로세스 내부에서 CLR ETW 이벤트 수신
; https://www.sysnet.pe.kr/2/0/12474




C# - ETW 관련 Win32 API 사용 예제 코드 (4) CLR ETW Consumer

지난 글에서,

C# - ETW 관련 Win32 API 사용 예제 코드 (1)
; https://www.sysnet.pe.kr/2/0/12292

C# - ETW 관련 Win32 API 사용 예제 코드 (2) NT Kernel Logger
; https://www.sysnet.pe.kr/2/0/12296

C# - ETW 관련 Win32 API 사용 예제 코드 (3) ETW Consumer 구현
; https://www.sysnet.pe.kr/2/0/12299

실습한 내용을 바탕으로 이제 CLR 관련 ETW 이벤트를,

ETW(Event Tracing for Windows)를 이용한 닷넷 프로그램의 내부 이벤트 활용
; https://www.sysnet.pe.kr/2/0/12244

Win32 API를 이용한 방법으로 재구성해 보겠습니다.




우선, CLR을 위한 ETW Provider를 찾아야겠지요. ETW Provider 목록에 보면 당연히 "ETW(Event Tracing for Windows)를 이용한 닷넷 프로그램의 내부 이벤트 활용" 글에서 다룬 "Microsoft-Windows-DotNETRuntime"과 그것의 GUID 값인 "{e13c0d23-ccbc-4e12-931b-d9cc2eee27e4}"도 나옵니다.

CLR ETW Providers
; https://learn.microsoft.com/en-us/dotnet/framework/performance/clr-etw-providers

따라서, 지난 글의 소스 코드에서 단순히 ETW Provider GUID 값만 바꿔 다음과 같이 구현할 수 있습니다.

using Microsoft.Samples.Eventing.Interop;
using System;
using System.Diagnostics;
using System.Runtime.InteropServices;
using System.Threading;

namespace ConsoleApp1
{
    class Program
    {
        static bool _disposed = false;
        static int _processId = 0;

        static void Main(string[] _)
        {
            _processId = Process.GetCurrentProcess().Id;

            string sessionName = "clrETWSession";
            Guid clrProvider = new Guid("{e13c0d23-ccbc-4e12-931b-d9cc2eee27e4}");
            int result = 0;

            ulong traceHandle = 0;
            ulong sessionHandle = 0;

            try
            {
                if (EtwInterop.IsSessionActive(sessionName, out var _) == true)
                {
                }
                else
                {
                    EventTraceProperties prop = new EventTraceProperties(true, sessionName);
                    result = NativeMethods.StartTrace(out sessionHandle, sessionName, ref prop);
                    Console.WriteLine(result);

                    if (result == 0)
                    {
                        ENABLE_TRACE_PARAMETERS enableParameters = new ENABLE_TRACE_PARAMETERS();
                        enableParameters.Version = 1;
                        enableParameters.EnableProperty = (uint)EventEnableProperty.Sid;

                        result = NativeMethods.EnableTraceEx2(sessionHandle, ref clrProvider,
                            NativeMethods.EVENT_CONTROL_CODE_ENABLE_PROVIDER, (byte)TraceEventLevel.Informational,
                            0, 0, 0, ref enableParameters);

                        Console.WriteLine(result);
                    }
                }

                if (result == 0)
                {
                    EventTraceLogfile logFile = new EventTraceLogfile();
                    logFile.LoggerName = sessionName;
                    logFile.EventRecordCallback = EventRecordCallback;

                    logFile.ProcessTraceMode = NativeMethods.PROCESS_TRACE_MODE_EVENT_RECORD | NativeMethods.PROCESS_TRACE_MODE_REAL_TIME
                        | NativeMethods.PROCESS_TRACE_MODE_RAW_TIMESTAMP;
                    traceHandle = NativeMethods.OpenTrace(ref logFile);
                }

                if (traceHandle != 0)
                {
                    Thread t = new Thread((ThreadStart)(
                    () =>
                    {
                        Console.WriteLine("PrcoessTrace: " + EtwInterop.ProcessTrace(traceHandle));
                    }));

                    t.Start();

                    Console.WriteLine("Press ENTER key to exit...");
                    Console.ReadLine();

                    // Console.WriteLine("Disabled: " + EtwInterop.DisableProvider(sessionHandle, ref auditApiCalls));
                    Console.WriteLine("Closed: " + (NativeMethods.CloseTrace(traceHandle) == 0));

                    t.Join();
                }
            }
            finally
            {
                _disposed = true;
                EtwInterop.CloseActiveSession(sessionName);
            }
        }

        static private void EventRecordCallback([In] ref EventRecord eventRecord)
        {
            if (_processId != eventRecord.EventHeader.ProcessId)
            {
                return;
            }
            
            Console.WriteLine(DateTime.Now + ": " + eventRecord.EventHeader.ProcessId);
        }
    }
}




위와 같이 실행하면 현재 프로세스에서 발생하는 CLR 이벤트를 종류에 상관없이 모두 콜백을 받는데요, "ETW(Event Tracing for Windows)를 이용한 닷넷 프로그램의 내부 이벤트 활용" 글에서는 이벤트 종류를 선택할 수 있는 코드가,

userSession.EnableProvider(
    ClrTraceEventParser.ProviderGuid,
    TraceEventLevel.Verbose,
    (ulong)(
    ClrTraceEventParser.Keywords.Contention |  // thread contention timing
    ClrTraceEventParser.Keywords.Threading |   // threadpool events
    ClrTraceEventParser.Keywords.Exception |   // get the first chance exceptions
    ClrTraceEventParser.Keywords.GCHeapAndTypeNames | 
    ClrTraceEventParser.Keywords.Type | // for finalizer and exceptions type names
    ClrTraceEventParser.Keywords.GC     // garbage collector details
    )
);

있었으므로 이 부분에 대해서도 좀 더 봐야 할 필요가 있습니다. 우선, 위의 코드에서 "Keywords"에 담긴 값들을 ETW 이벤트에 대한 필터링 조건으로 주고 있는데 역어셈블을 해 보면 다음과 같이 값을 확인할 수 있습니다.

[Flags]
public enum Keywords : long
{
    None = 0L,
    All = -65L,
    GC = 1L,
    GCHandle = 2L,
    Binder = 4L,
    Loader = 8L,
    Jit = 0x10L,
    NGen = 0x20L,
    StartEnumeration = 0x40L,
    StopEnumeration = 0x80L,
    Security = 0x400L,
    AppDomainResourceManagement = 0x800L,
    JitTracing = 0x1000L,
    Interop = 0x2000L,
    Contention = 0x4000L,
    Exception = 0x8000L,
    Threading = 0x10000L,
    JittedMethodILToNativeMap = 0x20000L,
    OverrideAndSuppressNGenEvents = 0x40000L,
    Type = 0x80000L,
    GCHeapDump = 0x100000L,
    GCSampledObjectAllocationHigh = 0x200000L,
    GCHeapSurvivalAndMovement = 0x400000L,
    GCHeapCollect = 0x800000L,
    GCHeapAndTypeNames = 0x1000000L,
    GCSampledObjectAllocationLow = 0x2000000L,
    GCAllObjectAllocation = 0x2200000L,
    SupressNGen = 0x40000L,
    PerfTrack = 0x20000000L,
    Stack = 0x40000000L,
    ThreadTransfer = 0x80000000L,
    Debugger = 0x100000000L,
    Monitoring = 0x200000000L,
    Codesymbols = 0x400000000L,
    Compilation = 0x1000000000L,
    CompilationDiagnostic = 0x2000000000L,
    Default = 0x14c14fccbdL,
    JITSymbols = 0x60098L,
    GCHeapSnapshot = 0x1980001L
}

그런데, 이 값들은 Microsoft.Diagnostics.Tracing.TraceEvent 라이브러리 제작자가 임의로 결정한 것이 아니고 ETW manifest 파일, 즉 공급자가 정해 놓은 것입니다.

C:\Windows\Microsoft.NET\Framework\v4.0.30319\CLR-ETW.man
C:\Windows\Microsoft.NET\Framework64\v4.0.30319\CLR-ETW.man

실제로 CLR-ETW.man 파일을 보면 다음과 같은 내용들을 볼 수 있습니다.

...[생략]...

<keywords>
    <keyword name="GCKeyword" mask="0x1" message="$(string.RuntimePublisher.GCKeywordMessage)" symbol="CLR_GC_KEYWORD"/>
    <keyword name="GCHandleKeyword" mask="0x2" message="$(string.RuntimePublisher.GCHandleKeywordMessage)" symbol="CLR_GCHANDLE_KEYWORD"/>
    <keyword name="FusionKeyword" mask="0x4" message="$(string.RuntimePublisher.FusionKeywordMessage)" symbol="CLR_FUSION_KEYWORD"/>
    <keyword name="LoaderKeyword" mask="0x8" message="$(string.RuntimePublisher.LoaderKeywordMessage)" symbol="CLR_LOADER_KEYWORD"/>
    <keyword name="JitKeyword" mask="0x10" message="$(string.RuntimePublisher.JitKeywordMessage)" symbol="CLR_JIT_KEYWORD"/>
    <keyword name="NGenKeyword" mask="0x20" message="$(string.RuntimePublisher.NGenKeywordMessage)" symbol="CLR_NGEN_KEYWORD"/>
    <keyword name="StartEnumerationKeyword" mask="0x40" message="$(string.RuntimePublisher.StartEnumerationKeywordMessage)" symbol="CLR_STARTENUMERATION_KEYWORD"/>
    <keyword name="EndEnumerationKeyword" mask="0x80" message="$(string.RuntimePublisher.EndEnumerationKeywordMessage)" symbol="CLR_ENDENUMERATION_KEYWORD"/>
    <!-- Keyword mask 0x100 is now defunct -->
    <!-- Keyword mask 0x200 is now defunct -->
    <keyword name="SecurityKeyword" mask="0x400" message="$(string.RuntimePublisher.SecurityKeywordMessage)" symbol="CLR_SECURITY_KEYWORD"/>
    <keyword name="AppDomainResourceManagementKeyword" mask="0x800" message="$(string.RuntimePublisher.AppDomainResourceManagementKeywordMessage)" symbol="CLR_APPDOMAINRESOURCEMANAGEMENT_KEYWORD"/>
    <keyword name="JitTracingKeyword" mask="0x1000" message="$(string.RuntimePublisher.JitTracingKeywordMessage)" symbol="CLR_JITTRACING_KEYWORD"/>
    <keyword name="InteropKeyword" mask="0x2000" message="$(string.RuntimePublisher.InteropKeywordMessage)" symbol="CLR_INTEROP_KEYWORD"/>
    <keyword name="ContentionKeyword" mask="0x4000" message="$(string.RuntimePublisher.ContentionKeywordMessage)" symbol="CLR_CONTENTION_KEYWORD"/>
    <keyword name="ExceptionKeyword" mask="0x8000" message="$(string.RuntimePublisher.ExceptionKeywordMessage)" symbol="CLR_EXCEPTION_KEYWORD"/>
    <keyword name="ThreadingKeyword" mask="0x10000" message="$(string.RuntimePublisher.ThreadingKeywordMessage)" symbol="CLR_THREADING_KEYWORD"/>
    <keyword name="JittedMethodILToNativeMapKeyword" mask="0x20000" message="$(string.RuntimePublisher.JittedMethodILToNativeMapKeywordMessage)" symbol="CLR_JITTEDMETHODILTONATIVEMAP_KEYWORD"/>
    <keyword name="OverrideAndSuppressNGenEventsKeyword" mask="0x40000" message="$(string.RuntimePublisher.OverrideAndSuppressNGenEventsKeywordMessage)" symbol="CLR_OVERRIDEANDSUPPRESSNGENEVENTS_KEYWORD"/>
    <keyword name="TypeKeyword" mask="0x80000" message="$(string.RuntimePublisher.TypeKeywordMessage)" symbol="CLR_TYPE_KEYWORD"/>
    <keyword name="GCHeapDumpKeyword" mask="0x100000" message="$(string.RuntimePublisher.GCHeapDumpKeywordMessage)" symbol="CLR_GCHEAPDUMP_KEYWORD"/>
    <keyword name="GCSampledObjectAllocationHighKeyword" mask="0x200000" message="$(string.RuntimePublisher.GCSampledObjectAllocationHighKeywordMessage)" symbol="CLR_GCHEAPALLOCHIGH_KEYWORD"/>
    <keyword name="GCHeapSurvivalAndMovementKeyword" mask="0x400000" message="$(string.RuntimePublisher.GCHeapSurvivalAndMovementKeywordMessage)" symbol="CLR_GCHEAPSURVIVALANDMOVEMENT_KEYWORD"/>
    <keyword name="GCHeapCollectKeyword" mask="0x800000" message="$(string.RuntimePublisher.GCHeapCollectKeyword)" symbol="CLR_GCHEAPCOLLECT_KEYWORD"/>
    <keyword name="GCHeapAndTypeNamesKeyword" mask="0x1000000" message="$(string.RuntimePublisher.GCHeapAndTypeNamesKeyword)" symbol="CLR_GCHEAPANDTYPENAMES_KEYWORD"/>
    <keyword name="GCSampledObjectAllocationLowKeyword" mask="0x2000000" message="$(string.RuntimePublisher.GCSampledObjectAllocationLowKeywordMessage)" symbol="CLR_GCHEAPALLOCLOW_KEYWORD"/>
    <keyword name="PerfTrackKeyword" mask="0x20000000" message="$(string.RuntimePublisher.PerfTrackKeywordMessage)" symbol="CLR_PERFTRACK_KEYWORD"/>
    <keyword name="StackKeyword" mask="0x40000000" message="$(string.RuntimePublisher.StackKeywordMessage)" symbol="CLR_STACK_KEYWORD"/>
    <keyword name="ThreadTransferKeyword" mask="0x80000000" message="$(string.RuntimePublisher.ThreadTransferKeywordMessage)" symbol="CLR_THREADTRANSFER_KEYWORD"/>
    <keyword name="DebuggerKeyword" mask="0x100000000" message="$(string.RuntimePublisher.DebuggerKeywordMessage)" symbol="CLR_DEBUGGER_KEYWORD"/>
    <keyword name="MonitoringKeyword" mask="0x200000000" message="$(string.RuntimePublisher.MonitoringKeywordMessage)" symbol="CLR_MONITORING_KEYWORD"/>
</keywords>

...[생략]...

이런 식으로 ETW Provider 측에서 제공하는 "Keywords"에 기반을 둔 필터링은 EnableProvider2 메서드의 5번째 인자에 전달하면 되고,

ulong matchAnyKeywords = (ulong)ClrProviderKeywords.Exception;

result = NativeMethods.EnableTraceEx2(sessionHandle, ref clrProvider,
    NativeMethods.EVENT_CONTROL_CODE_ENABLE_PROVIDER, (byte)TraceEventLevel.Informational,
    matchAnyKeywords, 0, 0, ref enableParameters);

따라서 위의 코드는 Exception에 대한 조건만을 전달했으므로 이후 콜백 함수는 Exception이 발생했을 때에만 호출되는 것을 확인할 수 있습니다.




EnableTraceEx2의 6번째 인자를 보면 또 다른 필터링 조건이 있는데, 다음의 글에서 자세한 설명을 볼 수 있습니다.

Tampering with Windows Event Tracing: Background, Offense, and Defense
; https://medium.com/palantir/tampering-with-windows-event-tracing-background-offense-and-defense-4be7ac62ac63

Microsoft.Diagnostics.Tracing.TraceEvent 라이브러리에는 다음과 같이 정의되어 있고,

// Used int the EVENT_FILTER_DESCRIPTOR.Type field 
internal const int EVENT_FILTER_TYPE_NONE = (0x00000000);
internal const int EVENT_FILTER_TYPE_SCHEMATIZED = unchecked((int)(0x80000000));
internal const int EVENT_FILTER_TYPE_SYSTEM_FLAGS = unchecked((int)(0x80000001));
internal const int EVENT_FILTER_TYPE_TRACEHANDLE = unchecked((int)(0x80000002));      // Used with CAPTURE_STATE to get a rundown delivered only to your session
internal const int EVENT_FILTER_TYPE_PID = unchecked((int)(0x80000004));              // Ptr points at array of ints.   (Size determined by byteSize/sizeof(int)
internal const int EVENT_FILTER_TYPE_EXECUTABLE_NAME = unchecked((int)(0x80000008));  // Ptr points at string, can have ';' to separate names. 
internal const int EVENT_FILTER_TYPE_PACKAGE_ID = unchecked((int)(0x80000010));       // Ptr points at string, can have ';' to separate names.
internal const int EVENT_FILTER_TYPE_PACKAGE_APP_ID = unchecked((int)(0x80000020));   // Package Relative App Id = (PRAID);
internal const int EVENT_FILTER_TYPE_PAYLOAD = unchecked((int)(0x80000100));          // Can filter on 
internal const int EVENT_FILTER_TYPE_EVENT_ID = unchecked((int)(0x80000200));         // Ptr points at EVENT_FILTER_EVENT_ID
internal const int EVENT_FILTER_TYPE_STACKWALK = unchecked((int)(0x80001000));        // Ptr points at EVENT_FILTER_EVENT_ID

가장 관심 있는 항목이 PID와 같은 조건이 될 수 있습니다. 사실 시스템 전체에 걸쳐서 이벤트가 오기 때문에 잦은 콜백 함수는 시스템 부하를 늘리므로, 가령 Process ID를 필터링 조건으로 지정해 특정 프로세스에 해당하는 이벤트만 받게 해도 부담이 줄기 때문입니다.

단지, 6번째 인자에 해당하는 필터링 조건은 운영체제 제한이 있는 듯한데, Microsoft.Diagnostics.Tracing.TraceEvent의 소스 코드에 윈도우 8.1부터 가능하다고 나옵니다.

/// <summary>
/// This return true on OS version beyond 8.1 (windows Version 6.3).   It means most of the
/// per-event filtering is supported.  
/// </summary>
public static bool FilteringSupported
{
    get
    {
        if (!s_IsEtwFilteringSupported.HasValue)
        {
            var ret = false;

            // For Windows Versions above windows 8, OSVersion lies and returns 6.2 (window 8) even though
            // the windows version is higher.  We have to try harder to figure out whether we are windows 8 or something
            // later.   Currently we look at the file version number of an OS DLL.  
            // There is probably a better way.   
            var winDir = Environment.GetEnvironmentVariable("WinDir");
            var kernel32 = Path.Combine(winDir, @"system32\Kernel32.dll");
            if (File.Exists(kernel32))
            {
                using (var kernel32PE = new PEFile.PEFile(kernel32))
                {
                    var versionInfo = kernel32PE.GetFileVersionInfo();
                    if (versionInfo != null)
                    {
                        // versionInfo.FileVersion is now the real version number we want but it is a string, not a 
                        // number.   Our tests is if version number bigger than 6.3 (as a string) or a two or more digit 
                        // major version.   
                        if (string.Compare("6.3", versionInfo.FileVersion) <= 0 || 2 <= versionInfo.FileVersion.IndexOf('.'))
                        {
                            ret = true;
                        }
                    }
                }
            }
            s_IsEtwFilteringSupported = ret;
        }
        return s_IsEtwFilteringSupported.Value;
    }
}

이에 대한 구현 코드는, 사실상 Microsoft.Diagnostics.Tracing.TraceEvent 소스 코드를 그대로 베끼는 것이 더 나을 정도이므로 여기서는 생략하겠습니다. ^^

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




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

[연관 글]






[최초 등록일: ]
[최종 수정일: 6/28/2023]

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

비밀번호

댓글 작성자
 




... 16  17  18  19  20  [21]  22  23  24  25  26  27  28  29  30  ...
NoWriterDateCnt.TitleFile(s)
13102정성태7/19/20226594.NET Framework: 2033. .NET Core/5+에서는 구할 수 없는 HttpRuntime.AppDomainAppId
13101정성태7/15/202215451도서: 시작하세요! C# 10 프로그래밍
13100정성태7/15/20227973.NET Framework: 2032. C# 11 - shift 연산자 재정의에 대한 제약 완화 (Relaxing Shift Operator)
13099정성태7/14/20227835.NET Framework: 2031. C# 11 - 사용자 정의 checked 연산자파일 다운로드1
13098정성태7/13/20226112개발 환경 구성: 647. Azure - scale-out 상태의 App Service에서 특정 인스턴스에 요청을 보내는 방법 [1]
13097정성태7/12/20225508오류 유형: 817. Golang - binary.Read: invalid type int32
13096정성태7/8/20228283.NET Framework: 2030. C# 11 - UTF-8 문자열 리터럴
13095정성태7/7/20226359Windows: 208. AD 도메인에 참여하지 않은 컴퓨터에서 Kerberos 인증을 사용하는 방법
13094정성태7/6/20226064오류 유형: 816. Golang - "short write" 오류 원인
13093정성태7/5/20226984.NET Framework: 2029. C# - HttpWebRequest로 localhost 접속 시 2초 이상 지연
13092정성태7/3/20227935.NET Framework: 2028. C# - HttpWebRequest의 POST 동작 방식파일 다운로드1
13091정성태7/3/20226731.NET Framework: 2027. C# - IPv4, IPv6를 모두 지원하는 서버 소켓 생성 방법
13090정성태6/29/20225890오류 유형: 815. PyPI에 업로드한 패키지가 반영이 안 되는 경우
13089정성태6/28/20226338개발 환경 구성: 646. HOSTS 파일 변경 시 Edge 브라우저에 반영하는 방법
13088정성태6/27/20225451개발 환경 구성: 645. "Developer Command Prompt for VS 2022" 명령행 환경의 폰트를 바꾸는 방법
13087정성태6/23/20228423스크립트: 41. 파이썬 - FastAPI / uvicorn 호스팅 환경에서 asyncio 사용하는 방법 [1]
13086정성태6/22/20227828.NET Framework: 2026. C# 11 - 문자열 보간 개선 2가지파일 다운로드1
13085정성태6/22/20227890.NET Framework: 2025. C# 11 - 원시 문자열 리터럴(raw string literals)파일 다운로드1
13084정성태6/21/20226528개발 환경 구성: 644. Windows - 파이썬 2.7을 msi 설치 없이 구성하는 방법
13083정성태6/20/20227122.NET Framework: 2024. .NET 7에 도입된 GC의 메모리 해제에 대한 segment와 region의 차이점 [2]
13082정성태6/19/20226148.NET Framework: 2023. C# - Process의 I/O 사용량을 보여주는 GetProcessIoCounters Win32 API파일 다운로드1
13081정성태6/17/20226220.NET Framework: 2022. C# - .NET 7 Preview 5 신규 기능 - System.IO.Stream ReadExactly / ReadAtLeast파일 다운로드1
13080정성태6/17/20226852개발 환경 구성: 643. Visual Studio 2022 17.2 버전에서 C# 11 또는 .NET 7.0 preview 적용
13079정성태6/17/20224587오류 유형: 814. 파이썬 - Error: The file/path provided (...) does not appear to exist
13078정성태6/16/20226629.NET Framework: 2021. WPF - UI Thread와 Render Thread파일 다운로드1
13077정성태6/15/20226973스크립트: 40. 파이썬 - PostgreSQL 환경 구성
... 16  17  18  19  20  [21]  22  23  24  25  26  27  28  29  30  ...