다음을 통해 공유


CLR 프로파일러 및 Windows 스토어 앱

이 항목에서는 Windows 스토어 앱 내에서 실행되는 관리 코드를 분석하는 진단 도구를 작성할 때 고려해야 할 사항에 대해 설명합니다. 또한 Windows 스토어 앱에 대해 실행할 때 계속 작동하도록 기존 개발 도구를 수정하는 지침을 제공합니다. 이 정보를 이해하려면 공용 언어 런타임 프로파일링 API에 익숙한 경우 Windows 데스크톱 애플리케이션에 대해 올바르게 실행되는 진단 도구에서 이 API를 이미 사용했으며 이제 Windows 스토어 앱에 대해 올바르게 실행되도록 도구를 수정하는 데 관심을 갖는 것이 좋습니다.

소개

소개 단락을 지났다면 CLR 프로파일링 API에 익숙해졌습니다. 관리 데스크톱 애플리케이션에 대해 잘 작동하는 진단 도구를 이미 작성했습니다. 이제 도구가 관리 Windows 스토어 앱에서 작동하도록 하려면 어떻게 해야 할지 궁금할 것입니다. 아마 이미 이 작업을 하려고 했고 그것이 간단한 작업이 아니라는 것을 알게 되었을 것입니다. 실제로 모든 도구 개발자에게 명확하지 않을 수 있는 여러 가지 고려 사항이 있습니다. 예시:

  • Windows 스토어 앱이 사용 권한이 심각하게 감소된 컨텍스트에서 실행됩니다.

  • Windows 메타데이터 파일은 기존의 관리 모듈과 비교할 때 고유한 특성을 가집니다.

  • Windows 스토어 앱은 상호 작용이 중단되면 자체 일시 중단하는 습관이 있습니다.

  • 프로세스 간 통신 메커니즘은 여러 가지 이유로 더 이상 작동하지 않을 수 있습니다.

이 항목에서는 사용자가 알고 있어야 할 사항과 이러한 메커니즘을 제대로 다루는 방법을 나열합니다.

CLR 프로파일링 API를 처음 사용하는 경우 이 항목의 끝에 있는 리소스로 건너뛰어 더 적절한 소개 정보를 찾습니다.

특정 Windows API와 그 사용 방법에 대한 세부 정보 제공도 이 항목의 범위를 벗어납니다. 이 항목을 시작점으로 간주하고 MSDN을 참조하여 여기에서 참조하는 모든 Windows API에 대해 자세히 알아보세요.

아키텍처 및 용어

일반적으로 진단 도구는 다음 그림과 같은 아키텍처를 가집니다. “프로파일러”라는 용어를 사용하지만, 이러한 많은 도구는 일반적인 성능 또는 메모리 프로파일링을 훨씬 뛰어넘어 코드 검사, 모의 개체 프레임워크, 시간 이동 디버깅, 애플리케이션 모니터링 등의 영역으로 확장됩니다. 간단히 하기 위해 이 항목에서는 이러한 모든 도구를 계속 프로파일러로 나타냅니다.

이 항목 전체에서 사용되는 용어는 다음과 같습니다.

애플리케이션

이것은 프로파일러가 분석하는 애플리케이션입니다. 일반적으로 이 애플리케이션의 개발자는 이제 프로파일러를 사용하여 애플리케이션 문제를 진단합니다. 일반적으로 이 애플리케이션은 Windows 데스크톱 애플리케이션이지만, 이 항목에서는 Windows 스토어 앱을 살펴봅니다.

Profiler DLL

이것은 분석 중인 애플리케이션의 프로세스 공간에 로드되는 구성 요소입니다. 프로파일러 “에이전트”로도 알려진 이 구성 요소는 ICorProfilerCallbackICorProfilerCallback 인터페이스(2,3 등)를 구현하고 ICorProfilerInfo(2,3 등) 인터페이스를 사용하여 분석된 애플리케이션에 대한 데이터를 수집하고 애플리케이션 동작의 양상을 수정합니다.

Profiler UI

이것은 프로파일러 사용자가 상호 작용하는 데스크톱 애플리케이션입니다. 사용자에게 애플리케이션 상태를 표시하고 사용자에게 분석된 애플리케이션의 동작을 제어할 수 있는 수단을 제공합니다. 이 구성 요소는 항상 프로파일링되는 애플리케이션의 프로세스 공간과 분리된 별도의 자체 프로세스 공간에서 실행됩니다. Profiler UI는 ICLRProfiling::AttachProfiler 메서드를 호출하는 프로세스인 “연결 트리거”로 작동하여 Profiler DLL이 시작 시 로드되지 않은 경우 분석된 애플리케이션이 Profiler DLL을 로드하도록 할 수도 있습니다.

Important

Profiler UI는 Windows 스토어 앱을 제어하고 보고하는 데 사용되는 경우에도 Windows 데스크톱 애플리케이션으로 유지되어야 합니다. Windows 스토어에서 진단 도구를 패키지하고 배송할 수 있을 것으로 기대하지 마세요. 도구는 Windows 스토어 앱에서 수행할 수 없는 작업을 수행해야 하며, 그 중 많은 작업이 Profiler UI 내에 있습니다.

이 문서 전체에서 샘플 코드는 다음과 같이 가정합니다.

  • Profiler DLL은 CLR 프로파일링 API의 요구 사항에 따라 네이티브 DLL이어야 하므로 C++로 작성되었습니다.

  • Profiler UI는 C#으로 작성되었습니다. 이것은 필수적이지 않지만, Profiler UI 프로세스에 대한 언어에는 요구 사항이 없으니, 왜 간결하고 간단한 언어를 선택하지 않겠어요?

Windows RT 디바이스

Windows RT 디바이스는 상당히 잠겨 있습니다. 타사 프로파일러는 이러한 디바이스에 로드할 수 없습니다. 이 문서는 Windows 8 PC에 중점을 둡니다.

Windows 런타임 API 사용

다음 섹션에서 설명하는 여러 시나리오에서 Profiler UI 데스크톱 애플리케이션은 몇 가지 새로운 Windows 런타임 API를 사용해야 합니다. 설명서를 참조하여 데스크톱 애플리케이션에서 사용할 수 있는 Windows 런타임 API와 데스크톱 애플리케이션 및 Windows 스토어 앱에서 호출할 때 해당 동작이 다른지 여부를 이해해야 합니다.

Profiler UI가 관리 코드로 작성된 경우 이러한 Windows 런타임 API를 쉽게 사용하기 위해 수행해야 하는 몇 가지 단계가 있습니다. 자세한 내용은 관리 데스크톱 앱 및 Windows 런타임 문서를 참조하세요.

Profiler DLL 로드

이 섹션에서는 Profiler UI로 인해 Windows 스토어 앱이 Profiler DLL을 로드하는 방식을 설명합니다. 이 섹션에서 설명하는 코드는 Profiler UI 데스크톱 앱에 속하므로, 데스크톱 앱에 안전하지만 Windows 스토어 앱에 반드시 안전하지는 않은 Windows API를 사용해야 합니다.

프로파일러 UI로 인해 Profiler DLL은 애플리케이션의 프로세스 공간에 두 가지 방법으로 로드될 수 있습니다.

  • 환경 변수에 의해 제어되는 애플리케이션 시작 시.

  • ICLRProfiling::AttachProfiler 메서드를 호출하여 시작이 완료된 후 애플리케이션에 연결합니다.

첫 번째 장애물 중 하나는 Windows 스토어 앱에서 제대로 작동하도록 Profiler DLL의 시작 로드 및 연결 부하를 가져오는 것입니다. 두 가지 양식의 로드 모두 공통적으로 몇 가지 특별한 고려 사항을 공유하므로, 이를 먼저 시작하겠습니다.

시작 및 연결 부하에 대한 공통적인 고려 사항

Profiler DLL 서명

Windows에서 Profiler DLL을 로드하려고 할 때 Profiler DLL이 제대로 서명되었는지 확인합니다. 그렇지 않으면 기본적으로 로드가 실패합니다. 여기에는 두 가지 방법이 있습니다.

  • Profiler DLL이 서명되었는지 확인합니다.

  • 도구를 사용하기 전에 Windows 8 컴퓨터에 개발자 라이선스를 설치해야 한다고 사용자에게 알립니다. 이 작업은 Visual Studio에서 자동으로 수행하거나 명령 프롬프트에서 수동으로 수행할 수 있습니다. 자세한 내용은 개발자 라이선스 만들기를 참조하세요.

파일 시스템 사용 권한

Windows 스토어 앱은 상주하는 파일 시스템의 위치에서 Profiler DLL을 로드하고 실행할 수 있는 권한이 있어야 합니다. 기본적으로, Windows 스토어 앱에는 대부분의 디렉터리에 대한 권한이 없으며, Profiler DLL을 로드하지 못하면 다음과 같은 항목이 Windows 애플리케이션 이벤트 로그에 생성됩니다.

NET Runtime version 4.0.30319.17929 - Loading profiler failed during CoCreateInstance.  Profiler CLSID: '{xxxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx}'.  HRESULT: 0x80070005.  Process ID (decimal): 4688.  Message ID: [0x2504].

일반적으로 Windows 스토어 앱은 디스크의 제한된 위치 집합에만 액세스할 수 있습니다. 각 Windows 스토어 앱은 자체 애플리케이션 데이터 폴더뿐만 아니라, 모든 Windows 스토어 앱에 액세스 권한이 부여된 파일 시스템의 몇몇 다른 영역에 액세스할 수 있습니다. 모든 Windows 스토어 앱에는 기본적으로 읽기 및 실행 권한이 있으므로 Profiler DLL 및 해당 종속성을 Program Files 또는 Program Files (x86) 아래 어딘가에 설치하는 것이 가장 좋습니다.

시작 로드

일반적으로 데스크톱 앱에서 Profiler UI는 필요한 CLR 프로파일링 API 환경 변수(즉, COR_PROFILER, COR_ENABLE_PROFILINGCOR_PROFILER_PATH)가 포함된 환경 블록을 초기화한 다음 해당 환경 블록을 사용하여 새 프로세스를 만들어 Profiler DLL의 시작 부하를 프롬프트합니다. Windows 스토어 앱의 경우도 마찬가지이지만 메커니즘은 다릅니다.

상승된 권한으로 실행 안 함

프로세스 A가 Windows 스토어 앱 프로세스 B를 생성하려고 시도하는 경우 프로세스 A는 높은 무결성 수준(즉, 상승된 권한이 아님)이 아닌 중간 무결성 수준에서 실행되어야 합니다. 즉, Profiler UI가 중간 무결성 수준에서 실행되어야 하거나, Windows 스토어 앱 시작을 처리하기 위해 중간 무결성 수준에서 다른 데스크톱 프로세스를 생성해야 합니다.

프로파일링할 Windows 스토어 앱 선택

먼저 프로파일러 사용자에게 어떤 Windows 스토어 앱을 시작할지 물을 것입니다. 데스크톱 앱의 경우 파일 찾아보기 대화 상자를 표시합니다. 사용자가 .exe 파일을 찾아서 선택할 수 있습니다. 그러나 Windows 스토어 앱은 다르며 찾아보기 대화 상자를 사용하는 것이 적합하지 않습니다. 대신 해당 사용자가 선택할 수 있도록 설치된 Windows 스토어 앱 목록을 사용자에게 표시하는 것이 좋습니다.

PackageManager 클래스를 사용하여 이 목록을 생성할 수 있습니다. PackageManager는 데스크톱 앱에서 사용할 수 있는 Windows 런타임 클래스이며 실제로 데스크톱 앱에서만 사용할 수 있습니다.

C#로 작성된 데스크톱 앱으로 작성된 가상 Profiler UI의 다음 코드 예제는 PackageManager를 사용하여 Windows 앱 목록을 사용합니다.

string currentUserSID = WindowsIdentity.GetCurrent().User.ToString();
IAppxFactory appxFactory = (IAppxFactory) new AppxFactory();
PackageManager packageManager = new PackageManager();
IEnumerable<Package> packages = packageManager.FindPackagesForUser(currentUserSID);

사용자 지정 환경 블록 지정

새로운 COM 인터페이스인 IPackageDebugSettings를 사용하면 Windows 스토어 앱의 실행 동작을 사용자 지정하여 일부 형태의 진단을 더 쉽게 만들 수 있습니다. 해당 방법 중 하나인 EnableDebugging을 사용하면 자동 프로세스 일시 중단을 사용하지 않도록 설정하는 것과 같은 다른 유용한 효과와 함께 Windows 스토어 앱이 실행될 때 환경 블록을 앱에 전달할 수 있습니다. 환경 블록은 CLR에서 프로파일러 DLL을 로드하는 데 사용하는 환경 변수(COR_PROFILERCOR_ENABLE_PROFILINGCOR_PROFILER_PATH))를 지정해야 하기 때문에 중요합니다.

다음 코드 조각을 살펴봅니다.

IPackageDebugSettings pkgDebugSettings = new PackageDebugSettings();
pkgDebugSettings.EnableDebugging(packageFullName, debuggerCommandLine,
                                                                 (IntPtr)fixedEnvironmentPzz);

올바르게 이해해야 할 몇 가지 항목이 있습니다.

  • packageFullName은 패키지를 반복하고 package.Id.FullName를 저장하는 과정에서 확인될 수 있습니다.

  • debuggerCommandLine은 조금 더 흥미롭습니다. Windows 스토어 앱으로 사용자 지정 환경 블록을 전달하려면 자신의 단순한 더미 디버거를 작성해야 합니다. Windows는 일시 중단된 Windows 스토어 앱을 생성한 다음, 다음 예제와 같이 명령줄을 사용하여 디버거를 시작하여 디버거를 연결합니다.

    MyDummyDebugger.exe -p 1336 -tid 1424
    

    여기서, -p 1336은 Windows 스토어 앱에 프로세스 ID 1336이 있다는 의미이고, -tid 1424는 스레드 ID 1424가 일시 중단된 스레드임을 의미합니다. 더미 디버거는 명령줄에서 ThreadID를 구문 분석하고 해당 스레드를 다시 시작한 다음, 종료합니다.

    다음은 이 작업을 수행하는 몇 가지 예제 C++ 코드입니다(오류 검사를 추가해야 합니다).

    int wmain(int argc, wchar_t* argv[])
    {
        // …
        // Parse command line here
        // …
    
        HANDLE hThread = OpenThread(THREAD_SUSPEND_RESUME,
                                                                  FALSE /* bInheritHandle */, nThreadID);
        ResumeThread(hThread);
        CloseHandle(hThread);
        return 0;
    }
    

    진단 도구 설치의 일부로 이 더미 디버거를 배포한 다음 debuggerCommandLine 매개 변수에 이 디버거의 경로를 지정해야 합니다.

Windows 스토어 앱 시작

드디어 Windows 스토어 앱을 시작하는 순간이 되었습니다. 이미 이 작업을 직접 시도했다면 CreateProcess가 Windows 스토어 앱 프로세스를 만드는 방법이 아니라는 것을 알 수 있었을 겁니다. 대신 IApplicationActivationManager::ActivateApplication 메서드를 사용해야 합니다. 이렇게 하려면 시작 중인 Windows 스토어 앱의 앱 사용자 모델 ID를 가져와야 합니다. 즉, 매니페스트를 조금 더 자세하게 파악해야 합니다.

패키지를 반복하는 동안(앞의 시작 로드 섹션에서 “프로필에 Windows 스토어 앱 선택” 참조) 현재 패키지의 매니페스트에 포함된 애플리케이션 집합을 확인하려고 합니다.

string manifestPath = package.InstalledLocation.Path + "\\AppxManifest.xml";

AppxPackaging.IStream manifestStream;
SHCreateStreamOnFileEx(
                    manifestPath,
                    0x00000040,     // STGM_READ | STGM_SHARE_DENY_NONE
                    0,              // file creation attributes
                    false,          // fCreate
                    null,           // reserved
                    out manifestStream);

IAppxManifestReader manifestReader = appxFactory.CreateManifestReader(manifestStream);

IAppxManifestApplicationsEnumerator appsEnum = manifestReader.GetApplications();

예. 패키지 하나에 여러 애플리케이션이 있을 수 있고, 각 애플리케이션은 각각의 고유한 애플리케이션 사용자 모듈 ID를 가집니다. 따라서 사용자에게 프로파일링할 애플리케이션을 묻고 해당 특정 애플리케이션에서 애플리케이션 사용자 모델 ID를 확인하려고 합니다.

while (appsEnum.GetHasCurrent() != 0)
{
    IAppxManifestApplication app = appsEnum.GetCurrent();
    string appUserModelId = app.GetAppUserModelId();
    //...
}

마지막으로, 이제 Windows 스토어 앱을 시작하는 데 필요한 것이 있습니다.

IApplicationActivationManager appActivationMgr = new ApplicationActivationManager();
appActivationMgr.ActivateApplication(appUserModelId, appArgs, ACTIVATEOPTIONS.AO_NONE, out pid);

DisableDebugging 호출하기

IPackageDebugSettings::EnableDebugging을 호출했을 때, IPackageDebugSettings::DisableDebugging 메서드를 호출하여 직접 정리할 것을 약속했으므로, 프로파일링 세션이 끝나면 그렇게 하세요.

부하 연결

Profiler UI는 이미 실행을 시작한 애플리케이션에 Profiler DLL을 연결하려고 할 때 ICLRProfiling::AttachProfiler를 사용합니다. Windows 스토어 앱에서도 마찬가지입니다. 그러나 앞에서 나열한 공통적인 고려 사항 외에도 대상 Windows 스토어 앱이 일시 중단되지 않았는지 확인해야 합니다.

EnableDebugging

시작 로드와 마찬가지로, IPackageDebugSettings::EnableDebugging 메서드를 호출합니다. 이것은 환경 블록을 전달하는 데는 필요하지 않지만, 자동 프로세스 일시 중단을 사용하지 않도록 설정하는 다른 기능 중 하나가 필요합니다. 그렇지 않은 경우, Profiler UI가 AttachProfiler를 호출할 때, 대상 Windows 스토어 앱이 일시 중단될 수 있습니다. 실제로 사용자가 현재 Profiler UI와 상호 작용하고 있고 Windows 스토어 앱이 사용자의 화면에서 활성화되지 않은 경우일 수 있습니다. 그리고 Windows 스토어 앱이 일시 중단되면 CLR이 Profiler DLL을 연결하기 위해 전송하는 신호에 응답할 수 없습니다.

따라서 다음과 같은 작업을 수행하려고 합니다.

IPackageDebugSettings pkgDebugSettings = new PackageDebugSettings();
pkgDebugSettings.EnableDebugging(packageFullName, null /* debuggerCommandLine */,
                                                                 IntPtr.Zero /* environment */);

이것은 디버거 명령줄 또는 환경 블록을 지정하지 않는 경우를 제외하면, 시작 로드 사례에 대해 수행되는 것과 동일한 호출입니다.

DisableDebugging

항상 그렇듯이, 프로파일링 세션이 완료될 때 IPackageDebugSettings::DisableDebugging을 호출하는 것을 잊지 마세요.

Windows 스토어 앱 내에서 실행

따라서 Windows 스토어 앱이 마침내 Profiler DLL을 로드했습니다. 이제 Profiler DLL에 허용되는 API와 감소된 사용 권한으로 실행하는 방법을 포함하여, Windows 스토어 앱에 필요한 다양한 규칙에 따라 재생하는 방법을 알려야 합니다.

Windows 스토어 앱 API에 고정

Windows API를 살펴보면 모든 API는 데스크톱 앱, Windows 스토어 앱 또는 둘 다에 적용 가능한 것으로 문서화되었음을 알 수 있습니다. 예를 들어, InitializeCriticalSectionAndSpinCount 함수에 대한 설명서의 요구 사항 섹션은 함수가 데스크톱 앱에만 적용됨을 나타냅니다. 반면, InitializeCriticalSectionEx 함수는 데스크톱 앱과 Windows 스토어 앱 모두에 사용할 수 있습니다.

Profiler DLL을 개발할 때는 이것을 Windows 스토어 앱인 것처럼 취급하고 Windows 스토어 앱에서 사용할 수 있는 것으로 문서화된 API만 사용합니다. 종속성을 분석한 다음(예를 들어, Profiler DLL에 대해 link /dump /imports를 실행하여 감사할 수 있음) 문서를 검색하여 어떤 종속성이 정상이고 어떤 종속성이 그렇지 않은지 확인합니다. 대부분의 경우, 위반은 안전한 것으로 문서화된 최신 형식의 API로 간단히 바꿔서 수정할 수 있습니다(예: InitializeCriticalSectionAndSpinCountInitializeCriticalSectionEx로 대체함).

Profiler DLL은 데스크톱 앱에만 적용되는 일부 API를 호출하는 것을 알 수 있지만, Profiler DLL이 Windows 스토어 앱 내에 로드된 경우에도 작동하는 것처럼 보입니다. Windows 스토어 앱 프로세스에 로드될 때 Profiler DLL에서 Windows 스토어 앱과 함께 사용하기 위해 문서화되지 않은 API를 사용하는 것은 위험하다는 것을 알아두세요.

  • 이러한 API는 Windows 스토어 앱이 실행되는 고유한 컨텍스트에서 호출될 때 작동하도록 보장되지는 않습니다.

  • 이러한 API는 다른 Windows 스토어 앱 프로세스 내에서 호출될 때 일관되게 작동하지 않을 수 있습니다.

  • 이러한 API는 현재 버전의 Windows에서 Windows 스토어 앱에서 잘 작동하는 것처럼 보일 수 있지만 향후 Windows 릴리스에서는 중단되거나 비활성화될 수 있습니다.

가장 좋은 조언은 모든 위반을 수정하고 위험을 피하는 것입니다.

특정 API 없이는 절대로 수행할 수 없으며 Windows 스토어 앱에 적합한 대체 항목을 찾을 수 없다는 것을 알 게 될 것입니다. 이러한 경우 적어도 다음과 같이 하세요.

  • 해당 API 사용량 중의 living daylight를 테스트하고, 테스트하고 또 테스트합니다.

  • 이후 Windows 릴리스에서 Windows 스토어 앱 내부에서 호출되면 API가 갑자기 중단되거나 사라질 수 있음을 이해합니다. 이것은 Microsoft의 호환성 문제로 간주되지 않고, 사용 지원이 우선 순위가 아닙니다.

감소된 사용 권한

Windows 스토어 앱 권한이 데스크톱 앱과 다른 모든 방법을 나열하는 것은 이 항목에서 다루지 않습니다. 그러나 Profiler DLL(데스크톱 앱과 비교하여 Windows 스토어 앱에 로드될 때)이 리소스에 액세스를 시도할 때마다 동작이 달라집니다. 파일 시스템이 가장 일반적인 예입니다. 디스크에는 지정된 Windows 스토어 앱에 액세스할 수 있는 위치가 몇 곳 있지만(파일 액세스 및 권한(Windows 런타임 앱 참조), Profiler DLL에는 동일한 제한사항이 적용됩니다. 코드를 철저히 테스트합니다.

프로세스 간 통신

이 문서의 시작 부분에 있는 다이어그램에 표시된 것처럼, (Windows 스토어 앱 프로세스 공간에 로드된) Profiler DLL은 사용자 자신의 사용자 지정 IPC(프로세스 간 통신) 채널을 통해 (별도의 데스크톱 앱 프로세스 공간에서 실행 중인) Profiler UI와 통신해야 할 수 있습니다. Profiler UI는 프로파일러 DLL에 신호를 보내 해당 동작을 수정하고, Profiler DLL은 분석된 Windows 스토어 앱의 데이터를 다시 Profiler UI로 보내어 후처리하고 프로파일러 사용자에게 표시합니다.

대부분의 프로파일러가 이러한 방식으로 작동해야 하지만, Profiler DLL이 Windows 스토어 앱에 로드될 때 IPC 메커니즘에 대한 선택은 더 제한적입니다. 예를 들어, 명명된 파이프는 Windows 스토어 앱 SDK의 일부가 아니므로 사용할 수 없습니다.

그러나 물론 파일이 여전히 있지만, 더 제한된 방식으로 있습니다. 이벤트도 사용할 수 있습니다.

파일을 통해 통신

대부분의 데이터는 파일을 통해 Profiler DLL과 Profiler UI 간에 전달될 가능성이 높습니다. 키는 Profiler DLL(Windows 스토어 앱의 컨텍스트에서)과 Profiler UI가 모두 읽기 및 쓰기 액세스 권한을 가지는 파일 위치를 선택하는 것입니다. 예를 들어, 임시 폴더 경로는 Profiler DLL과 Profiler UI 모두 액세스할 수 있는 위치이지만, 다른 Windows 스토어 앱 패키지는 액세스할 수 없습니다(따라서 다른 Windows 스토어 앱 패키지에서 로그하는 모든 정보를 보호함).

Profiler UI와 Profiler DLL 모두 이 경로를 독립적으로 결정할 수 있습니다. Profiler UI는 현재 사용자에 대해 설치된 모든 패키지를 반복할 때(이전 샘플 코드 참조) 이 코드 조작과 유사한 코드로 임시 폴더 경로를 파생시킬 수 있는 PackageId 클래스에 액세스할 수 있습니다. (항상 그렇듯이, 간결성을 위해 오류 검사를 생략합니다.)

// C# code for the Profiler UI.
ApplicationData appData =
    ApplicationDataManager.CreateForPackageFamily(
        packageId.FamilyName);

tempDir = appData.TemporaryFolder.Path;

한편, Profiler DLL은 ApplicationData.Current 속성을 사용하여 ApplicationData 클래스에 더 쉽게 접근할 수 있지만, 기본적으로 동일한 작업을 수행할 수 있습니다.

이벤트를 통해 통신

Profiler UI와 Profiler DLL 간에 간단한 신호 의미를 원하는 경우, Windows 스토어 앱뿐만 아니라, 데스크톱 앱 내에서도 이벤트를 사용할 수 있습니다.

Profiler DLL에서 간단히 CreateEventEx 함수를 호출하여 원하는 이름으로 명명된 이벤트를 만들 수 있습니다. 예시:

// Profiler DLL in Windows Store app (C++).
CreateEventEx(
    NULL,  // Not inherited
    "MyNamedEvent"
    CREATE_EVENT_MANUAL_RESET, /* explicit ResetEvent() required; leave initial state unsignaled */
    EVENT_ALL_ACCESS);

그런 다음 Profiler UI는 Windows 스토어 앱의 네임스페이스 아래에서 명명된 이벤트를 찾아야 합니다. 예를 들어, Profiler UI는 CreateEventEx를 호출하여, 이벤트 이름을 다음으로 지정할 수 있습니다.

AppContainerNamedObjects\<acSid>\MyNamedEvent

<acSid>는 Windows 스토어 앱의 AppContainer SID입니다. 이 항목의 이전 섹션에서는 현재 사용자에게 설치된 패키지에 대해 반복하는 방법을 보여 줍니다. 해당 샘플 코드에서 packageId를 얻을 수 있습니다. 또한 packageId에서 다음과 유사한 코드로 <acSid>를 얻을 수 있습니다.

IntPtr acPSID;
DeriveAppContainerSidFromAppContainerName(packageId.FamilyName, out acPSID);

string acSid;
ConvertSidToStringSid(acPSID, out acSid);

string acDir;
GetAppContainerFolderPath(acSid, out acDir);

종료 알림 없음

Windows 스토어 앱 내에서 실행할 때, Profiler DLL은 Windows 스토어 앱이 종료되고 있음을 Profiler DLL에 알리기 위해 호출되는 ICorProfilerCallback::Shutdown 또는 DllMain(DLL_PROCESS_DETACH 포함)을 사용해선 안됩니다. 실제로 이들은 절대 호출되지 않을 것입니다. 지금까지 많은 Profiler DLL은 이러한 알림을 디스크에 캐시를 플러시하고, 파일을 닫고, 알림을 Profiler UI로 다시 보내기 위한 편리한 위치로 사용했습니다. 하지만 이제 Profiler DLL을 약간 다르게 구성해야 합니다.

Profiler DLL은 계속 정보를 로깅해야 합니다. 성능상의 이유로, 메모리의 정보를 일괄 처리하고 일괄 처리가 일부 임계값을 초과하여 크기가 커지면 디스크로 플러시해야 할 수 있습니다. 그러나 디스크에 아직 플러시되지 않은 정보는 손실될 수 있다고 가정합니다. 즉, 임계값을 현명하게 선택해야 하고 Profiler DLL에서 작성한 불완전한 정보를 처리하기 위해 Profiler UI를 강화해야 합니다.

Windows 런타임 메타데이터 파일

이 문서에서는 WinMD(Windows 런타임 메타데이터) 파일이 무엇인지 자세히 다루지 않습니다. 이 섹션은 Profiler DLL이 분석하는 Windows 스토어 앱에서 WinMD 파일을 로드할 때 CLR 프로파일링 API가 반응하는 방식으로 제한됩니다.

관리형 및 비관리형 WinMD

개발자가 Visual Studio를 사용하여 새 Windows 런타임 구성 요소 프로젝트를 만드는 경우 해당 프로젝트의 빌드는 개발자가 작성한 메타데이터(클래스, 인터페이스 등에 대한 형식 설명)를 설명하는 WinMD 파일을 생성합니다. 이 프로젝트가 C# 또는 Visual Basic으로 작성된 관리 언어 프로젝트인 경우 동일한 WinMD 파일에는 해당 형식의 구현도 포함됩니다(즉, 개발자의 소스 코드에서 컴파일된 모든 IL이 포함됨). 이러한 파일을 관리형 WinMD 파일이라고 합니다. 파일에는 Windows 런타임 메타데이터와 기본 구현을 모두 포함하고 있다는 점이 흥미롭습니다.

반면 개발자가 C++용 Windows 런타임 구성 요소 프로젝트를 만드는 경우 해당 프로젝트의 빌드는 메타데이터만 포함하는 WinMD 파일을 생성하고 구현은 별도의 네이티브 DLL로 컴파일됩니다. 마찬가지로 Windows SDK에 제공되는 WinMD 파일에는 메타데이터만 포함되며, 구현은 Windows의 일부로 제공되는 별도의 네이티브 DLL로 컴파일됩니다.

아래 정보는 메타데이터와 구현을 포함하는 관리형 WinMD와 메타데이터만 포함하는 비관리형 WinMD 모두에 적용됩니다.

WinMD 파일은 CLR 모듈처럼 보입니다.

CLR에 관한 한 모든 WinMD 파일은 모듈입니다. 따라서 CLR 프로파일링 API는 WinMD 파일이 로드되는 시기와 ModuleID가 무엇인지를 다른 관리 모듈과 같은 방식으로 Profiler DLL에 알립니다.

Profiler DLL은 ICorProfilerInfo3::GetModuleInfo2 메서드를 호출하고 COR_PRF_MODULE_WINDOWS_RUNTIME 플래그에 대한 pdwModuleFlags 출력 매개 변수를 검사하여 WinMD 파일을 다른 모듈과 구분할 수 있습니다. (이것은 ModuleID가 WinMD를 나타내는 경우에만 설정됩니다.)

WinMD에서 메타데이터 읽기

일반 모듈과 같이, WinMD 파일은 메타데이터 API를 통해 읽을 수 있는 메타데이터를 포함합니다. 그러나 CLR은 WinMD 파일을 읽을 때 Windows 런타임 형식을 .NET Framework 형식에 매핑하므로 관리 코드에서 프로그래밍하고 WinMD 파일을 사용하는 개발자는 보다 자연스러운 프로그래밍 환경을 사용할 수 있습니다. 이러한 매핑의 몇 가지 예는 Windows 스토어 앱 및 Windows 런타임에 대한 .NET Framework 지원을 참조하세요.

그렇다면 프로파일러가 메타데이터 API를 사용할 때 어떤 보기를 가져오나요? 원시 Windows 런타임 보기인가요? 아니면 매핑된 .NET Framework 보기인가요? 대답: 그건 사용자에게 달려 있습니다.

WinMD에서 ICorProfilerInfo::GetModuleMetaData 메서드를 호출하여 IMetaDataImport와 같은 메타데이터 인터페이스를 얻을 때 dwOpenFlags 매개 변수에서 ofNoTransform을 설정하여 이 매핑을 해제할 것을 선택할 수 있습니다. 그렇지 않으면 기본적으로 매핑이 사용하도록 설정됩니다. 일반적으로 프로파일러는 매핑을 사용하도록 설정하여 Profiler DLL이 WinMD 메타데이터에서 얻는 문자열(예: 형식 이름)이 프로파일러 사용자에게 친숙하고 자연스럽게 표시되도록 합니다.

WinMD에서 메타데이터 수정

WinMD에서 메타데이터 수정은 지원되지 않습니다. WinMD 파일에 대해 ICorProfilerInfo::GetModuleMetaData 메서드를 호출하고 dwOpenFlags 매개 변수에서 ofWrite를 지정하거나 IMetaDataEmit과 같은 쓰기 가능한 메타데이터 인터페이스를 요청하면, GetModuleMetaData가 실패합니다. 이는 계측을 지원하기 위해 메타데이터를 수정해야 하는 IL 재작성 프로파일러에 특히 중요합니다(예: AssemblyRefs 또는 새 메서드 추가). 따라서 (이전 섹션에서 논의한 대로) COR_PRF_MODULE_WINDOWS_RUNTIME을 먼저 확인하고 이러한 모듈에서 쓰기 가능한 메타데이터 인터페이스를 요청하지 못하도록 해야 합니다.

WinMD를 사용하여 어셈블리 참조 확인

많은 프로파일러가 계측 또는 형식 검사를 돕기 위해 메타데이터 참조를 수동으로 해결해야 합니다. 이러한 프로파일러는 CLR이 WinMD를 가리키는 어셈블리 참조를 확인하는 방법을 알고 있어야 합니다. 이러한 참조는 표준 어셈블리 참조와는 완전히 다른 방식으로 확인되기 때문입니다.

메모리 프로파일러

가비지 수집기와 관리되는 힙은 Windows 스토어 앱과 데스크톱 앱에서 근본적으로 다르지 않습니다. 그러나 프로파일러 작성자가 알아야 할 몇 가지 미묘한 차이점이 있습니다.

ForceGC는 관리되는 스레드를 만듭니다.

메모리 프로파일링을 수행할 때 Profiler DLL은 일반적으로 ForceGC Method 메서드를 호출할 별도의 스레드를 만듭니다. 이것은 새로운 것이 없습니다. 그러나 놀라운 점은 Windows 스토어 앱 내에서 가비지 수집을 수행하는 행위가 스레드를 관리되는 스레드로 변환할 수 있다는 것입니다(예: 해당 스레드에 대해 프로파일링 API ThreadID가 생성됨).

이러한 결과를 이해하려면 CLR 프로파일링 API에 정의된 동기 호출과 비동기 호출 간의 차이점을 이해하는 것이 중요합니다. 이것은 Windows 스토어 앱의 비동기 호출 개념과는 매우 다릅니다. 자세한 내용은 블로그 게시물 CORPROF_E_UNSUPPORTED_CALL_SEQUENCE가 있는 이유를 참조하세요.

관련점은 프로파일러가 만든 스레드에서 수행한 호출이 Profiler DLL의 ICorProfilerCallback 메서드 중 하나의 구현 외부에서 호출되었더라도, 항상 동기로 간주된다는 것입니다. 적어도, 그 경우에 사용되었습니다. 이제 CLR은 ForceGC 메서드에 대한 호출 때문에 CLR이 프로파일러의 스레드를 관리 스레드로 전환했으므로, 스레드는 더 이상 프로파일러의 스레드로 간주되지 않습니다. 따라서 CLR은 해당 스레드에 대해 동기로 한정되는 항목에 대해 더 엄격한 정의를 적용합니다. 즉, 호출이 동기로 한정되도록 하려면 Profiler DLL의 ICorProfilerCallback 메서드 중 하나에서 시작되어야 합니다.

실제로 어떤 의미인가요? 대부분의 ICorProfilerInfo 메서드는 동기식으로 호출되는 것만 안전하고, 그렇지 않으면 즉시 실패합니다. 따라서 Profiler DLL이 일반적으로 프로파일러가 만든 스레드에 대해 수행된 다른 호출(예: RequestProfilerDetach, RequestReJIT 또는 RequestRevert)에 대해 ForceGC 메서드 스레드를 다시 사용하는 경우 문제가 발생합니다. 심지어 DoStackSnapshot과 같은 비동기 안전 함수는 관리 스레드에서 호출될 때 특별 규칙이 있습니다. (자세한 내용은 블로그 게시물 Profiler 스택 워크: 기본 사항 및 그 이상을 참조하세요.)

따라서 Profiler DLL이 ForceGC 메서드를 호출하기 위해 만든 스레드는 GC를 트리거한 다음 GC 콜백에 응답하기 위한 목적으로만 사용하는 것이 좋습니다. 스택 샘플링 또는 분리와 같은 다른 작업을 수행하기 위해 프로파일링 API를 호출해서는 안 됩니다.

ConditionalWeakTableReferences

.NET Framework 4.5부터 프로파일러에 종속 핸들에 대한 보다 완전한 정보를 제공하는 새로운 GC 콜백인 ConditionalWeakTableElementReferences가 있습니다. 이러한 핸들은 GC 수명 관리를 위해 원본 개체의 참조를 대상 개체에 효과적으로 추가합니다. 종속 핸들은 새로운 것이 없습니다. 관리 코드에서 프로그래밍하는 개발자는 Windows 8 및 .NET Framework 4.5 이전에서도 System.Runtime.CompilerServices.ConditionalWeakTable<TKey,TValue> 클래스를 사용하여 자체 종속 핸들을 만들 수 있었습니다.

그러나 관리형 XAML Windows 스토어 앱은 이제 종속 핸들을 많이 사용합니다. 특히 CLR은 이러한 핸들을 사용하여 관리되는 개체와 관리되지 않는 Windows 런타임 개체 간의 참조 주기를 관리하는 데 도움을 줍니다. 즉, 메모리 프로파일러가 힙 그래프의 나머지 에지와 함께 시각화할 수 있도록 이러한 종속 핸들에 대한 정보를 제공하는 것이 그 어느 때보다 중요해졌습니다. Profiler DLL은 RootReferences2, ObjectReferencesConditionalWeakTableElementReferences를 함께 사용하여 힙 그래프의 전체 보기를 형성해야 합니다.

결론

CLR 프로파일링 API를 사용하여 Windows 스토어 앱 내에서 실행되는 관리 코드를 분석할 수 있습니다. 실제로 개발 중인 기존 프로파일러를 가져와서 Windows 스토어 앱을 대상으로 지정할 수 있도록 몇 가지 특정 변경을 수행할 수 있습니다. Profiler UI는 디버깅 모드에서 Windows 스토어 앱을 활성화하기 위해 새 API를 사용해야 합니다. Profiler DLL이 Windows 스토어 앱에 적용 가능한 API만 사용하는지 확인합니다. Profiler DLL과 Profiler UI 간의 통신 메커니즘은 Windows 스토어 앱 API 제한을 염두에 두고 Windows 스토어 앱에 대한 제한된 사용 권한을 인식하여 작성해야 합니다. Profiler DLL은 CLR이 WinMD를 처리하는 방법과 관리되는 스레드와 관련하여 가비지 수집기의 동작이 어떻게 다른지 알고 있어야 합니다.

리소스

공용 언어 런타임(CLR)

CLR의 Windows 런타임과 상호 작용

Windows 스토어 앱