Inicialização de Assemblies mistos

No Visual C++.NET e Visual C++ 2003, as DLLs compiladas com o /clr opção de compilador não determinista poderia bloqueio quando carregado; Esse problema foi chamado, o carregamento de DLL misto ou problema de bloqueio do carregador. Quase todos os não-determinismo foi removido do processo de carregamento de DLL misto. No entanto, existem algumas restantes cenários para qual carregador lock (determinista) pode ocorrer. Para obter mais informações sobre esse problema, consulte "Problema de carregamento de DLL misto" no Biblioteca MSDN.

O código dentro de DllMain não deve acessar o CLR. Isso significa que DllMain não deve fazer nenhuma chamada de funções gerenciadas, direta ou indiretamente; Nenhum código gerenciado deve ser declarado ou implementado em DllMain; e nenhuma coleta de lixo ou o carregamento da biblioteca automática deve ocorrer em DllMain.

ObservaçãoObservação

Visual C++ 2003 fornecido _vcclrit.h para facilitar a inicialização da DLL, minimizando a oportunidade de deadlock. Usando o _vcclrit.h não é mais necessário e faz com que os avisos de substituição a ser produzida durante a compilação. A estratégia recomendada é remover dependências deste arquivo usando as etapas no How To: Remove Dependency on _vcclrit.h. Menos soluções ideais incluem suprimir avisos definindo _CRT_VCCLRIT_NO_DEPRECATE antes para incluindo _vcclrit.h, ou simplesmente ignorar os avisos de substituição.

Causas de bloqueio do carregador

Com a introdução da.NET de lá são dois mecanismos distintos para carregar um módulo de execução (EXE ou DLL): uma para o Windows, que é usada para os módulos não gerenciados, e outra para o.NET Common Language Runtime (CLR) que carrega.NET assemblies. O problema de carregamento de DLL misto centers ao redor do carregador do sistema operacional Microsoft Windows.

Quando um assembly que contém somente.NET construções é carregado em um processo, o carregador do CLR pode realizar todo o carregamento necessário e tarefas de inicialização próprio. No entanto, para assemblies mistos, pois eles podem conter código nativo e dados, o carregador do Windows deve ser usado também.

O carregador do Windows garante que nenhum código pode acessar código ou dados nessa dll antes que ele foi inicializado e que nenhum código redundante pode carregar a DLL enquanto ele é inicializado parcialmente. Para fazer isso, o carregador do Windows usa uma processo global seção crítica (geralmente chamada de "bloqueio de carregador") que impede o acesso seguro durante a inicialização do módulo. Como resultado, o processo de carregamento é vulnerável a vários cenários de deadlock clássico. Para assemblies mistos, dois cenários a seguir aumentam o risco de deadlock:

  • Primeiro, se os usuários tentarem executar funções compiladas para a Microsoft intermediate language (MSIL) quando o bloqueio do carregador é mantido (de DllMain ou em inicializadores estáticos, por exemplo), isso pode causar deadlocks. Considere o caso em que a função MSIL faz referência a um tipo em um assembly que não foi carregado. O CLR tentará carregará automaticamente o assembly, que pode exigir o carregador do Windows para bloquear o bloqueio do carregador. Desde que o bloqueio de carregador já é mantido pelo código anterior na seqüência de chamada, resulta um deadlock. No entanto, a MSIL em execução sob o bloqueio do carregador não garante que ocorrerá um deadlock dificultando diagnosticar e corrigir esse cenário. Em algumas circunstâncias, como, por exemplo, onde a DLL do tipo referenciado não contém nenhuma construções nativas e todas as suas dependências não contenham nenhuma construções nativas, o carregador do Windows não é necessário para carregar o.NET assembly do tipo referenciado. Além disso, o assembly necessário ou sua misto nativo /.NET dependências talvez já foram carregadas por outro código. Conseqüentemente, o travado pode ser difícil prever e pode variar dependendo da configuração da máquina de destino.

  • Segundo, quando o carregamento de DLLs nas versões 1.0 e 1.1 do.NET Framework, o CLR considerado que o bloqueio de carregador não foi mantido e executadas várias ações são inválidas em bloqueio do carregador. Supondo que o bloqueio de carregador não é mantido é puramente uma suposição válida para.NET DLLs, mas, como DLLs mistas executada rotinas de inicialização nativa, eles exigem que o carregador nativo do Windows e, portanto, o carregador de bloquear. Conseqüentemente, mesmo se o desenvolvedor não estava tentando executar quaisquer funções MSIL durante a inicialização da DLL, ainda havia uma possibilidade pequena de deadlock não determinístico com as versões 1.0 e 1.1 do.NET Framework.

Todos os não-determinismo foi removido do processo de carregamento de DLL misto. Isso foi feito com essas alterações:

  • O CLR não faz mais suposições falsas ao carregar DLLs mistas.

  • Inicialização gerenciada e não é executada em duas etapas separadas e distintas. Não gerenciada de inicialização ocorre primeiro (via DllMain) e gerenciada de inicialização ocorre posteriormente, através de um.Construção NET suportado chamada um . cctor. Este último é completamente transparente ao usuário, a menos que /Zl ou /NODEFAULTLIB são usados. Consulte/ /NODEFAULTLIB (ignorar bibliotecas) e /ZL (omitir o nome da biblioteca padrão) para obter mais informações.

Bloqueio do carregador ainda pode ocorrer, mas agora ele ocorre em reproducibly e é detectado. Se DllMain contém instruções MSIL, o compilador gerará o aviso de C4747 de aviso (nível 1) do compilador. Além disso, a CRT ou do CLR tentará detectar e relatar tentativas de executar o MSIL em bloqueio do carregador. Detecção de CRT resulta em tempo de execução do diagnóstico R6033 de erro de tempo de execução C.

O restante deste documento descreve os restantes cenários para o qual MSIL pode ser executado sob o bloqueio do carregador e resoluções para o problema em cada um desses cenários, e técnicas de depuração.

Cenários e soluções

Há várias situações diferentes sob a qual o código do usuário pode executar MSIL sob o bloqueio do carregador. O desenvolvedor deve garantir que a implementação de código do usuário não tenta executar instruções da MSIL em cada uma destas circunstâncias. As subseções seguintes descrevem todas as possibilidades com uma discussão sobre como resolver problemas nos casos mais comuns.

  • DllMain

  • Inicializadores estáticos

  • Funções fornecidas pelo usuário, que afetam a inicialização

  • Localidades personalizadas

DllMain

O DllMain função é um ponto de entrada definido pelo usuário para uma DLL. A menos que o usuário especifique caso contrário, DllMain é chamado sempre que um processo ou segmento anexa ou desanexa da DLL que contém. Como essa invocação pode ocorrer enquanto o bloqueio do carregador é mantido, não fornecido pelo usuário DllMain função deve ser compilada para MSIL. Além disso, nenhuma função na árvore de chamada enraizada em DllMain podem ser compilados para MSIL. Para resolver problemas aqui, o bloco de código que define DllMain deve ser modificado com # pragma unmanaged. O mesmo deve ser feito para cada função que DllMain chamadas.

Em casos onde essas funções devem chamar uma função que requer uma implementação de MSIL para outros contextos de chamada, uma estratégia de duplicação pode ser usada onde tanto um.NET e uma versão nativa do que a mesma função são criados.

Como alternativa, se DllMain não é necessária ou se ele não precisa ser executado em loader bloquear, o fornecido pelo usuário DllMain implementação pode ser removida, o que eliminará o problema.

Se DllMain tenta executar o MSIL diretamente, C4747 de aviso (nível 1) do compilador será o resultado. No entanto, o compilador não pode detectar os casos onde o DllMain chama uma função em outro módulo que por sua vez, tenta executar o MSIL.

Consulte "Impedimentos para diagnóstico" Para obter mais informações sobre esse cenário.

Inicializando objetos estáticos

Inicializar objetos estáticos pode resultar em deadlock se um inicializador dinâmico é necessário. Para casos simples, como, por exemplo, quando uma variável estática é atribuída de simplesmente para um valor conhecido em tempo de compilação, sem inicialização dinâmica é necessária, portanto não há nenhum risco de deadlock. No entanto, as variáveis estáticas inicializadas por chamadas de função, chamadas de construtor ou expressões que não podem ser avaliadas no todas exigem que o código seja executado durante a inicialização do módulo de tempo de compilação.

O código a seguir mostra exemplos de inicializadores estáticos que requerem inicialização dinâmica: uma chamada de função, construção de objeto e a inicialização do ponteiro. (Esses exemplos não são estáticos, mas são considerados ser definido no escopo global, que tem o mesmo efeito).

// dynamic initializer function generated
int a = init();
CObject o(arg1, arg2);  
CObject* op = new CObject(arg1, arg2);

Esse risco de deadlock depende se o módulo contendo é compilado com /clr e se o MSIL será executada. Especificamente, se a variável estática é compilado sem /clr (ou reside em um # pragma unmanaged block), e o inicializador dinâmico necessário para inicializar o resultados na execução de instruções MSIL, deadlock pode ocorrer. Isso ocorre porque, para os módulos compilados sem /clra inicialização de variáveis estáticas é feito por DllMain. Em contraste, as variáveis estáticas compiladas com /clr são inicializadas pelo cctor, após o estágio de inicialização não gerenciada foi concluída e o bloqueio do carregador foi liberado.

Há um número de soluções de deadlock causada pela inicialização dinâmica de variáveis estáticas (aproximadamente organizados na ordem de tempo necessário para corrigir o problema):

  • O arquivo de origem que contém a variável estática pode ser compilado com /clr.

  • Todas as funções chamadas pela variável estática podem ser compiladas para código nativo usando o # pragma unmanaged diretiva.

  • Clonar manualmente o código que a variável estática depende, fornecendo a ambas uma.NET e uma versão nativa com nomes diferentes. Os desenvolvedores podem chamar a versão nativa do nativos inicializadores estáticos e a chamada a.NET versão em outro lugar.

Funções fornecidas pelo usuário, que afetam a inicialização

Existem várias funções fornecidas pelo usuário em que dependem de bibliotecas para inicialização do. Por exemplo, quando globalmente a sobrecarga de operadores em C++, como o new e delete operadores, as versões fornecido pelo usuário são usados em todos os lugares, incluindo na inicialização da STL e destruição. Como resultado, a STL e fornecido pelo usuário inicializadores estáticos invocará quaisquer versões fornecido pelo usuário desses operadores.

Se as versões fornecido pelo usuário são compiladas para MSIL, esses inicializadores serão tentando executar instruções MSIL, enquanto o bloqueio do carregador é mantido. Um malloc fornecido pelo usuário tem as mesmas conseqüências. Para resolver esse problema, qualquer um desses sobrecargas ou definições de usuário fornecido deve ser implementado como código nativo usando o # pragma unmanaged diretiva.

Consulte "Impedimentos para diagnóstico" Para obter mais informações sobre esse cenário.

Localidades personalizadas

Se o usuário fornece uma localidade personalizada de global, essa localidade será usada para inicializar todos os fluxos de i/O futuros, incluindo aqueles que são inicializadas estaticamente. Se este objeto de localidade global é compilado para MSIL, funções de membro de objeto de localidade compiladas para MSIL pode ser chamadas enquanto o bloqueio do carregador é mantido.

Há três opções para resolver esse problema:

Os arquivos de origem que contém todas as definições globais de fluxo de i/O podem ser compilados usando o /clr opção. Isso impedirá que os inicializadores de estáticos seja executado sob o bloqueio do carregador.

As definições de função da localidade personalizada podem ser compiladas para código nativo usando o # pragma unmanaged diretiva.

Evite definir a localidade personalizada como a localidade global até depois que o bloqueio do carregador é liberado. Em seguida, configure explicitamente os fluxos de e/S criados durante a inicialização com a localidade personalizada.

Impedimentos diagnóstico

Em alguns casos é difícil de detectar a origem de deadlocks. As subseções a seguir abordam esses cenários e as maneiras de contornar esses problemas.

Implementação de cabeçalhos

Em casos de select, implementações de função dentro de arquivos de cabeçalho podem complicar o diagnóstico. As funções embutidas e o código do modelo exigem que as funções seja especificado em um arquivo de cabeçalho. A linguagem C++ especifica uma regra de definição, que força todas as implementações de funções com o mesmo nome a ser semanticamente equivalente. Conseqüentemente, o vinculador C++ não precisa fazer quaisquer considerações especiais ao mesclar arquivos de objeto têm implementações duplicadas de uma determinada função.

No Visual C++.NET e Visual C++.NET 2003, o vinculador simplesmente escolhe o maior dessas definições semanticamente equivalente, para acomodar os cenários e as declarações de encaminhamento quando as opções de otimização diferentes são usadas para arquivos de origem diferente. Isso cria um problema para misto nativo /.NET DLLs.

Porque o mesmo cabeçalho pode ser incluídos por um arquivos CPP com /clr habilitado e desabilitado ou um # incluem pode ser encapsulado em um # pragma unmanaged bloco, é possível ter o MSIL e versões nativas de funções que fornecem implementações em cabeçalhos. MSIL e implementações nativas tem uma semântica diferente com relação a inicialização do bloqueio do carregador efetivamente viola a regra de uma definição. Conseqüentemente, quando o vinculador escolhe a maior implementação, a versão MSIL de uma função, pode optar mesmo explicitamente, ele foi compilado para código nativo em outro local usando a diretiva de # pragma não gerenciado. Para garantir que uma versão MSIL de uma modelo ou uma função in-line nunca é chamada em um bloqueio do carregador, cada definição de cada tal função chamada sob o bloqueio do carregador deve ser modificada com a # pragma unmanaged diretiva. Se o arquivo de cabeçalho é de terceiros, a maneira mais fácil de conseguir isso é enviar por push e pop a diretiva de # pragma não gerenciado em torno do # incluem a diretiva para o arquivo de cabeçalho incorreto. (Consulte managed, unmanaged para um exemplo.) No entanto, essa estratégia não funcionará para cabeçalhos que contêm o outro código que deve chamar diretamente.NET APIs.

Como uma conveniência para lidar com o bloqueio do carregador de usuários, o vinculador escolherá a implementação nativa sobre gerenciado quando apresentado com ambos. Isso evita problemas acima. No entanto, existem duas exceções a essa regra nesta versão devido a problemas não resolvidos e dois com o compilador:

  • A chamada é para um in-line função é através de um ponteiro de função estática global. Esse cenário é particularmente importante porque funções virtuais são chamadas através de ponteiros de função global. For example,
#include "definesmyObject.h"
#include "definesclassC.h"

typedef void (*function_pointer_t)();

function_pointer_t myObject_p = &myObject;

#pragma unmanaged
void DuringLoaderlock(C & c)
{
    // Either of these calls could resolve to a managed implementation, 
    // at link-time, even if a native implementation also exists.
    c.VirtualMember();
    myObject_p();
}
  • Com Itanium-alvo de compilação, há um bug na implementação de todos os ponteiros de função. No trecho de código acima, se myObject_p foram definidas localmente dentro de during_loaderlock(), a chamada também pode resolver para uma implementação gerenciada.

Diagnosticando no modo de depuração

Todos os diagnósticos de bloqueio de carregador problemas devem ser feitos com compilações de depuração. Compilações lançadas podem não produzir o diagnóstico e as otimizações realizadas no modo de versão podem mascarar alguns MSIL em cenários de bloqueio do carregador.

Como depurar problemas de bloqueio do carregador

O diagnóstico que o CLR gera quando uma função MSIL é invocada faz com que o CLR suspender a execução. Por sua vez, isso faz com que o depurador de modo misto do Visual C++ a ser suspenso também durante a execução a ser depurado no processo. No entanto, ao anexar ao processo, não é possível obter uma pilha de chamadas de gerenciado para o depurado usando o depurador misto.

Para identificar a função específica de MSIL que foi chamada em bloqueio de carregador, os desenvolvedores devem concluir as seguintes etapas:

  1. Certifique-se de que os símbolos de mscoree e mscorwks. dll estão disponíveis.

    Isso pode ser feito de duas maneiras. Primeiro, os PDBs para mscoree e mscorwks. dll podem ser adicionados ao caminho de pesquisa do símbolo. Para fazer isso, abra a caixa de diálogo de opções de caminho de pesquisa de símbolo. (No menu Ferramentas, clique em Opções. No painel esquerdo da caixa de diálogo Opções, abra o nó Debugging e clique em símbolos.) Adicione o caminho para os arquivos PDB mscoree e mscorwks. dll à lista de pesquisa. Esses PDBs instalados % VSINSTALLDIR%\SDK\v2.0\symbols. Click OK.

    Segundo, os PDBs para mscoree e mscorwks. dll podem ser baixados from the Symbol Server da Microsoft. Para configurar o servidor de símbolos, abra a caixa de diálogo de opções de caminho de pesquisa de símbolo. (No menu Ferramentas, clique em Opções. No painel esquerdo da caixa de diálogo Opções, abra o nó Debugging e clique em símbolos.) Adicione o seguinte caminho de pesquisa à lista de pesquisa: http://MSDL.microsoft.com/download/symbols. Adicione um diretório de cache de símbolo para a caixa de texto de cache de servidor de símbolo. Click OK.

  2. Defina o modo de depurador para o modo somente para nativo.

    Para fazer isso, abra a grade de propriedades para o projeto de inicialização na solução. Na subárvore Configuration Properties, selecione o nó de depuração. Defina o campo de tipo de depurador somente nativo.

  3. Inicie o depurador (F5).

  4. Quando o /clr diagnóstico é gerado, clique em ' Repetir ' e, em seguida, clique em quebra.

  5. Abra a janela call stack. (No menu Debug, clique Windows, então a pilha de chamadas). Se ofensivo DllMain ou inicializador estático é identificado com uma seta verde. Se a função ofensivo não for identificada, as etapas a seguir devem ser tomadas para localizá-lo.

  6. Abra a janela Imediata (no menu Debug, clique Windows, em seguida, imediata).

  7. Digite o SOS. dll de carregar na janela imediata para carregar o SOS, serviço de depuração.

  8. Tipo! dumpstack na janela imediata para obter uma lista completa de internos /clr pilha.

  9. Procure a primeira instância (próxima à parte inferior da pilha) de qualquer um dos _CorDllMain (se DllMain faz com que o problema) ou _VTableBootstrapThunkInitHelperStub ou GetTargetForVTableEntry (se o problema causa o inicializador estático). A entrada de pilha logo abaixo dessa chamada é que a invocação do MSIL implementado a função tentou executar sob o bloqueio do carregador.

  10. Vá para o arquivo de origem e linha número identificado na etapa 9 e corrigir o problema usando os cenários e soluções descritas na seção cenários.

Example

Description

O exemplo a seguir mostra como evitar o bloqueio do carregador, movendo o código DllMain no construtor de um objeto global.

Neste exemplo, há um objeto gerenciado global, cujos construtor contém o objeto gerenciado que era originalmente no DllMain. A segunda parte deste exemplo faz referência a assembly, criando uma instância do objeto gerenciado para chamar o construtor de módulo que faz a inicialização.

Code

// initializing_mixed_assemblies.cpp
// compile with: /clr /LD 
#pragma once
#include <stdio.h>
#include <windows.h>
struct __declspec(dllexport) A {
   A() {
      System::Console::WriteLine("Module ctor initializing based on global instance of class.\n");
   }

   void Test() {
      printf_s("Test called so linker does not throw away unused object.\n");
   }
};
 
#pragma unmanaged
// Global instance of object
A obj;
 
extern "C"
BOOL WINAPI DllMain(HINSTANCE hInstance, DWORD dwReason, LPVOID lpReserved) {
   // Remove all managed code from here and put it in constructor of A.
   return true;
}

Example

Code

// initializing_mixed_assemblies_2.cpp
// compile with: /clr initializing_mixed_assemblies.lib
#include <windows.h>
using namespace System;
#include <stdio.h>
#using "initializing_mixed_assemblies.dll"
struct __declspec(dllimport) A {
   void Test();
};

int main() {
   A obj;
   obj.Test();
}

Output

Module ctor initializing based on global instance of class.

Test called so linker does not throw away unused object.

Consulte também

Conceitos

Mistos de Assemblies (nativos e gerenciados)