Diretrizes para implementar extensões de In-Process
As extensões em processo são carregadas em todos os processos que as disparam. Por exemplo, uma extensão de namespace do Shell pode ser carregada em qualquer processo que acesse o namespace do Shell direta ou indiretamente. O namespace do 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 específico de observação é o CLR (Common Language Runtime), também conhecido como código gerenciado ou o .NET Framework. A Microsoft recomenda não gravar 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 quando você determina se qualquer runtime diferente do CLR é adequado para uso por extensões em processo. Exemplos de outros runtimes incluem Java, Visual Basic, JavaScript/ECMAScript, Delphi e a biblioteca de runtime C/C++. Este tópico também fornece alguns motivos pelos quais o código gerenciado não tem suporte em extensões em processo.
Conflitos de versão
Um conflito de versão pode surgir por meio de um runtime que não dá suporte ao carregamento de várias versões de runtime em um único processo. As versões do CLR anteriores à versão 4.0 se enquadram nessa categoria. Se o carregamento de uma versão de um runtime impedir o carregamento de outras versões desse mesmo runtime, isso poderá 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 gravada usando uma versão do CLR antes da versão 4.0. Cada aplicativo no computador que usa uma caixa de diálogo Abrir arquivo poderia potencialmente ter o código gerenciado da caixa de diálogo e sua dependência CLR de atendedor carregada no processo do aplicativo. O aplicativo ou extensão que primeiro deve 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 causar falhas no aplicativo. Por outro lado, se a extensão for a primeira a ser carregada 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 controle de versão porque foram 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 podem coexistir com outras versões). No entanto, problemas que não sejam conflitos de versão podem surgir conforme discutido no restante deste tópico.
Problemas de desempenho
Problemas de desempenho podem surgir com runtimes que impõem uma penalidade significativa de desempenho quando são carregados em um processo. A penalidade de desempenho pode estar 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 runtimes 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 pelo usuário), runtimes de alto impacto podem afetar negativamente a capacidade de resposta geral.
Um runtime de alto impacto que consome recursos significativos pode causar uma falha no processo do host ou em outra extensão em processo. Por exemplo, um runtime de alto impacto que consome centenas de megabytes de espaço de endereço para seu heap pode fazer com que o aplicativo host não consiga carregar um grande conjunto de dados. 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 multiplicar rapidamente em alto consumo de recursos em todo o sistema.
Se um runtime permanecer carregado ou continuar consumindo recursos mesmo quando a extensão que usa esse runtime for descarregada, esse runtime 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 tem suporte em extensões e pontos a serem considerados quando você avalia o uso de outros runtimes.
Reentrância
Quando o CLR bloqueia um thread STA (apartamento de thread único), por exemplo, devido a uma instrução Monitor.Enter, WaitHandle.WaitOne ou bloqueio em espera, o CLR, em 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 em um comportamento anômalo difícil de reproduzir e diagnosticar.
O Apartamento Multithreaded
O CLR cria Wrappers Callable do Runtime para objetos COM (Component Object Model). Esses mesmos Wrappers Callable do Runtime são destruídos posteriormente pelo finalizador do CLR, que faz parte do MTA (multithreaded apartment). Mover o proxy do STA para o MTA requer marshaling, mas nem todas as interfaces usadas pelas extensões podem ser empacotadas.
Tempos de vida de objeto não determinísticos
O CLR tem garantias de tempo de vida de 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 de objeto COM mantida pelo Runtime Callable Wrapper não será liberada até que o Wrapper Callable do Runtime seja coletado por lixo. O comportamento de versão não determinística pode entrar em conflito com alguns contratos de interface. Por exemplo, o método IPersistPropertyBag::Load exige que nenhuma referência ao recipiente de propriedades seja mantida pelo objeto quando o método Load retornar.
- Se uma referência de objeto CLR for retornada ao código nativo, o Wrapper Callable do Runtime 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 será finalizado até que ele seja coletado por lixo. A finalização não determinística pode entrar em conflito com alguns contratos de interface. Por exemplo, manipuladores de miniatura são necessários para liberar todos os recursos imediatamente quando a contagem de referência cai para zero.
Usos aceitáveis de código gerenciado e outros runtimes
É aceitável usar o código gerenciado e outros runtimes para implementar extensões fora do processo. Exemplos de extensões do Shell fora do processo incluem o seguinte:
- Manipuladores de visualização
- Ações baseadas em linha de comando, como aquelas registradas em subchaves decomando deverbo\ do shell\.
- 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 registrada em uma subchave decomando deverbo\ do shell\.
- IDropTarget associado ao CLSID registrado em uma subchavedropTarget doverbo\ do shell\.
- IExplorerCommandState associado a uma entrada CommandStateHandler registrada em uma subchavede verbo do shell\.