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

비밀번호

댓글 작성자
 




1  2  3  4  5  6  7  [8]  9  10  11  12  13  14  15  ...
NoWriterDateCnt.TitleFile(s)
13423정성태10/6/20233078스크립트: 58. 파이썬 - async/await 기본 사용법
13422정성태10/5/20233219닷넷: 2148. C# - async 유무에 따른 awaitable 메서드의 병렬 및 예외 처리
13421정성태10/4/20233247닷넷: 2147. C# - 비동기 메서드의 async 예약어 유무에 따른 차이
13420정성태9/26/20235337스크립트: 57. 파이썬 - UnboundLocalError: cannot access local variable '...' where it is not associated with a value
13419정성태9/25/20233107스크립트: 56. 파이썬 - RuntimeError: dictionary changed size during iteration
13418정성태9/25/20233781닷넷: 2146. C# - ConcurrentDictionary 자료 구조의 동기화 방식
13417정성태9/19/20233351닷넷: 2145. C# - 제네릭의 형식 매개변수에 속한 (매개변수를 가진) 생성자를 호출하는 방법
13416정성태9/19/20233161오류 유형: 877. redis-py - MISCONF Redis is configured to save RDB snapshots, ...
13415정성태9/18/20233641닷넷: 2144. C# 12 - 컬렉션 식(Collection Expressions)
13414정성태9/16/20233406디버깅 기술: 193. Windbg - ThreadStatic 필드 값을 조사하는 방법
13413정성태9/14/20233597닷넷: 2143. C# - 시스템 Time Zone 변경 시 이벤트 알림을 받는 방법
13412정성태9/14/20236871닷넷: 2142. C# 12 - 인라인 배열(Inline Arrays) [1]
13411정성태9/12/20233380Windows: 252. 권한 상승 전/후 따로 관리되는 공유 네트워크 드라이브 정보
13410정성태9/11/20234883닷넷: 2141. C# 12 - Interceptor (컴파일 시에 메서드 호출 재작성) [1]
13409정성태9/8/20233741닷넷: 2140. C# - Win32 API를 이용한 모니터 전원 끄기
13408정성태9/5/20233730Windows: 251. 임의로 만든 EXE 파일을 포함한 ZIP 파일의 압축을 해제할 때 Windows Defender에 의해 삭제되는 경우
13407정성태9/4/20233488닷넷: 2139. C# - ParallelEnumerable을 이용한 IEnumerable에 대한 병렬 처리
13406정성태9/4/20233442VS.NET IDE: 186. Visual Studio Community 버전의 라이선스
13405정성태9/3/20233869닷넷: 2138. C# - async 메서드 호출 원칙
13404정성태8/29/20233394오류 유형: 876. Windows - 키보드의 등호(=, Equals sign) 키가 눌리지 않는 경우
13403정성태8/21/20233223오류 유형: 875. The following signatures couldn't be verified because the public key is not available: NO_PUBKEY EB3E94ADBE1229CF
13402정성태8/20/20233289닷넷: 2137. ILSpy의 nuget 라이브러리 버전 - ICSharpCode.Decompiler
13401정성태8/19/20233526닷넷: 2136. .NET 5+ 환경에서 P/Invoke의 성능을 높이기 위한 SuppressGCTransition 특성 [1]
13400정성태8/10/20233351오류 유형: 874. 파이썬 - pymssql을 윈도우 환경에서 설치 불가
13399정성태8/9/20233375닷넷: 2135. C# - 지역 변수로 이해하는 메서드 매개변수의 값/참조 전달
13398정성태8/3/20234135스크립트: 55. 파이썬 - pyodbc를 이용한 SQL Server 연결 사용법
1  2  3  4  5  6  7  [8]  9  10  11  12  13  14  15  ...