Orientações para a implementação de extensões In-Process
As extensões internas ao processo são carregadas em todos os processos que as acionam. Por exemplo, uma extensão de namespace Shell pode ser carregada em qualquer processo que acesse o namespace Shell direta ou indiretamente. O namespace Shell é usado por muitas operações do Shell, como a exibição de uma caixa de diálogo de arquivo comum, a inicialização de um documento por meio de seu aplicativo associado ou a obtenção do ícone usado para representar um arquivo. Como as extensões em processo podem ser carregadas em processos arbitrários, deve-se tomar cuidado para que elas não afetem negativamente o aplicativo host ou outras extensões em processo.
Um runtime que merece destaque é o Common Language Runtime (CLR), também conhecido como código gerido ou o .NET Framework . A Microsoft recomenda não escrever extensões gerenciadas em processo no Windows Explorer ou no Windows Internet Explorer e não as considera um cenário com suporte.
Este tópico discute os fatores a serem considerados ao determinar se qualquer tempo de execução diferente do CLR é adequado para uso por extensões em processo. Exemplos de outros tempos de execução incluem Java, Visual Basic, JavaScript/ECMAScript, Delphi e a biblioteca de tempo de execução C/C++. Este tópico também fornece alguns motivos pelos quais o código gerenciado não é suportado em extensões em processo.
Conflitos de versão
Um conflito de versão pode surgir quando um tempo de execução não suporta o carregamento de várias versões dentro de um único processo. As versões do CLR anteriores à versão 4.0 enquadram-se nesta categoria. Se o carregamento de uma versão de um tempo de execução impedir o carregamento de outras versões desse mesmo tempo de execução, isso pode criar um conflito se o aplicativo host ou outra extensão em processo usar uma versão conflitante. No caso de um conflito de versão com outra extensão em processo, o conflito pode ser difícil de reproduzir porque a falha requer as extensões conflitantes corretas e o modo de falha depende da ordem em que as extensões conflitantes são carregadas.
Considere uma extensão em processo escrita usando uma versão do CLR anterior à versão 4.0. Cada aplicação no computador que usa a caixa de diálogo Abrir de um ficheiro pode ter potencialmente o código gerido da caixa de diálogo e a sua dependência CLR associada carregada no processo da aplicação. O aplicativo ou extensão que é primeiro a carregar uma versão pré-4.0 do CLR no processo do aplicativo restringe quais versões do CLR podem ser usadas posteriormente por esse processo. Se um aplicativo gerenciado com uma caixa de diálogo Abrir for criado em uma versão conflitante do CLR, a extensão poderá falhar ao ser executada corretamente e poderá causar falhas no aplicativo. Por outro lado, se a extensão for a primeira a carregar em um processo e uma versão conflitante do código gerenciado tentar iniciar depois disso (talvez um aplicativo gerenciado ou um aplicativo em execução carregue o CLR sob demanda), a operação falhará. Para o usuário, parece que alguns recursos do aplicativo param de funcionar aleatoriamente, ou o aplicativo falha misteriosamente.
Observe que as versões do CLR iguais ou posteriores à versão 4.0 geralmente não são suscetíveis ao problema de versionamento porque são projetadas para coexistir entre si e com a maioria das versões pré-4.0 do CLR (com exceção da versão 1.0, que não pode coexistir com outras versões). No entanto, outras questões além de conflitos de versão podem surgir, conforme discutido no restante deste tópico.
Problemas de desempenho
Problemas de desempenho podem surgir com tempos de execução que impõem uma penalidade de desempenho significativa quando são carregados em um processo. A penalidade de desempenho pode ser na forma de uso de memória, uso da CPU, tempo decorrido ou até mesmo consumo de espaço de endereço. O CLR, JavaScript/ECMAScript e Java são conhecidos por serem tempos de execução de alto impacto. Como as extensões em processo podem ser carregadas em muitos processos, e geralmente são feitas em momentos sensíveis ao desempenho (como ao preparar um menu para ser exibido ao usuário), os tempos de execução de alto impacto podem afetar negativamente a capacidade de resposta geral.
Um tempo de execução de alto impacto que consome recursos significativos pode causar uma falha no processo do host ou outra extensão em processo. Por exemplo, um tempo de execução de alto impacto que consome centenas de megabytes de espaço de endereço para o seu heap pode resultar na incapacidade da aplicação anfitriã de carregar um conjunto de dados volumoso. Além disso, como as extensões em processo podem ser carregadas em vários processos, o alto consumo de recursos em uma única extensão pode se multiplicar rapidamente em alto consumo de recursos em todo o sistema.
Se um tempo de execução permanecer carregado ou continuar a consumir recursos mesmo quando a extensão que usa esse tempo de execução tiver sido descarregada, esse tempo de execução não será adequado para uso em uma extensão.
Problemas específicos do .NET Framework
As seções a seguir discutem exemplos de problemas encontrados com o uso de código gerenciado para extensões. Eles não são uma lista completa de todos os possíveis problemas que você pode encontrar. Os problemas discutidos aqui são os motivos pelos quais o código gerenciado não é suportado em extensões e os pontos a serem considerados quando você avalia o uso de outros tempos de execução.
Reentrabilidade
Quando o CLR bloqueia um thread de apartamento de thread único (STA), por exemplo, devido a uma instrução Monitor.Enter, WaitHandle.WaitOne, ou de bloqueio contended, o CLR, na sua configuração padrão, insere um loop de mensagem aninhado enquanto aguarda. Muitos métodos de extensão são proibidos de processar mensagens, e essa reentrância imprevisível e inesperada pode resultar num comportamento anómalo que é difícil de reproduzir e diagnosticar.
O Apartamento Multithreaded
O CLR cria Runtime Callable Wrappers para objetos COM (Component Object Model). Esses mesmos Runtime Callable Wrappers são destruídos mais tarde pelo finalizador do CLR, que faz parte do apartamento de múltiplos encadeamentos (MTA). Mover o proxy do STA para o MTA requer marshaling, mas nem todas as interfaces utilizadas por extensões podem ser marshaladas.
Tempos de vida de objetos não determinísticos
O CLR tem garantias de tempo de vida do objeto mais fracas do que o código nativo. Muitas extensões têm requisitos de contagem de referência em objetos e interfaces, e o modelo de coleta de lixo empregado pelo CLR não pode atender a esses requisitos.
- Se um objeto CLR obtiver uma referência a um objeto COM, a referência do objeto COM mantida pelo Runtime Callable Wrapper não será liberada até que o Runtime Callable Wrapper seja recolhido pelo coletor de lixo. O comportamento de liberação não determinística pode entrar em conflito com alguns contratos de interface. Por exemplo, o método IPersistPropertyBag::Load requer que nenhuma referência ao conjunto de propriedades seja retida pelo objeto quando o método Load retorna.
- Se uma referência de objeto CLR for retornada ao código nativo, o Runtime Callable Wrapper abrirá mão de sua referência ao objeto CLR quando a chamada final do Runtime Callable Wrapper para Release for feita, mas o objeto CLR subjacente não é finalizado até que seja recolhido pelo coletor de lixo. A finalização não determinística pode entrar em conflito com alguns contratos de interface. Por exemplo, os manipuladores de miniaturas são obrigados a liberar todos os recursos imediatamente quando sua contagem de referência cai para zero.
Usos aceitáveis de código gerenciado e outros tempos de execução
É aceitável usar código gerenciado e outros tempos de execução para implementar extensões fora do processo. Exemplos de extensões do Shell fora de processo incluem o seguinte:
- Manipuladores de visualização
- Ações baseadas em linha de comando, como as registradas em shell\verbo\comando subchaves.
- Objetos COM implementados em um servidor local, para pontos de extensão do Shell que permitem a ativação fora do processo.
Algumas extensões podem ser implementadas como extensões em processo ou fora do processo. Você pode implementar essas extensões como extensões fora do processo se elas não atenderem a esses requisitos para extensões em processo. A lista a seguir mostra exemplos de extensões que podem ser implementadas como extensões em processo ou fora do processo:
- IExecuteCommand associado a uma entrada DelegateExecute registada em shell\verbo\comando subchave.
- IDropTarget associado ao CLSID registrado sob um shell\verbo\subchave DropTarget.
- IExplorerCommandState associado a uma entrada CommandStateHandler registrada sob um shell \verbo subchave.