CLR Profilers och Windows Store-appar
I det här avsnittet beskrivs vad du behöver tänka på när du skriver diagnostikverktyg som analyserar hanterad kod som körs i en Windows Store-app. Den innehåller också riktlinjer för att ändra dina befintliga utvecklingsverktyg så att de fortsätter att fungera när du kör dem mot Windows Store-appar. För att förstå den här informationen är det bäst om du är bekant med API:et för Common Language Runtime-profilering, du har redan använt det här API:et i ett diagnostikverktyg som körs korrekt mot Windows-skrivbordsprogram, och du är nu intresserad av att ändra verktyget så att det körs korrekt mot Windows Store-appar.
Introduktion
Om du kom förbi det inledande stycket är du bekant med CLR-profilerings-API:et. Du har redan skrivit ett diagnostikverktyg som fungerar bra mot hanterade skrivbordsprogram. Nu är du nyfiken på vad du ska göra så att verktyget fungerar med en hanterad Windows Store-app. Du kanske redan har försökt att få det här att fungera och har upptäckt att det inte är en enkel uppgift. Det finns faktiskt ett antal överväganden som kanske inte är uppenbara för alla verktygsutvecklare. Till exempel:
Windows Store-appar körs i en kontext med kraftigt begränsade behörigheter.
Windows Metadata-filer har unika egenskaper jämfört med traditionella hanterade moduler.
Windows Store-appar har en vana att pausa sig själva när interaktiviteten minskar.
Dina kommunikationsmekanismer mellan processer kanske inte längre fungerar av olika skäl.
I det här avsnittet visas de saker du behöver känna till och hur du hanterar dem på rätt sätt.
Om du är nybörjare på CLR-profilerings-API:et går du ned till Resurserna i slutet av det här avsnittet för att hitta bättre introduktionsinformation.
Information om specifika Windows-API:er och hur de ska användas ligger också utanför omfånget för det här avsnittet. Se det här avsnittet som en startpunkt och läs MER om eventuella Windows-API:er som refereras här i MSDN.
Arkitektur och terminologi
Vanligtvis har ett diagnostikverktyg en arkitektur som den som visas i följande bild. Den använder termen "profilerare", men många sådana verktyg går långt utöver typisk prestanda- eller minnesprofilering i områden som kodtäckning, mock object frameworks, time-travel debugging, application monitoring och så vidare. För enkelhetens skull fortsätter det här avsnittet att referera till alla dessa verktyg som profilerare.
Följande terminologi används i hela det här avsnittet:
Program
Det här är det program som profileraren analyserar. Normalt använder utvecklaren av det här programmet nu profileraren för att diagnostisera problem med programmet. Traditionellt sett skulle det här programmet vara ett Windows-skrivbordsprogram, men i det här avsnittet tittar vi på Windows Store-appar.
Profilerar-DLL
Det här är komponenten som läses in i processutrymmet för det program som analyseras. Den här komponenten, även kallad profilerarens "agent", implementerar gränssnitten ICorProfilerCallbackICorProfilerCallback Interface(2,3 osv.) och använder gränssnitten ICorProfilerInfo(2,3 osv.) för att samla in data om det analyserade programmet och eventuellt ändra aspekter av programmets beteende.
Profilerar-användargränssnitt
Det här är ett skrivbordsprogram som profileraren interagerar med. Den ansvarar för att visa programstatus för användaren och ge användaren möjlighet att styra beteendet för det analyserade programmet. Den här komponenten körs alltid i sitt eget processutrymme, separat från processutrymmet för det program som profileras. Användargränssnittet Profiler kan också fungera som "attach trigger", vilket är den process som anropar metoden ICLRProfiling::AttachProfiler för att få det analyserade programmet att läsa in Profiler DLL i de fall där profilerar-DLL inte lästes in vid start.
Viktigt!
Ditt Profiler-användargränssnitt bör förbli ett Windows-skrivbordsprogram, även när det används för att styra och rapportera i en Windows Store-app. Förvänta dig inte att kunna paketera och skicka diagnostikverktyget i Windows Store. Ditt verktyg måste göra saker som Windows Store-appar inte kan göra, och många av dessa saker finns i ditt Profiler-användargränssnitt.
I det här dokumentet förutsätter exempelkoden att:
Din Profiler-DLL är skriven i C++, eftersom den måste vara en inbyggd DLL enligt kraven i CLR-profilerings-API:et.
Ditt Profiler-användargränssnitt är skrivet i C#. Detta är inte nödvändigt, men eftersom det inte finns några krav på språket för profileringsgränssnittets process, varför inte välja ett språk som är koncist och enkelt?
Windows RT-enheter
Windows RT-enheter är ganska låsta. Profilerare från tredje part kan helt enkelt inte läsas in på sådana enheter. Det här dokumentet fokuserar på Windows 8-datorer.
Använda Windows Runtime-API:er
I ett antal scenarier som beskrivs i följande avsnitt måste ditt Profiler UI-skrivbordsprogram använda några nya Windows Runtime-API:er. Du vill läsa dokumentationen för att förstå vilka Windows Runtime-API:er som kan användas från skrivbordsprogram och om deras beteende skiljer sig från skrivbordsprogram och Windows Store-appar.
Om ditt Profiler-användargränssnitt är skrivet i hanterad kod finns det några steg du behöver göra för att göra det enkelt att använda dessa Windows Runtime-API:er. Mer information finns i artikeln Hanterade skrivbordsappar och Windows Runtime .
Läsa in profilerar-DLL:en
I det här avsnittet beskrivs hur ditt Profiler-användargränssnitt gör att Windows Store-appen läser in din Profiler-DLL. Koden som beskrivs i det här avsnittet tillhör din Profiler UI-skrivbordsapp och omfattar därför användning av Windows-API:er som är säkra för skrivbordsappar men inte nödvändigtvis säkra för Windows Store-appar.
Ditt Profiler-användargränssnitt kan göra så att din Profiler-DLL läses in i programmets processutrymme på två sätt:
Vid programstart, som styrs av miljövariabler.
Genom att ansluta till programmet efter att starten har slutförts genom att anropa metoden ICLRProfiling::AttachProfiler .
En av dina första hinder är att få startbelastning och anslutning av din Profiler DLL att fungera korrekt med Windows Store-appar. Båda typerna av inläsning har vissa särskilda överväganden gemensamt, så vi börjar med dem.
Vanliga överväganden vid start och anslutning av belastningar
Signera din Profiler-DLL
När Windows försöker läsa in din Profiler DLL verifierar det att din Profiler DLL är korrekt signerad. Annars misslyckas belastningen som standard. Det finns två sätt att göra detta på:
Kontrollera att din Profiler-DLL är signerad.
Berätta för användaren att de måste installera en utvecklarlicens på sin Windows 8-dator innan du använder verktyget. Detta kan göras automatiskt från Visual Studio eller manuellt från en kommandotolk. Mer information finns i Hämta en utvecklarlicens.
Filsystembehörigheter
Windows Store-appen måste ha behörighet att läsa in och köra din Profiler-DLL från platsen i filsystemet där den finns Som standard, Windows Store-appen har inte sådan behörighet på de flesta kataloger och eventuella misslyckade försök att läsa in din Profiler-DLL skapar en post i händelseloggen för Windows-programmet som ser ut ungefär så här:
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].
I allmänhet tillåts Windows Store-appar endast komma åt en begränsad uppsättning platser på disken. Varje Windows Store-app har åtkomst till sina egna programdatamappar samt några andra områden i filsystemet som alla Windows Store-appar beviljas åtkomst till. Det är bäst att installera din Profiler-DLL och dess beroenden någonstans under Programfiler eller Programfiler (x86), eftersom alla Windows Store-appar har läs- och körningsbehörigheter där som standard.
Startbelastning
I en skrivbordsapp uppmanar ditt Profiler-användargränssnitt vanligtvis en startbelastning av din Profiler DLL genom att initiera ett miljöblock som innehåller de obligatoriska miljövariablerna för CLR-profilerings-API :et (t.ex. COR_PROFILER
, , COR_ENABLE_PROFILING
och ) och COR_PROFILER_PATH
sedan skapa en ny process med det miljöblocket. Detsamma gäller för Windows Store-appar, men mekanismerna skiljer sig åt.
Kör inte förhöjd
Om Process A försöker skapa Windows Store-appen Process B ska process A köras på medelhög integritetsnivå, inte på hög integritetsnivå (det vill s.v.s. inte förhöjd). Det innebär att ditt Profiler-användargränssnitt antingen ska köras på medelhög integritetsnivå eller att det måste skapa en annan skrivbordsprocess på medelhög integritetsnivå för att kunna starta Windows Store-appen.
Välja en Windows Store-app att profilera
Först vill du be profileraren vilken Windows Store-app som ska startas. För skrivbordsappar kanske du skulle visa en fil i dialogrutan Bläddra och användaren skulle hitta och välja en .exe fil. Men Windows Store-appar skiljer sig åt, och det är inte meningsfullt att använda en bläddra-dialogruta. I stället är det bättre att visa användaren en lista över Windows Store-appar som är installerade för användaren att välja mellan.
Du kan använda PackageManager klassen för att generera den här listan. PackageManager
är en Windows Runtime-klass som är tillgänglig för skrivbordsappar, och i själva verket är den endast tillgänglig för skrivbordsappar.
Följande kodexempel från ett hypotetiskt Profiler-användargränssnitt skrivet som en skrivbordsapp i C# använder PackageManager
för att generera en lista över Windows-appar:
string currentUserSID = WindowsIdentity.GetCurrent().User.ToString();
IAppxFactory appxFactory = (IAppxFactory) new AppxFactory();
PackageManager packageManager = new PackageManager();
IEnumerable<Package> packages = packageManager.FindPackagesForUser(currentUserSID);
Ange det anpassade miljöblocket
Med ett nytt COM-gränssnitt, IPackageDebug Inställningar, kan du anpassa körningsbeteendet för en Windows Store-app för att göra vissa former av diagnostik enklare. En av dess metoder, EnableDebugging, låter dig skicka ett miljöblock till Windows Store-appen när den startas, tillsammans med andra användbara effekter som att inaktivera automatisk processavstängning. Miljöblocket är viktigt eftersom det är där du behöver ange de miljövariabler (COR_PROFILER
, COR_ENABLE_PROFILING
och COR_PROFILER_PATH)
) som används av CLR för att läsa in din Profiler-DLL.
Överväg följande kodfragment:
IPackageDebugSettings pkgDebugSettings = new PackageDebugSettings();
pkgDebugSettings.EnableDebugging(packageFullName, debuggerCommandLine,
(IntPtr)fixedEnvironmentPzz);
Det finns ett par objekt som du behöver för att få rätt:
packageFullName
kan fastställas när du itererar över paketen och tar tag ipackage.Id.FullName
.debuggerCommandLine
är lite mer intressant. För att kunna skicka det anpassade miljöblocket till Windows Store-appen måste du skriva ett eget, förenklat dummy-felsökningsprogram. Windows skapar windows store-appen pausad och bifogar sedan felsökningsprogrammet genom att starta felsökningsprogrammet med en kommandorad som i det här exemplet:MyDummyDebugger.exe -p 1336 -tid 1424
where
-p 1336
means the Windows Store app has Process ID 1336, and-tid 1424
means Thread ID 1424 is the thread that is suspended. Ditt dummy-felsökningsprogram parsar ThreadID från kommandoraden, återupptar tråden och avslutar sedan.Här är ett exempel på C++-kod för att göra detta (se till att lägga till felkontroll!):
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; }
Du måste distribuera det här dummy-felsökningsprogrammet som en del av installationen av diagnostikverktyget och sedan ange sökvägen till det här felsökningsprogrammet i parametern
debuggerCommandLine
.
Starta Windows Store-appen
Nu har det äntligen kommit att starta Windows Store-appen. Om du redan har provat att göra detta själv kanske du har märkt att CreateProcess inte är hur du skapar en Windows Store-appprocess. I stället måste du använda metoden IApplicationActivationManager::ActivateApplication . För att göra det måste du hämta appanvändarmodell-ID:t för den Windows Store-app som du startar. Och det betyder att du måste gräva lite genom manifestet.
När du itererar över dina paket (se "Välja en Windows Store-app till profil" i avsnittet Startinläsning tidigare) vill du hämta den uppsättning program som finns i det aktuella paketets manifest:
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();
Ja, ett paket kan ha flera program och varje program har ett eget programanvändarmodell-ID. Så du vill be användaren vilket program som ska profileras och hämta programanvändarmodell-ID:t från just det programmet:
while (appsEnum.GetHasCurrent() != 0)
{
IAppxManifestApplication app = appsEnum.GetCurrent();
string appUserModelId = app.GetAppUserModelId();
//...
}
Slutligen har du nu det du behöver för att starta Windows Store-appen:
IApplicationActivationManager appActivationMgr = new ApplicationActivationManager();
appActivationMgr.ActivateApplication(appUserModelId, appArgs, ACTIVATEOPTIONS.AO_NONE, out pid);
Kom ihåg att anropa DisableDebugging
När du anropade IPackageDebug Inställningar::EnableDebugging gav du ett löfte om att du skulle rensa efter dig själv genom att anropa IPackageDebug Inställningar::D isableDebugging-metoden, så se till att göra det när profileringssessionen är över.
Koppla inläsning
När ditt Profiler-användargränssnitt vill koppla sin Profiler-DLL till ett program som redan har börjat köras använder det ICLRProfiling::AttachProfiler. Detsamma gäller för Windows Store-appar. Men förutom de vanliga överväganden som anges tidigare kontrollerar du att Windows Store-målappen inte är pausad.
EnableDebugging
Precis som med startbelastning anropar du metoden IPackageDebug Inställningar::EnableDebugging. Du behöver den inte för att skicka ett miljöblock, men du behöver en av dess andra funktioner: inaktivera automatisk processavstängning. Annars kan windows Store-målappen pausas när ditt Profiler-användargränssnitt anropar AttachProfiler. Faktum är att detta är troligt om användaren nu interagerar med ditt Profiler-användargränssnitt och Windows Store-appen inte är aktiv på någon av användarens skärmar. Och om Windows Store-appen är avstängd kan den inte svara på någon signal som CLR skickar till den för att bifoga din Profiler DLL.
Så du vill göra något liknande:
IPackageDebugSettings pkgDebugSettings = new PackageDebugSettings();
pkgDebugSettings.EnableDebugging(packageFullName, null /* debuggerCommandLine */,
IntPtr.Zero /* environment */);
Det här är samma anrop som du skulle göra för startinläsningsfallet, förutom att du inte anger någon felsökningskommandorad eller ett miljöblock.
DisableDebugging
Glöm som alltid inte att anropa IPackageDebug Inställningar::D isableDebugging när profileringssessionen har slutförts.
Körs i Windows Store-appen
Så Windows Store-appen har äntligen läst in din Profiler DLL. Nu måste din Profiler-DLL lära sig att spela upp med de olika regler som krävs av Windows Store-appar, inklusive vilka API:er som är tillåtna och hur du kör med nedsatt behörighet.
Håll dig till Windows Store-app-API:er
När du bläddrar i Windows-API:et ser du att varje API dokumenteras som tillämpligt för skrivbordsappar, Windows Store-appar eller både och. Avsnittet Krav i dokumentationen för funktionen InitializeCriticalSectionAndSpinCount anger till exempel att funktionen endast gäller för skrivbordsappar. Däremot är funktionen InitializeCriticalSectionEx tillgänglig för både skrivbordsappar och Windows Store-appar.
När du utvecklar din Profiler-DLL behandlar du det som om det är en Windows Store-app och endast använder API:er som dokumenteras som tillgängliga för Windows Store-appar. Analysera dina beroenden (du kan till exempel köra link /dump /imports
mot din Profiler DLL för granskning) och sök sedan i dokumenten för att se vilka av dina beroenden som är ok och vilka som inte är det. I de flesta fall kan dina överträdelser åtgärdas genom att helt enkelt ersätta dem med en nyare form av API:et som dokumenteras som säkert (till exempel ersätta InitializeCriticalSectionAndSpinCount med InitializeCriticalSectionEx).
Du kanske märker att din Profiler-DLL anropar vissa API:er som endast gäller för skrivbordsappar, och ändå verkar de fungera även när din Profiler-DLL läses in i en Windows Store-app. Tänk på att det är riskabelt att använda ett API som inte är dokumenterat för användning med Windows Store-appar i din Profiler-DLL när det läses in i en Windows Store-appprocess:
Sådana API:er fungerar inte garanterat när de anropas i den unika kontext som Windows Store-appar körs i.
Sådana API:er kanske inte fungerar konsekvent när de anropas inifrån olika Windows Store-appprocesser.
Sådana API:er kan verka fungera bra från Windows Store-appar i den aktuella versionen av Windows, men kan brytas eller inaktiveras i framtida versioner av Windows.
Det bästa rådet är att åtgärda alla dina överträdelser och undvika risken.
Du kanske upptäcker att du absolut inte kan göra utan ett visst API och inte kan hitta en ersättning som är lämplig för Windows Store-appar. I ett sådant fall minst:
Testa, testa, testa det levande dagsljuset från din användning av api:et.
Förstå att API:et plötsligt kan brytas eller försvinna om det anropas inifrån Windows Store-appar i framtida versioner av Windows. Detta kommer inte att betraktas som ett kompatibilitetsproblem av Microsoft, och stöd för din användning av det kommer inte att vara en prioritet.
Begränsade behörigheter
Det är utanför omfånget för det här avsnittet för att visa en lista över alla sätt som Windows Store-appbehörigheter skiljer sig från skrivbordsappar. Men visst kommer beteendet att vara annorlunda varje gång din Profiler DLL (när den läses in i en Windows Store-app jämfört med en skrivbordsapp) försöker komma åt alla resurser. Filsystemet är det vanligaste exemplet. Det finns bara några platser på disken som en viss Windows Store-app har åtkomst till (se Filåtkomst och behörigheter (Windows Runtime-appar) och din Profiler-DLL kommer att omfattas av samma begränsningar. Testa koden noggrant.
Kommunikation mellan processer
Som du ser i diagrammet i början av det här dokumentet behöver din Profiler-DLL (som läses in i Windows Store-appprocessutrymmet) sannolikt kommunicera med ditt Profiler-användargränssnitt (som körs i ett separat skrivbordsappprocessutrymme) via din egen anpassade IPC-kanal (Inter-Process Communication). Profiler-användargränssnittet skickar signaler till Profiler DLL för att ändra dess beteende, och Profiler DLL skickar data från den analyserade Windows Store-appen tillbaka till Profiler-användargränssnittet för efterbearbetning och visning för profileraren.
De flesta profilerare måste fungera på det här sättet, men dina val för IPC-mekanismer är mer begränsade när din Profiler-DLL läses in i en Windows Store-app. Namngivna pipes ingår till exempel inte i Windows Store-appens SDK, så du kan inte använda dem.
Men naturligtvis är filerna fortfarande i, om än på ett mer begränsat sätt. Händelser är också tillgängliga.
Kommunicera via filer
De flesta av dina data skickas sannolikt mellan Profiler DLL- och Profiler-användargränssnittet via filer. Nyckeln är att välja en filplats som både din Profiler DLL (i kontexten för en Windows Store-app) och Profiler-användargränssnittet har läs- och skrivåtkomst till. Den tillfälliga mappsökvägen är till exempel en plats som både ditt Profiler DLL- och Profiler-användargränssnitt kan komma åt, men inget annat Windows Store-apppaket kan komma åt (vilket avskärmar all information som du loggar från andra Windows Store-apppaket).
Både ditt Profiler-användargränssnitt och Profiler DLL kan bestämma den här sökvägen oberoende av varandra. Ditt Profiler-användargränssnitt, när det itererar genom alla paket som installerats för den aktuella användaren (se exempelkoden tidigare), får åtkomst till PackageId
klassen, från vilken sökvägen till den tillfälliga mappen kan härledas med kod som liknar det här kodfragmentet. (Som alltid utelämnas felkontroll för korthet.)
// C# code for the Profiler UI.
ApplicationData appData =
ApplicationDataManager.CreateForPackageFamily(
packageId.FamilyName);
tempDir = appData.TemporaryFolder.Path;
Under tiden kan din Profiler DLL göra i princip samma sak, även om det enklare kan komma till ApplicationData klassen med hjälp av egenskapen ApplicationData.Current .
Kommunicera via händelser
Om du vill ha enkel signalsemantik mellan ditt Profiler-användargränssnitt och Profiler DLL kan du använda händelser i Windows Store-appar samt skrivbordsappar.
Från din Profiler DLL kan du helt enkelt anropa funktionen CreateEventEx för att skapa en namngiven händelse med valfritt namn. Till exempel:
// 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);
Användargränssnittet för Profiler måste sedan hitta den namngivna händelsen under Windows Store-appens namnområde. Ditt Profiler-användargränssnitt kan till exempel anropa CreateEventEx och ange händelsenamnet som
AppContainerNamedObjects\<acSid>\MyNamedEvent
<acSid>
är Windows Store-appens AppContainer SID. Ett tidigare avsnitt i det här avsnittet visade hur du itererar över de paket som installerats för den aktuella användaren. Från den exempelkoden kan du hämta packageId. Och från packageId kan du hämta <acSid>
med kod som liknar följande:
IntPtr acPSID;
DeriveAppContainerSidFromAppContainerName(packageId.FamilyName, out acPSID);
string acSid;
ConvertSidToStringSid(acPSID, out acSid);
string acDir;
GetAppContainerFolderPath(acSid, out acDir);
Inga avstängningsmeddelanden
När du kör i en Windows Store-app bör din Profiler DLL inte förlita sig på att antingen ICorProfilerCallback::Shutdown eller ens DllMain (med DLL_PROCESS_DETACH
) anropas för att meddela din Profiler DLL att Windows Store-appen avslutas. Faktum är att du bör förvänta dig att de aldrig kommer att kallas. Tidigare har många Profiler-DLL:er använt dessa meddelanden som praktiska platser för att rensa cacheminnen till diskar, stänga filer, skicka meddelanden tillbaka till Profiler-användargränssnittet osv. Men nu måste din Profiler DLL organiseras lite annorlunda.
Din Profiler-DLL bör logga information allt eftersom. Av prestandaskäl kanske du vill batchinformation i minnet och tömma den till disk när batchen växer i storlek efter ett visst tröskelvärde. Men anta att all information som ännu inte har tömts till disken kan gå förlorad. Det innebär att du vill välja tröskelvärdet på ett klokt sätt och att ditt Profiler-användargränssnitt måste härdas för att hantera ofullständig information som skrivits av Profiler DLL.
Windows Runtime-metadatafiler
Det ligger utanför omfånget för det här dokumentet för att gå in på detaljer om vilka WinMD-filer (Windows Runtime-metadata) som finns. Det här avsnittet är begränsat till hur CLR-profilerings-API:et reagerar när WinMD-filer läses in av Windows Store-appen som din Profiler DLL analyserar.
Hanterade och icke-hanterade WinMD:ar
Om en utvecklare använder Visual Studio för att skapa ett nytt Windows Runtime-komponentprojekt skapar en version av projektet en WinMD-fil som beskriver metadata (typbeskrivningar av klasser, gränssnitt osv.) som skapats av utvecklaren. Om det här projektet är ett projekt för hanterat språk skrivet i C# eller Visual Basic, innehåller samma WinMD-fil även implementeringen av dessa typer (vilket innebär att den innehåller all IL som kompilerats från utvecklarens källkod). Sådana filer kallas hanterade WinMD-filer. De är intressanta eftersom de innehåller både Windows Runtime-metadata och den underliggande implementeringen.
Om en utvecklare däremot skapar ett Windows Runtime Component-projekt för C++, skapar en version av projektet en WinMD-fil som endast innehåller metadata och implementeringen kompileras till en separat intern DLL. På samma sätt innehåller WinMD-filerna som levereras i Windows SDK endast metadata, med implementeringen kompilerad till separata interna DLL:er som levereras som en del av Windows.
Informationen nedan gäller både hanterade WinMD:er, som innehåller metadata och implementering, och för icke-hanterade WinMD:er, som endast innehåller metadata.
WinMD-filer ser ut som CLR-moduler
När det gäller CLR är alla WinMD-filer moduler. CLR-profilerings-API:et talar därför om för din Profiler-DLL när WinMD-filer läses in och vilka deras ModuleID:er är, på samma sätt som för andra hanterade moduler.
Din Profiler-DLL kan skilja WinMD-filer från andra moduler genom att anropa metoden ICorProfilerInfo3::GetModuleInfo2 och inspektera utdataparametern pdwModuleFlags
för flaggan COR_PRF_MODULE_WINDOWS_RUNTIME . (Den anges om och endast om ModuleID representerar en WinMD.)
Läsa metadata från WinMDs
WinMD-filer, till exempel vanliga moduler, innehåller metadata som kan läsas via API:er för metadata. CLR mappar dock Windows Runtime-typer till .NET Framework-typer när de läser WinMD-filer så att utvecklare som programmerar i hanterad kod och använder WinMD-filen kan ha en mer naturlig programmeringsupplevelse. Några exempel på dessa mappningar finns i .NET Framework-stöd för Windows Store-appar och Windows Runtime.
Så vilken vy får profileraren när den använder metadata-API:erna: den råa Windows Runtime-vyn eller den mappade .NET Framework-vyn? Svaret: det är upp till dig.
När du anropar metoden ICorProfilerInfo::GetModuleMetaData på en WinMD för att hämta ett metadatagränssnitt, till exempel IMetaDataImport, kan du välja att angeNoTransformi parametern dwOpenFlags
för att inaktivera den här mappningen. Annars aktiveras mappningen som standard. Vanligtvis håller en profilerare mappningen aktiverad, så att strängarna som Profiler DLL hämtar från WinMD-metadata (till exempel namn på typer) ser bekanta och naturliga ut för profileraren.
Ändra metadata från WinMDs
Det går inte att ändra metadata i WinMDs. Om du anropar metoden ICorProfilerInfo::GetModuleMetaData för en WinMD-fil och anger ofWrite i parametern dwOpenFlags
eller ber om ett skrivbart metadatagränssnitt, till exempel IMetaDataEmit, misslyckas GetModuleMetaData . Detta är särskilt viktigt för IL-omskrivning profilerare, som behöver ändra metadata för att stödja deras instrumentation (till exempel för att lägga till AssemblyRefs eller nya metoder). Du bör därför söka efter COR_PRF_MODULE_WINDOWS_RUNTIME först (enligt beskrivningen i föregående avsnitt) och avstå från att be om skrivbara metadatagränssnitt i sådana moduler.
Lösa sammansättningsreferenser med WinMDs
Många profilerare behöver lösa metadatareferenser manuellt för att underlätta instrumentation eller typkontroll. Sådana profilerare måste vara medvetna om hur CLR löser sammansättningsreferenser som pekar på WinMDs, eftersom dessa referenser löses på ett helt annat sätt än standardsammansättningsreferenser.
Minnesprofiler
Skräpinsamlaren och den hanterade heapen skiljer sig inte i grunden i en Windows Store-app och en skrivbordsapp. Det finns dock vissa subtila skillnader som profilerare måste vara medvetna om.
ForceGC skapar en hanterad tråd
När du gör minnesprofilering skapar din Profiler DLL vanligtvis en separat tråd som du kan anropa metoden ForceGC-metod från. Det här är inget nytt. Men vad som kan vara förvånande är att åtgärden att göra en skräpinsamling i en Windows Store-app kan omvandla tråden till en hanterad tråd (till exempel skapas ett PROFILERings-API ThreadID för den tråden).
För att förstå konsekvenserna av detta är det viktigt att förstå skillnaderna mellan synkrona och asynkrona anrop enligt definitionen i CLR-profilerings-API:et. Observera att detta skiljer sig mycket från begreppet asynkrona anrop i Windows Store-appar. Mer information finns i blogginlägget Varför vi har CORPROF_E_UNSUPPORTED_CALL_SEQUENCE .
Den relevanta punkten är att anrop som görs på trådar som skapats av profileraren alltid betraktas som synkrona, även om dessa anrop görs utanför en implementering av någon av profilerar-DLL:ernas ICorProfilerCallback-metoder . Det brukade åtminstone vara fallet. Nu när CLR har förvandlat profilerarens tråd till en hanterad tråd på grund av ditt anrop till ForceGC-metoden anses den tråden inte längre vara profilerarens tråd. Därför framtvingar CLR en strängare definition av vad som kvalificerar sig som synkront för den tråden, nämligen att ett anrop måste komma inifrån någon av profilerings-DLL:ernas ICorProfilerCallback-metoder för att kvalificera sig som synkrona.
Vad innebär detta i praktiken? De flesta ICorProfilerInfo-metoder är bara säkra att anropa synkront och misslyckas omedelbart annars. Så om din Profiler DLL återanvänder din ForceGC-metodtråd för andra anrop som vanligtvis görs på profileringsskapade trådar (till exempel till RequestProfilerDetach, RequestReJIT eller RequestRevert), kommer du att få problem. Även en asynkron-säker funktion som DoStackSnapshot har särskilda regler när den anropas från hanterade trådar. (Se blogginlägget Profiler stack walking: Grundläggande och bortom för mer information.)
Därför rekommenderar vi att alla trådar som din Profiler DLL skapar för att anropa ForceGC-metoden endast ska användas för att utlösa GC:er och sedan svara på återanrop från GC. Den bör inte anropa profilerings-API:et för att utföra andra uppgifter som stacksampling eller frånkoppling.
ConditionalWeakTableReferences
Från och med .NET Framework 4.5 finns det ett nytt GC-återanrop, ConditionalWeakTableElementReferences, som ger profileraren mer fullständig information om beroende handtag. Dessa referenser lägger effektivt till en referens från ett källobjekt till ett målobjekt för GC-livslängdshantering. Beroende referenser är inget nytt, och utvecklare som programmerar i hanterad kod har kunnat skapa sina egna beroende handtag med hjälp System.Runtime.CompilerServices.ConditionalWeakTable<TKey,TValue> av klassen redan före Windows 8 och .NET Framework 4.5.
Men hanterade XAML Windows Store-appar använder sig nu mycket av beroende handtag. I synnerhet använder CLR dem för att hantera referenscykler mellan hanterade objekt och ohanterade Windows Runtime-objekt. Det innebär att det är viktigare nu än någonsin för minnesprofilerare att informeras om dessa beroende handtag så att de kan visualiseras tillsammans med resten av kanterna i heap-grafen. Din Profiler-DLL bör använda RootReferences2, ObjectReferences och ConditionalWeakTableElementReferences tillsammans för att skapa en fullständig vy över heap-grafen.
Slutsats
Det är möjligt att använda CLR-profilerings-API:et för att analysera hanterad kod som körs i Windows Store-appar. I själva verket kan du ta en befintlig profilerare som du utvecklar och göra vissa specifika ändringar så att den kan rikta in sig på Windows Store-appar. Ditt Profiler-användargränssnitt bör använda de nya API:erna för att aktivera Windows Store-appen i felsökningsläge. Kontrollera att din Profiler-DLL endast använder de API:er som gäller för Windows Store-appar. Kommunikationsmekanismen mellan ditt Profiler DLL- och Profiler-användargränssnitt bör skrivas med begränsningarna för Windows Store-app-API:et i åtanke och med medvetenhet om de begränsade behörigheter som finns för Windows Store-appar. Din Profiler-DLL bör vara medveten om hur CLR behandlar WinMDs och hur skräpinsamlarens beteende skiljer sig från hanterade trådar.
Resurser
The Common Language Runtime
CLR:s interaktion med Windows Runtime
Windows Store-appar