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

비밀번호

댓글 작성자
 




... 136  137  138  139  140  141  142  143  144  145  146  [147]  148  149  150  ...
NoWriterDateCnt.TitleFile(s)
1439정성태4/18/201330514VS.NET IDE: 76. Visual Studio 2012와 Itanium 빌드 옵션 [2]
1438정성태4/17/201330480.NET Framework: 366. 다른 프로세스에 환경 변수 설정하는 방법 - 두 번째 이야기 [1]파일 다운로드1
1437정성태4/17/201330374VC++: 67. CRT(C Runtime DLL: msvcr...dll)에 대한 의존성 제거
1436정성태4/17/201335051.NET Framework: 365. Local SYSTEM 권한으로 코드를 실행하는 방법파일 다운로드1
1435정성태4/15/201344665Windows: 71. ad-hoc 보다 더 편리한 "가상 Wifi" 를 이용한 인터넷 공유 [2]
1434정성태4/9/201325880오류 유형: 173. TFS 서버의 이벤트 로그 오류 - WebHost failed to process a request. Parameter name: certificate
1433정성태4/9/201326338개발 환경 구성: 189. TFS에 설치된 SharePoint 의 PowerShell 콘솔 띄우는 방법
1432정성태4/5/201326962오류 유형: 172. System.Web.PipelineModuleStepContainer.GetEventCount 에서 NullReferenceException 이 발생한다면?
1431정성태4/5/201327629기타: 29. 부팅 가능한 (외장) HDD를 기존 부팅 메뉴에 추가하는 방법
1430정성태4/4/201329832제니퍼 .NET: 23. 모바일용 웹 사이트에서 발생하는 응답 시간 지연 현상 [5]파일 다운로드1
1429정성태3/29/201325893개발 환경 구성: 188. SCOM 2012 - ASP.NET 모니터링 방법
1428정성태3/29/201327029개발 환경 구성: 187. SCOM 2012 환경 구성 - Management Packs
1427정성태3/29/201323812오류 유형: 171. SCOM 2012 - 원격 에이전트 설치 오류
1426정성태3/29/201326554개발 환경 구성: 186. SCOM 2012 환경 구성 - 관리 대상 추가
1424정성태3/21/201328749개발 환경 구성: 185. System Center 2012 Operations Manager 설치
1423정성태3/18/201323734오류 유형: 170. The specified domain either does not exist or could not be contacted.
1422정성태3/14/201326022오류 유형: 169. Windows 8/2012에 .NET 3.5가 설치되지 않는 경우
1421정성태3/13/201343217.NET Framework: 364. WCF RIA 서비스 + Silverlight 사용 예제
1420정성태3/12/201326894오류 유형: 168. ORA-12514: TNS:listener does not currently know of service requested in connect descriptor
1419정성태3/12/201324243Windows: 70. 관리 도구에서 "Windows Server Backup" 항목이 없는 경우
1418정성태2/28/201334234오류 유형: 167. Internet Explorer 10 설치 후 Flash 객체의 메서드/속성 접근 오류가 발생한다면?
1417정성태2/25/201330297.NET Framework: 363. ASP.NET AJAX PageMethods - ASPX.cs의 static 메서드를 AJAX로 호출파일 다운로드1
1416정성태2/22/201332036개발 환경 구성: 184. Xamarin 2.0 - Visual Studio에서 Android 앱을 폰으로 직접 배포하는 방법
1415정성태2/21/201340440개발 환경 구성: 183. Xamarin 2.0 - 윈도우 환경의 Visual Studio에서 C#으로 iOS/Android 응용 프로그램 개발 [4]파일 다운로드1
1414정성태2/21/201335261개발 환경 구성: 182. JMeter로 XML 웹 서비스 호출에 대한 부하 테스트 방법파일 다운로드2
1413정성태2/19/201334996VC++: 66. Chromium 컴파일하는 방법 - 두 번째 이야기 [3]
... 136  137  138  139  140  141  142  143  144  145  146  [147]  148  149  150  ...