Profilers CLR e aplicativos da Windows Store
Este tópico discute o que você precisa pensar ao escrever ferramentas de diagnóstico que analisam o código gerenciado em execução dentro de um aplicativo da Windows Store. Ele também fornece diretrizes para modificar suas ferramentas de desenvolvimento existentes para que elas continuem a funcionar quando você as executa em aplicativos da Windows Store. Para entender essas informações, é melhor se você estiver familiarizado com a API de criação de perfil do Common Language Runtime, já tiver usado essa API em uma ferramenta de diagnóstico que é executada corretamente em aplicativos da área de trabalho do Windows e agora está interessado em modificar a ferramenta para ser executada corretamente em aplicativos da Windows Store.
Introdução
Se você passou do parágrafo introdutório, está familiarizado com a API de criação de perfil CLR. Você já escreveu uma ferramenta de diagnóstico que funciona bem em aplicativos de desktop gerenciados. Agora você está curioso sobre o que fazer para que sua ferramenta funcione com um aplicativo gerenciado da Windows Store. Talvez você já tenha tentado fazer isso funcionar e tenha descoberto que não é uma tarefa simples. Na verdade, há uma série de considerações que podem não ser óbvias para todos os desenvolvedores de ferramentas. Por exemplo:
Os aplicativos da Windows Store são executados em um contexto com permissões severamente reduzidas.
Os arquivos de metadados do Windows têm características exclusivas quando comparados aos módulos gerenciados tradicionais.
Os aplicativos da Windows Store têm o hábito de se suspender quando a interatividade diminui.
Os seus mecanismos de comunicação entre processos podem já não funcionar por várias razões.
Este tópico lista as coisas que você precisa estar ciente e como lidar com elas corretamente.
Se você é novo na API de criação de perfil CLR, pule para os Recursos no final deste tópico para encontrar melhores informações introdutórias.
Fornecer detalhes sobre APIs específicas do Windows e como elas devem ser usadas também está fora do escopo deste tópico. Considere este tópico um ponto de partida e consulte o MSDN para saber mais sobre as APIs do Windows mencionadas aqui.
Arquitetura e terminologia
Normalmente, uma ferramenta de diagnóstico tem uma arquitetura como a mostrada na ilustração a seguir. Ele usa o termo "profiler", mas muitas dessas ferramentas vão muito além do desempenho típico ou do perfil de memória em áreas como cobertura de código, estruturas de objeto fictício, depuração de viagem no tempo, monitoramento de aplicativos e assim por diante. Para simplificar, este tópico continuará a referir-se a todas estas ferramentas como profilers.
A seguinte terminologia é usada ao longo deste tópico:
Aplicação
Este é o aplicativo que o criador de perfil está analisando. Normalmente, o desenvolvedor deste aplicativo agora está usando o criador de perfil para ajudar a diagnosticar problemas com o aplicativo. Tradicionalmente, este aplicativo seria um aplicativo da área de trabalho do Windows, mas neste tópico, estamos analisando os aplicativos da Windows Store.
Profiler DLL
Este é o componente que é carregado no espaço de processo do aplicativo que está sendo analisado. Este componente, também conhecido como o "agente" do profiler, implementa as interfaces ICorProfilerCallbackICorProfilerCallback Interface(2,3,etc.) e consome as interfaces ICorProfilerInfo(2,3,etc.) para coletar dados sobre o aplicativo analisado e potencialmente modificar aspetos do comportamento do aplicativo.
Interface do usuário do Profiler
Este é um aplicativo de desktop com o qual o usuário do criador de perfil interage. Ele é responsável por exibir o status do aplicativo para o usuário e dar ao usuário os meios para controlar o comportamento do aplicativo analisado. Esse componente sempre é executado em seu próprio espaço de processo, separado do espaço de processo do aplicativo que está sendo perfilado. A interface do usuário do Profiler também pode atuar como o "gatilho de anexação", que é o processo que chama o método ICLRProfiling::AttachProfiler , para fazer com que o aplicativo analisado carregue a DLL do Profiler nos casos em que a DLL do profiler não foi carregada na inicialização.
Importante
Sua interface do usuário do Profiler deve permanecer um aplicativo da área de trabalho do Windows, mesmo quando é usada para controlar e gerar relatórios em um aplicativo da Windows Store. Não espere poder empacotar e enviar sua ferramenta de diagnóstico na Windows Store. Sua ferramenta precisa fazer coisas que os aplicativos da Windows Store não podem fazer, e muitas dessas coisas residem na interface do usuário do Profiler.
Ao longo deste documento, o código de exemplo pressupõe que:
Sua DLL do Profiler é escrita em C++, porque deve ser uma DLL nativa, de acordo com os requisitos da API de criação de perfil CLR.
Sua interface do usuário do Profiler é escrita em C#. Isso não é necessário, mas como não há requisitos sobre o idioma para o processo da interface do usuário do Profiler, por que não escolher um idioma que seja conciso e simples?
Dispositivos Windows RT
Os dispositivos Windows RT estão bastante bloqueados. Os criadores de perfil de terceiros simplesmente não podem ser carregados nesses dispositivos. Este documento concentra-se em PCs com Windows 8.
Consumindo APIs do Tempo de Execução do Windows
Em vários cenários discutidos nas seções a seguir, seu aplicativo de área de trabalho da interface do usuário do Profiler precisa consumir algumas novas APIs do Tempo de Execução do Windows. Consulte a documentação para entender quais APIs do Tempo de Execução do Windows podem ser usadas em aplicativos da área de trabalho e se seu comportamento é diferente quando chamado de aplicativos da área de trabalho e aplicativos da Windows Store.
Se a interface do usuário do Profiler estiver escrita em código gerenciado, haverá algumas etapas que você precisará fazer para facilitar o consumo dessas APIs do Tempo de Execução do Windows. Para obter mais informações, consulte o artigo Aplicativos de área de trabalho gerenciados e Tempo de Execução do Windows .
Carregando a DLL do Profiler
Esta seção descreve como a interface do usuário do Profiler faz com que o aplicativo da Windows Store carregue sua DLL do Profiler. O código discutido nesta seção pertence ao seu aplicativo de área de trabalho Profiler UI e, portanto, envolve o uso de APIs do Windows que são seguras para aplicativos da área de trabalho, mas não necessariamente seguras para aplicativos da Windows Store.
Sua interface do usuário do Profiler pode fazer com que sua DLL do Profiler seja carregada no espaço de processo do aplicativo de duas maneiras:
Na inicialização do aplicativo, conforme controlado por variáveis de ambiente.
Anexando ao aplicativo após a conclusão da inicialização, chamando o método ICLRProfiling::AttachProfiler .
Um dos seus primeiros obstáculos será fazer com que o start-load e o attach-load da sua DLL do Profiler funcionem corretamente com aplicativos da Windows Store. Ambas as formas de carregamento compartilham algumas considerações especiais em comum, então vamos começar com elas.
Considerações comuns para inicialização e anexação de cargas
Assinando sua DLL do Profiler
Quando o Windows tenta carregar a DLL do Profiler, ele verifica se a DLL do Profiler está assinada corretamente. Caso contrário, a carga falhará por padrão. Poderá fazê-lo de duas formas:
Verifique se a DLL do Profiler está assinada.
Diga ao usuário que ele deve instalar uma licença de desenvolvedor em sua máquina Windows 8 antes de usar sua ferramenta. Isso pode ser feito automaticamente a partir do Visual Studio ou manualmente a partir de um prompt de comando. Para obter mais informações, consulte Obter uma licença de desenvolvedor.
Permissões do sistema de arquivos
O aplicativo da Windows Store deve ter permissão para carregar e executar sua DLL do Profiler do local no sistema de arquivos em que residePor padrão, o aplicativo da Windows Store não tem essa permissão na maioria dos diretórios, e qualquer tentativa falhada de carregar sua DLL do Profiler produzirá uma entrada no log de eventos do Aplicativo do Windows com esta aparência:
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].
Geralmente, os aplicativos da Windows Store só têm permissão para acessar um conjunto limitado de locais no disco. Cada aplicativo da Windows Store pode acessar suas próprias pastas de dados de aplicativos, bem como algumas outras áreas no sistema de arquivos para as quais todos os aplicativos da Windows Store têm acesso. É melhor instalar sua DLL do Profiler e suas dependências em algum lugar em Arquivos de Programas ou Arquivos de Programas (x86), porque todos os aplicativos da Windows Store têm permissões de leitura e execução por padrão.
Carga de arranque
Normalmente, em um aplicativo de área de trabalho, a interface do usuário do Profiler solicita uma carga de inicialização da DLL do Profiler inicializando um bloco de ambiente que contém as variáveis de ambiente necessárias da API de criação de perfil CLR (ou seja, , COR_ENABLE_PROFILING
e COR_PROFILER_PATH
) e, em seguida, COR_PROFILER
criando um novo processo com esse bloco de ambiente. O mesmo se aplica às aplicações da Loja Windows, mas os mecanismos são diferentes.
Não corra elevado
Se o Processo A tentar gerar o Processo B do aplicativo da Windows Store, o Processo A deverá ser executado em nível de integridade médio, não em nível de integridade alto (ou seja, não elevado). Isso significa que a interface do usuário do Profiler deve estar sendo executada em nível de integridade médio ou deve gerar outro processo de área de trabalho em nível de integridade médio para cuidar da inicialização do aplicativo da Windows Store.
Escolher uma aplicação da Loja Windows para criar um perfil
Primeiro, você vai querer perguntar ao usuário do seu criador de perfil qual aplicativo da Windows Store iniciar. Para aplicativos da área de trabalho, talvez você mostrasse uma caixa de diálogo Procurar arquivo e o usuário localizasse e selecionasse um arquivo .exe. Mas os aplicativos da Windows Store são diferentes e usar uma caixa de diálogo Procurar não faz sentido. Em vez disso, é melhor mostrar ao usuário uma lista de aplicativos da Windows Store instalados para esse usuário selecionar.
Você pode usar a PackageManager classe para gerar essa lista. PackageManager
é uma classe do Tempo de Execução do Windows que está disponível para aplicativos da área de trabalho e, na verdade, só está disponível para aplicativos da área de trabalho.
O exemplo de código a seguir de uma interface do usuário hipotética do Profiler escrita como um aplicativo da área de trabalho em C# usa o PackageManager
para gerar uma lista de aplicativos do Windows:
string currentUserSID = WindowsIdentity.GetCurrent().User.ToString();
IAppxFactory appxFactory = (IAppxFactory) new AppxFactory();
PackageManager packageManager = new PackageManager();
IEnumerable<Package> packages = packageManager.FindPackagesForUser(currentUserSID);
Especificando o bloco de ambiente personalizado
Uma nova interface COM, IPackageDebugSettings, permite personalizar o comportamento de execução de um aplicativo da Windows Store para facilitar algumas formas de diagnóstico. Um de seus métodos, EnableDebugging, permite que você passe um bloco de ambiente para o aplicativo da Windows Store quando ele é iniciado, juntamente com outros efeitos úteis, como desabilitar a suspensão automática de processos. O bloco de ambiente é importante porque é aí que você precisa especificar as variáveis de ambiente (COR_PROFILER
, COR_ENABLE_PROFILING
e COR_PROFILER_PATH)
) usadas pelo CLR para carregar sua DLL do Profiler.
Considere o seguinte trecho de código:
IPackageDebugSettings pkgDebugSettings = new PackageDebugSettings();
pkgDebugSettings.EnableDebugging(packageFullName, debuggerCommandLine,
(IntPtr)fixedEnvironmentPzz);
Há alguns itens que você precisa acertar:
packageFullName
pode ser determinado ao iterar sobre os pacotes e agarrarpackage.Id.FullName
.debuggerCommandLine
é um pouco mais interessante. Para passar o bloco de ambiente personalizado para o aplicativo da Windows Store, você precisa escrever seu próprio depurador fictício simplista. O Windows gera o aplicativo da Windows Store suspenso e, em seguida, anexa o depurador iniciando o depurador com uma linha de comando, como neste exemplo:MyDummyDebugger.exe -p 1336 -tid 1424
onde
-p 1336
significa que o aplicativo da Windows Store tem a ID de Processo 1336 e-tid 1424
significa que a ID de Thread 1424 é o thread que está suspenso. Seu depurador fictício analisaria o ThreadID da linha de comando, retomaria esse thread e sairia.Aqui está um exemplo de código C++ para fazer isso (certifique-se de adicionar verificação de erro!):
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; }
Você precisará implantar esse depurador fictício como parte da instalação da ferramenta de diagnóstico e, em seguida, especificar o caminho para esse depurador no
debuggerCommandLine
parâmetro.
Iniciar a aplicação da Loja Windows
O momento de iniciar o aplicativo da Windows Store finalmente chegou. Se você já tentou fazer isso sozinho, deve ter notado que CreateProcess não é como você cria um processo de aplicativo da Windows Store. Em vez disso, você precisará usar o método IApplicationActivationManager::ActivateApplication . Para fazer isso, você precisará obter a ID do Modelo de Usuário do Aplicativo do aplicativo da Windows Store que está iniciando. E isso significa que você precisará fazer uma pequena escavação no manifesto.
Ao iterar sobre seus pacotes (consulte "Escolhendo um aplicativo da Windows Store para perfil" na seção Carregamento de inicialização anteriormente), você desejará pegar o conjunto de aplicativos contidos no manifesto do pacote atual:
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();
Sim, um pacote pode ter vários aplicativos e cada aplicativo tem seu próprio ID de modelo de usuário de aplicativo. Portanto, você vai querer perguntar ao seu usuário qual aplicativo criar um perfil e pegar o ID do modelo de usuário do aplicativo desse aplicativo específico:
while (appsEnum.GetHasCurrent() != 0)
{
IAppxManifestApplication app = appsEnum.GetCurrent();
string appUserModelId = app.GetAppUserModelId();
//...
}
Finalmente, agora você tem o que precisa para iniciar o aplicativo da Windows Store:
IApplicationActivationManager appActivationMgr = new ApplicationActivationManager();
appActivationMgr.ActivateApplication(appUserModelId, appArgs, ACTIVATEOPTIONS.AO_NONE, out pid);
Lembre-se de chamar DisableDebugging
Quando você chamou IPackageDebugSettings::EnableDebugging, você fez uma promessa de que limparia depois de si mesmo chamando o método IPackageDebugSettings::D isableDebugging , então certifique-se de fazer isso quando a sessão de criação de perfil terminar.
Anexar carga
Quando a interface do usuário do Profiler deseja anexar sua DLL do Profiler a um aplicativo que já começou a ser executado, ele usa ICLRProfiling::AttachProfiler. O mesmo se aplica às aplicações da Loja Windows. Mas, além das considerações comuns listadas anteriormente, certifique-se de que o aplicativo de destino da Windows Store não está suspenso.
EnableDebugging
Assim como na carga de inicialização, chame o método IPackageDebugSettings::EnableDebugging . Você não precisa dele para passar um bloqueio de ambiente, mas precisa de um de seus outros recursos: desativar a suspensão automática do processo. Caso contrário, quando a interface do usuário do Profiler chamar AttachProfiler, o aplicativo de destino da Windows Store poderá ser suspenso. Na verdade, isso é provável se o usuário estiver interagindo com sua interface do usuário do Profiler e o aplicativo da Windows Store não estiver ativo em nenhuma das telas do usuário. E se o aplicativo da Windows Store for suspenso, ele não poderá responder a nenhum sinal que o CLR enviar a ele para anexar sua DLL do Profiler.
Então você vai querer fazer algo assim:
IPackageDebugSettings pkgDebugSettings = new PackageDebugSettings();
pkgDebugSettings.EnableDebugging(packageFullName, null /* debuggerCommandLine */,
IntPtr.Zero /* environment */);
Esta é a mesma chamada que você faria para o caso de carregamento de inicialização, exceto que você não especifica uma linha de comando do depurador ou um bloco de ambiente.
DisableDebugging
Como sempre, não se esqueça de chamar IPackageDebugSettings::D isableDebugging quando sua sessão de criação de perfil for concluída.
Em execução dentro da aplicação da Loja Windows
Então, o aplicativo da Windows Store finalmente carregou sua DLL do Profiler. Agora, sua DLL do Profiler deve ser ensinada a jogar de acordo com as diferentes regras exigidas pelos aplicativos da Windows Store, incluindo quais APIs são permitidas e como executar com permissões reduzidas.
Respeite as APIs de aplicativos da Windows Store
Ao navegar pela API do Windows, você notará que cada API está documentada como sendo aplicável a aplicativos da área de trabalho, aplicativos da Windows Store ou ambos. Por exemplo, a seção Requisitos da documentação da função InitializeCriticalSectionAndSpinCount indica que a função se aplica somente a aplicativos da área de trabalho. Por outro lado, a função InitializeCriticalSectionEx está disponível para aplicativos da área de trabalho e aplicativos da Windows Store.
Ao desenvolver sua DLL do Profiler, trate-a como se fosse um aplicativo da Windows Store e use apenas APIs documentadas como disponíveis para aplicativos da Windows Store. Analise suas dependências (por exemplo, você pode executar link /dump /imports
sua DLL do Profiler para auditar) e, em seguida, pesquise os documentos para ver quais de suas dependências estão ok e quais não estão. Na maioria dos casos, suas violações podem ser corrigidas simplesmente substituindo-as por uma forma mais recente da API documentada como segura (por exemplo, substituindo InitializeCriticalSectionAndSpinCount por InitializeCriticalSectionEx).
Você pode notar que sua DLL do Profiler chama algumas APIs que se aplicam apenas a aplicativos da área de trabalho e, no entanto, elas parecem funcionar mesmo quando a DLL do Profiler é carregada dentro de um aplicativo da Windows Store. Lembre-se de que é arriscado usar qualquer API não documentada para uso com aplicativos da Windows Store em sua DLL do Profiler quando carregada em um processo de aplicativo da Windows Store:
Não é garantido que essas APIs funcionem quando chamadas no contexto exclusivo em que os aplicativos da Windows Store são executados.
Essas APIs podem não funcionar consistentemente quando chamadas de dentro de diferentes processos de aplicativos da Windows Store.
Essas APIs podem parecer funcionar bem em aplicativos da Windows Store na versão atual do Windows, mas podem quebrar ou ser desabilitadas em versões futuras do Windows.
O melhor conselho é corrigir todas as suas violações e evitar o risco.
Você pode achar que não pode prescindir de uma API específica e não consegue encontrar um substituto adequado para aplicativos da Windows Store. Nesse caso, no mínimo:
Teste, teste, teste as luzes do dia vivas do seu uso dessa API.
Entenda que a API pode quebrar ou desaparecer repentinamente se for chamada de dentro de aplicativos da Windows Store em versões futuras do Windows. Isso não será considerado uma preocupação de compatibilidade pela Microsoft, e dar suporte ao seu uso não será uma prioridade.
Permissões reduzidas
Está fora do escopo deste tópico listar todas as maneiras pelas quais as permissões de aplicativos da Windows Store diferem das aplicativos da área de trabalho. Mas certamente o comportamento será diferente cada vez que sua DLL do Profiler (quando carregada em um aplicativo da Windows Store em comparação com um aplicativo da área de trabalho) tentar acessar quaisquer recursos. O sistema de arquivos é o exemplo mais comum. Há apenas alguns locais no disco que um determinado aplicativo da Windows Store tem permissão para acessar (consulte Acesso e permissões de arquivos (aplicativos do Tempo de Execução do Windows), e sua DLL do Profiler estará sob as mesmas restrições. Teste seu código completamente.
Comunicação entre processos
Conforme mostrado no diagrama no início deste documento, sua DLL do Profiler (carregada no espaço de processo do aplicativo da Windows Store) provavelmente precisará se comunicar com a interface do usuário do Profiler (em execução em um espaço de processo separado do aplicativo da área de trabalho) por meio de seu próprio canal personalizado de comunicação entre processos (IPC). A interface do usuário do Profiler envia sinais para a DLL do Profiler para modificar seu comportamento, e a DLL do Profiler envia dados do aplicativo da Windows Store analisado de volta para a interface do usuário do Profiler para pós-processamento e exibição para o usuário do profiler.
A maioria dos criadores de perfil precisa trabalhar dessa forma, mas suas opções para mecanismos IPC são mais limitadas quando a DLL do Profiler é carregada em um aplicativo da Windows Store. Por exemplo, pipes nomeados não fazem parte do SDK do aplicativo da Windows Store, portanto, você não pode usá-los.
Mas é claro que os arquivos ainda estão dentro, embora de forma mais limitada. Eventos também estão disponíveis.
Comunicação através de ficheiros
A maioria dos seus dados provavelmente passará entre a DLL do Profiler e a interface do usuário do Profiler por meio de arquivos. A chave é escolher um local de arquivo ao qual a DLL do Profiler (no contexto de um aplicativo da Windows Store) e a interface do usuário do Profiler tenham acesso de leitura e gravação. Por exemplo, o caminho da Pasta Temporária é um local que a DLL do Profiler e a interface do usuário do Profiler podem acessar, mas nenhum outro pacote de aplicativo da Windows Store pode acessar (protegendo assim qualquer informação registrada de outros pacotes de aplicativos da Windows Store).
Tanto a interface do usuário do Profiler quanto a DLL do Profiler podem determinar esse caminho independentemente. Sua interface do usuário do Profiler, quando itera através de todos os pacotes instalados para o usuário atual (consulte o código de exemplo anteriormente), obtém acesso à PackageId
classe, da qual o caminho da Pasta Temporária pode ser derivado com código semelhante a esse trecho. (Como sempre, a verificação de erros é omitida por brevidade.)
// C# code for the Profiler UI.
ApplicationData appData =
ApplicationDataManager.CreateForPackageFamily(
packageId.FamilyName);
tempDir = appData.TemporaryFolder.Path;
Enquanto isso, sua DLL do Profiler pode fazer basicamente a mesma coisa, embora possa chegar mais facilmente à ApplicationData classe usando a propriedade ApplicationData.Current .
Comunicar através de eventos
Se quiser semântica de sinalização simples entre a interface do usuário do Profiler e a DLL do Profiler, você pode usar eventos dentro de aplicativos da Windows Store, bem como aplicativos da área de trabalho.
A partir da sua DLL do Profiler, você pode simplesmente chamar a função CreateEventEx para criar um evento nomeado com qualquer nome que desejar. Por exemplo:
// 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);
Em seguida, a interface do usuário do Profiler precisa localizar esse evento nomeado no namespace do aplicativo da Windows Store. Por exemplo, sua interface do usuário do Profiler pode chamar CreateEventEx, especificando o nome do evento como
AppContainerNamedObjects\<acSid>\MyNamedEvent
<acSid>
é o SID AppContainer do aplicativo da Windows Store. Uma seção anterior deste tópico mostrou como iterar sobre os pacotes instalados para o usuário atual. A partir desse código de exemplo, você pode obter o packageId. E a partir do packageId, você pode obter o <acSid>
código com semelhante ao seguinte:
IntPtr acPSID;
DeriveAppContainerSidFromAppContainerName(packageId.FamilyName, out acPSID);
string acSid;
ConvertSidToStringSid(acPSID, out acSid);
string acDir;
GetAppContainerFolderPath(acSid, out acDir);
Sem notificações de desligamento
Quando executado dentro de um aplicativo da Windows Store, sua DLL do Profiler não deve depender de ICorProfilerCallback::Shutdown ou mesmo DllMain (com DLL_PROCESS_DETACH
) sendo chamado para notificar sua DLL do Profiler de que o aplicativo da Windows Store está saindo. Na verdade, você deve esperar que eles nunca serão chamados. Historicamente, muitas DLLs do Profiler têm usado essas notificações como locais convenientes para liberar caches no disco, fechar arquivos, enviar notificações de volta para a interface do usuário do Profiler, etc. Mas agora sua DLL do Profiler precisa ser organizada um pouco diferente.
Sua DLL do Profiler deve estar registrando informações à medida que avançam. Por motivos de desempenho, convém armazenar informações em lote na memória e liberá-las para o disco à medida que o lote cresce em tamanho além de algum limite. Mas suponha que qualquer informação ainda não liberada para o disco pode ser perdida. Isso significa que você vai querer escolher seu limite com sabedoria e que sua interface do usuário do Profiler precisa ser reforçada para lidar com informações incompletas escritas pela DLL do Profiler.
Arquivos de metadados do Tempo de Execução do Windows
Está fora do escopo deste documento entrar em detalhes sobre o que são os arquivos de metadados do Tempo de Execução do Windows (WinMD). Esta seção é limitada a como a API de Criação de Perfil CLR reage quando os arquivos WinMD são carregados pelo aplicativo da Windows Store que sua DLL do Profiler está analisando.
WinMDs gerenciados e não gerenciados
Se um desenvolvedor usa o Visual Studio para criar um novo projeto de componente do Tempo de Execução do Windows, uma compilação desse projeto produz um arquivo WinMD que descreve os metadados (as descrições de tipo de classes, interfaces, etc.) criados pelo desenvolvedor. Se este projeto é um projeto de linguagem gerenciada escrito em C# ou Visual Basic, esse mesmo arquivo WinMD também contém a implementação desses tipos (o que significa que ele contém toda a IL compilada a partir do código-fonte do desenvolvedor). Esses arquivos são conhecidos como arquivos WinMD gerenciados. Eles são interessantes porque contêm metadados do Tempo de Execução do Windows e a implementação subjacente.
Por outro lado, se um desenvolvedor cria um projeto de componente do Tempo de Execução do Windows para C++, uma compilação desse projeto produz um arquivo WinMD que contém apenas metadados e a implementação é compilada em uma DLL nativa separada. Da mesma forma, os arquivos WinMD fornecidos no SDK do Windows contêm apenas metadados, com a implementação compilada em DLLs nativas separadas que são fornecidas como parte do Windows.
As informações abaixo se aplicam a WinMDs gerenciados, que contêm metadados e implementação, e a WinMDs não gerenciados, que contêm apenas metadados.
Os arquivos WinMD se parecem com módulos CLR
No que diz respeito ao CLR, todos os arquivos WinMD são módulos. Portanto, a API de criação de perfil CLR informa sua DLL do Profiler quando os arquivos WinMD são carregados e quais são seus ModuleIDs, da mesma forma que para outros módulos gerenciados.
Sua DLL do Profiler pode distinguir arquivos WinMD de outros módulos chamando o método ICorProfilerInfo3::GetModuleInfo2 e inspecionando o pdwModuleFlags
parâmetro de saída para o sinalizador COR_PRF_MODULE_WINDOWS_RUNTIME . (É definido se e somente se o ModuleID representar um WinMD.)
Lendo metadados de WinMDs
Os arquivos WinMD, como módulos regulares, contêm metadados que podem ser lidos por meio das APIs de metadados. No entanto, o CLR mapeia tipos do Tempo de Execução do Windows para tipos do .NET Framework quando lê arquivos WinMD para que os desenvolvedores que programam em código gerenciado e consomem o arquivo WinMD possam ter uma experiência de programação mais natural. Para obter alguns exemplos desses mapeamentos, consulte Suporte do .NET Framework para aplicativos da Windows Store e Tempo de Execução do Windows.
Então, qual modo de exibição seu criador de perfil obterá quando usar as APIs de metadados: o modo de exibição bruto do Tempo de Execução do Windows ou o modo de exibição mapeado do .NET Framework? A resposta: depende de você.
Quando você chama o método ICorProfilerInfo::GetModuleMetaData em um WinMD para obter uma interface de metadados, como IMetaDataImport, você pode optar por definir ofNoTransform no dwOpenFlags
parâmetro para desativar esse mapeamento. Caso contrário, por padrão, o mapeamento será habilitado. Normalmente, um profiler manterá o mapeamento habilitado, para que as cadeias de caracteres que a DLL do Profiler obtém dos metadados do WinMD (por exemplo, nomes de tipos) pareçam familiares e naturais para o usuário do profiler.
Modificando metadados de WinMDs
Não há suporte para a modificação de metadados em WinMDs. Se você chamar o método ICorProfilerInfo::GetModuleMetaData para um arquivo WinMD e especificar ofWrite no dwOpenFlags
parâmetro ou solicitar uma interface de metadados gravável, como IMetaDataEmit, GetModuleMetaData falhará. Isso é de particular importância para criadores de perfil de reescrita de IL, que precisam modificar metadados para dar suporte à sua instrumentação (por exemplo, para adicionar AssemblyRefs ou novos métodos). Portanto, você deve verificar COR_PRF_MODULE_WINDOWS_RUNTIME primeiro (como discutido na seção anterior) e abster-se de pedir interfaces de metadados graváveis em tais módulos.
Resolvendo referências de assembly com WinMDs
Muitos criadores de perfil precisam resolver referências de metadados manualmente para ajudar com instrumentação ou inspeção de tipo. Esses criadores de perfil precisam estar cientes de como o CLR resolve referências de assembly que apontam para WinMDs, porque essas referências são resolvidas de uma maneira completamente diferente das referências de assembly padrão.
Criadores de perfis de memória
O coletor de lixo e o heap gerenciado não são fundamentalmente diferentes em um aplicativo da Windows Store e em um aplicativo de área de trabalho. No entanto, existem algumas diferenças sutis que os autores de perfis precisam estar cientes.
ForceGC cria um thread gerenciado
Ao fazer a criação de perfil de memória, sua DLL do Profiler normalmente cria um thread separado a partir do qual chamar o método ForceGC Method . Isso não é novidade. Mas o que pode ser surpreendente é que o ato de fazer uma coleta de lixo dentro de um aplicativo da Windows Store pode transformar seu thread em um thread gerenciado (por exemplo, um ThreadID da API de Criação de Perfil será criado para esse thread).
Para entender as consequências disso, é importante entender as diferenças entre chamadas síncronas e assíncronas, conforme definido pela API de criação de perfil CLR. Observe que isso é muito diferente do conceito de chamadas assíncronas em aplicativos da Windows Store. Consulte a postagem do blog Por que temos CORPROF_E_UNSUPPORTED_CALL_SEQUENCE para obter mais informações.
O ponto relevante é que as chamadas feitas em threads criados pelo seu profiler são sempre consideradas síncronas, mesmo que essas chamadas sejam feitas de fora de uma implementação de um dos métodos ICorProfilerCallback da DLL do Profiler. Pelo menos, costumava ser assim. Agora que o CLR transformou o thread do seu profiler em um thread gerenciado por causa da sua chamada para o Método ForceGC, esse thread não é mais considerado o thread do seu profiler. Como tal, o CLR impõe uma definição mais rigorosa do que se qualifica como síncrono para esse thread — ou seja, que uma chamada deve ser originada de dentro de um dos métodos ICorProfilerCallback da DLL do Profiler para se qualificar como síncrona.
O que isso significa na prática? A maioria dos métodos ICorProfilerInfo só são seguros para serem chamados de forma síncrona e falharão imediatamente caso contrário. Portanto, se a DLL do Profiler reutilizar o thread do Método ForceGC para outras chamadas normalmente feitas em threads criados pelo criador de perfil (por exemplo, para RequestProfilerDetach, RequestReJIT ou RequestRevert), você terá problemas. Mesmo uma função segura assíncrona, como DoStackSnapshot , tem regras especiais quando chamada a partir de threads gerenciados. (Veja a postagem no blogProfiler stack walking: noções básicas e além para obter mais informações.)
Portanto, recomendamos que qualquer thread que sua DLL do Profiler crie para chamar o Método ForceGC deve ser usado apenas com a finalidade de acionar GCs e, em seguida, responder aos retornos de chamada GC. Ele não deve chamar a API de criação de perfil para executar outras tarefas, como amostragem de pilha ou desanexação.
ConditionalWeakTableReferences
A partir do .NET Framework 4.5, há um novo retorno de chamada GC, ConditionalWeakTableElementReferences, que fornece ao criador de perfil informações mais completas sobre identificadores dependentes. Esses identificadores efetivamente adicionam uma referência de um objeto de origem a um objeto de destino para fins de gerenciamento do tempo de vida do GC. Identificadores dependentes não são novidade, e os desenvolvedores que programam em código gerenciado puderam criar seus próprios identificadores dependentes usando a System.Runtime.CompilerServices.ConditionalWeakTable<TKey,TValue> classe antes mesmo do Windows 8 e do .NET Framework 4.5.
No entanto, os aplicativos XAML gerenciados da Windows Store agora fazem uso intenso de identificadores dependentes. Em particular, o CLR os usa para ajudar no gerenciamento de ciclos de referência entre objetos gerenciados e objetos não gerenciados do Tempo de Execução do Windows. Isso significa que é mais importante agora do que nunca que os criadores de perfis de memória sejam informados sobre essas alças dependentes para que elas possam ser visualizadas junto com o resto das bordas no gráfico de pilha. Sua DLL do Profiler deve usar RootReferences2, ObjectReferences e ConditionalWeakTableElementReferences juntos para formar uma exibição completa do gráfico de pilha.
Conclusão
É possível usar a API de Criação de Perfil CLR para analisar o código gerenciado em execução dentro de aplicativos da Windows Store. Na verdade, você pode pegar um criador de perfil existente que está desenvolvendo e fazer algumas alterações específicas para que ele possa direcionar aplicativos da Windows Store. Sua interface do usuário do Profiler deve usar as novas APIs para ativar o aplicativo da Windows Store no modo de depuração. Certifique-se de que sua DLL do Profiler consuma apenas as APIs aplicáveis aos aplicativos da Windows Store. O mecanismo de comunicação entre a DLL do Profiler e a interface do usuário do Profiler deve ser escrito com as restrições da API do aplicativo da Windows Store em mente e com conhecimento das permissões restritas em vigor para aplicativos da Windows Store. Sua DLL do Profiler deve estar ciente de como o CLR trata WinMDs e como o comportamento do coletor de lixo é diferente em relação aos threads gerenciados.
Recursos
O Common Language Runtime
A interação do CLR com o Tempo de Execução do Windows
Aplicações da Loja Windows