Compartilhar via


Inicialização de Assemblies mistos

No Visual C++ .NET e Visual C++ 2003, as dlls compiladas com a opção de compilador /clr podem bloquear carregados; quando non-deterministically esse problema foi chamado no problema misturado de bloqueio de carregamento ou do carregador de DLL.Quase qualquer determinismo não foi removido do processo de carregamento misturado de DLL.No entanto, há alguns cenários restantes para que o bloqueio do carregador pode deterministically () ocorre.Para obter mais informações sobre esse problema, consulte “misturou o problema de carregamento de DLL” Biblioteca MSDNno.

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

ObservaçãoObservação

O Visual C++ 2003 forneceu _vcclrit.h para facilitar a inicialização da DLL para minimizar a oportunidade para um deadlock.Usar _vcclrit.h não é mais necessário, e faz com que os avisos de substituição ser gerado durante a compilação.A estratégia é recomendada remover as dependências neste arquivo usando as etapas em Removing Deprecated Header File _vcclrit.h.As soluções menos ideais incluem a exclusão dos avisos definindo _CRT_VCCLRIT_NO_DEPRECATE antes de incluir _vcclrit.h, ou simplesmente de ignorar os avisos de substituição.

Causas de bloqueio do carregador

Com a entrada de plataforma .NET há dois mecanismos diferentes para carregar um módulo de execução (EXE ou DLL): para um Windows, que é usado para módulos não gerenciado, e um para o tempo de execução (CLR) language runtime .NET que carrega os assemblys .NET.Os centros mistos do problema de carregamento de DLL em torno do carregador do Microsoft Windows sistema operacional.

Quando um assembly que contém apenas compilações .NET é carregado em um processo, o carregador CLR pode executar qualquer inicialização necessária carregar e tarefas.No entanto, para assemblies mistos, pois elas podem conter código nativo e os 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 esteja inicializado, e qualquer código que pode redundante carregar a DLL quando é inicializado parcialmente.Para fazer isso, o carregador do Windows usa uma seção crítica de processo global (chamada geralmente o bloqueio carregador”) que impede o acesso não seguro durante a inicialização do módulo.Como resultado, o processo de carregamento é vulnerável a muitos cenários classic de bloqueio completa.Para assemblies mistos, os dois seguintes cenários aumentando o risco de bloqueio completa:

  • Primeiro, se os usuários tentarem executar funções compiladas a Microsoft intermediate language (MSIL) quando o bloqueio do carregador é mantido (ou em inicializadores de DllMain estáticos, por exemplo), isso pode causar um deadlock.Considere o caso em que as referências de função MSIL um tipo em um assembly que não está carregado.O CLR tentará carregar automaticamente o assembly, que pode exigir o carregador do Windows bloquear o bloqueio do carregador.Desde que o bloqueio do carregador já é mantido pelo código anterior em sequência de chamada, resultados de um deadlock.No entanto, execute MSIL no bloqueio do carregador não garante que um deadlock ocorrerá esse cenário, tornando difícil diagnosticar e corrigir.Em algumas circunstâncias, como onde o DLL de tipo de referência não contém nenhum compilação de ambos e todas as suas dependências não contêm nenhum compilação de nativo, o carregador do Windows não é necessária carregar o assembly .NET de tipo de referência.Além disso, o assembly necessário ou suas dependências misturadas de native/.NET podem ter sido carregados por outro código.Como consequência, bloquear pode ser difícil de prever, e pode variar dependendo da configuração do computador de destino.

  • Segundo, ao carregar a DLL em versões 1,0 e 1,1 do.NET Framework, o CLR assumiu que o bloqueio do carregador não esteve mantido e executar várias ações que não são válidas no bloqueio do carregador.Suponha o carregador que o bloqueio não é mantido é uma suposição válido para puramente DLL .NET, mas, porque as dlls mistos executam rotinas de inicialização nativos, requerem o carregador nativo do Windows e como consequência o bloqueio do carregador.Como consequência, mesmo se o desenvolvedor não estava tentando realizar nenhuma funções MSIL durante a inicialização de DLL, ainda houve uma pequena possibilidade de bloqueio completa nondeterministic com versões 1,0 e 1,1 do.NET Framework.

Qualquer determinismo não foi removido do processo de carregamento misturado de DLL.Isso foi feito com essas alterações:

  • O CLR não faz suposições falsas ao carregar misturou DLL.

  • A inicialização gerenciado e não gerenciado é executada em dois estágios separados e em distintas.A inicialização não gerenciado surge principalmente (através de DllMain), e a inicialização gerenciado ocorre mais tarde, por uma compilação de .NET-supported chamada .cctor.O segundo é completamente transparente para o 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 mais informações.

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

O restante deste documento descreve cenários restantes para MSIL que pode ser executado no bloqueio do carregador, resolve para o problema em cada um desses cenários, e técnicas de depuração.

Cenários e soluções alternativas

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

  • DllMain

  • Inicializadores estáticos

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

  • Localidades personalizados

ms173266.collapse_all(pt-br,VS.110).gifDllMain

A função de DllMain é um ponto de entrada definido pelo usuário para uma DLL.A menos que o usuário especificar caso contrário, DllMain é invocado cada vez que um processo ou um segmento anexa a desanexam ou DLL que contém.Como esta chamada pode ocorrer quando o bloqueio do carregador é mantido, nenhuma função fornecidas por DllMain da MSIL deve ser compilado.Além disso, nenhuma função na árvore de chamada enraizada em DllMain pode ser criada a MSIL.Para resolver problemas aqui, o bloco de código que define DllMain deve ser alterado com #pragma unmanaged.O mesmo devem ser feitos para cada função que chama DllMain .

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

Como alternativa, se DllMain não é necessário ou se não precisa ser executada no bloqueio do carregador, fornecido pela implementação de DllMain pode ser removido, eliminará que o problema.

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

Por favor consulte “impedimentos ao diagnóstico” para obter mais informações sobre esse cenário.

ms173266.collapse_all(pt-br,VS.110).gifInicializando objetos estáticos

Inicializar objetos estáticos pode levar a deadlock se um inicializador dinâmico é necessário.Para casos simples, como quando uma variável estática é atribuído somente para um valor conhecido em tempo de compilação dinâmica, qualquer inicialização não é necessária, então há nenhum risco de bloqueio completa.No entanto, variáveis estáticas inicializaram por chamadas de função, invocações de construtor, ou expressões que não podem ser avaliadas em tempo de compilação tudo requerem código executar durante a inicialização do módulo.

O código abaixo mostra exemplos de inicializadores estáticos que exigem a inicialização dinâmico: uma chamada de função, compilação do objeto, e uma inicialização do ponteiro.(Esses exemplos não são estáticos, mas são considerados para serem definidos 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 bloqueio completa depende se o módulo recipiente é compilado com /clr e se MSIL será executado.Especificamente, se a variável estática é compilado sem /clr (ou reside em um bloco de unmanaged de #pragma), e o inicializador dinâmico necessário para inicializá-la leva à execução de instruções da MSIL, deadlock pode ocorrer.Isso ocorre porque, para módulos compilados sem /clr, a inicialização de variáveis estáticas é executada por DllMain.Por outro lado, as variáveis estáticas criados com /clr são inicializadas pelo .cctor, após a fase de inicialização não gerenciado concluiu e o bloqueio do carregador esteve liberado.

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

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

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

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

ms173266.collapse_all(pt-br,VS.110).gifFunções fornecida pelo usuário que afetam a inicialização

Há várias funções fornecida pelo usuário que dependem das bibliotecas para inicialização durante a inicialização.Por exemplo, quando global sobrecarregar operadores em C++ como os operadores de new e de delete , forneceu de versões é usado em qualquer lugar, incluindo na inicialização de STL e a destruição.Como resultado, STL e de inicializadores forneceu estáticos chamar qualquer usuário fornecido versões desses operadores.

Se o usuário fornecido as versões são compiladas a MSIL, então esses serão inicializadores tentando executar instruções da MSIL quando o bloqueio do carregador é mantido.Um malloc usuário fornecido tem as mesmas consequências.Para resolver esse problema, qualquer uma das sobrecargas ou definições fornecida pelo usuário devem ser implementadas como o código nativo usando a diretiva de unmanaged de #pragma.

Por favor consulte “impedimentos ao diagnóstico” para obter mais informações sobre esse cenário.

ms173266.collapse_all(pt-br,VS.110).gifLocalidades personalizados

Se o usuário fornece uma localidade global personalizado, esta localidade será usada inicializando todos os fluxos futuros de E/S, incluindo aqueles que são inicializadas estaticamente.Se este objeto global de localidade é compilado a MSIL, então as funções de membro do objeto são compiladas a MSIL podem ser chamadas quando 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 do fluxo de E/S podem ser criados usando a opção de /clr .Isso evitará que seus inicializadores estáticas serão executados sob o bloqueio do carregador.

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

Abstenha-se de definir um local personalizado como a localidade global até depois de bloqueio do carregador é lançada.Então definir explicitamente os fluxos de E/S criados durante a inicialização com a localidade personalizado.

Impedimentos ao diagnóstico

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

ms173266.collapse_all(pt-br,VS.110).gifImplementação em cabeçalhos

Em casos, selecione as implementações de função nos arquivos de cabeçalho pode complicar o diagnóstico.As funções embutidas e o modelo de código ambas exigem que as funções são especificadas em um arquivo de cabeçalho.O idioma C++ especifica a regra de definição, que força todas as implementações das funções com o mesmo nome para ser semanticamente equivalente.Como consequência, o vinculador C++ não precisa fazer qualquer considerações especial para mesclar arquivos de objeto que têm implementações duplicados de uma função especificados.

No Visual C++ .NET e Visual C++ .NET 2003, o vinculador simplesmente escolher maior dessas definições semanticamente equivalentes, para acomodar declarações e cenários dianteiros quando as diferentes opções de otimização são usadas para arquivos de origem diferentes.Isso cria um problema para DLL mistos de native/.NET.

Porque o mesmo cabeçalho pode ser incluído em arquivos de CPP com /clr ativado e desativou, ou um #include pode ser disposto em um bloco de unmanaged de #pragma, é possível ter MSIL e versões nativos de funções que fornecem implementações em cabeçalhos.MSIL e as implementações nativos têm a semântica diferente em relação à inicialização no bloqueio do carregador, que viola a verdade uma regra de definição.Como consequência, quando o vinculador escolher a implementação maior, pode escolher a versão MSIL de uma função, mesmo se ele foi criado explicitamente para código nativo usando outro lugar na diretiva não gerenciado de #pragma.Para garantir que a versão do MSIL de um modelo ou uma função chamada nunca está embutido no bloqueio do carregador, cada definição de cada uma função chamada no bloqueio do carregador deve ser alterada com a política de unmanaged de #pragma.Se o arquivo de cabeçalho é de um terceiros, a maneira mais fácil para fazer isso é empurrar e aparecer a diretiva não gerenciado de #pragma redor de política de #include para o arquivo de cabeçalho de incorreto ficará.(Consulte gerenciado, não gerenciado para um exemplo.) No entanto, essa estratégia não funcionará para os cabeçalhos que contém outro código que deve chamar APIs .NET.

Como uma conveniência para os usuários que tratam o bloqueio do carregador, o vinculador escolherá a implementação nativo sobre o gerenciados quando apresentada com ambos.Isso impede que os problemas anterior.No entanto, há duas exceções a essa regra nesta versão devido a dois problemas com o compilador não resolvidos:

  • A chamada é a uma função in-line é através de um ponteiro de função estática global.Esse cenário é particularmente notável porque as funções virtuais são chamadas através de ponteiros de função globais.Por exemplo,
#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 compilação Itanium- destino, há um erro na implementação de todos os ponteiros de função.No exemplo anterior, se o myObject_p foi definido localmente em during_loaderlock(), a chamada também pode resolver a uma implementação gerenciada.

ms173266.collapse_all(pt-br,VS.110).gifDiagnóstico no modo de depuração

Todos os diagnóstico de problemas de bloqueio do carregador devem ser feitas com compilações de depuração.As construções de versão não podem gerar diagnóstico, e as otimizações executadas no modo de lançamento podem mascarar algum do 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 é faz chamadas o CLR suspender a execução.Por sua vez, isso faz com que o depurador em modo misto Visual C++ para estar suspensa para também executar o debuggee em processo.No entanto, para anexar a um processo, não é possível obter um callstack gerenciado para o debuggee usando o depurador misturado.

Para identificar o MSIL função específico que foi chamado no bloqueio do carregador, desenvolvedores deve concluir as seguintes etapas:

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

    Isso pode ser feito em duas maneiras.Primeiro, o PDBs para mscoree.dll mscorwks.dll e pode ser adicionado ao caminho de busca do símbolo.Para fazer isso, abra a caixa de diálogo opções do caminho de busca do símbolo.(No menu ferramentas, opções de clique.No painel esquerdo da caixa de diálogo, abra o nó de depuração e clique em símbolos.) Adicionar o caminho para os arquivos de PDB de mscoree.dll e de mscorwks.dll à lista de pesquisa.Este PDBs é instalado para o %VSINSTALLDIR% \ SDK \ v2.0 \ símbolos.Clique em OK.

    Segundo, o PDBs para mscoree.dll mscorwks.dll e pode ser baixado de servidor do símbolo da Microsoft.Para configurar o servidor do símbolo, abra a caixa de diálogo opções do caminho de busca do símbolo.(No menu ferramentas, opções de clique.No painel esquerdo da caixa de diálogo, abra o nó de depuração 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 do símbolo para a caixa de texto de cache de servidor do símbolo.Clique em OK.

  2. Definir o modo de depuração para o modo de ambos somente.

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

  3. Ligue o depurador (F5).

  4. Quando o diagnóstico de /clr é gerado, clique na nova tentativa e clique na interrupção.

  5. Abra a janela da pilha de chamadas.(No menu debug do Windows, clique em, na pilha de chamadas.) Se DllMain de incorreto ficará ou o inicializador estático são identificados com uma seta verde.Se a função de incorreto ficará não é identificada, as seguintes etapas devem ser executadas para achar.

  6. Abra a janela imediata (no menu debug do Windows, clique, então imediato.)

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

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

  9. Procure a primeira instância (mais próximo à parte inferior da pilha () de _CorDllMain se problema das causas de DllMain ) ou o _VTableBootstrapThunkInitHelperStub ou GetTargetForVTableEntry (se o problema estático das causas de inicializador).A entrada de pilha logo abaixo desse chamada é a chamada de função implementada MSIL que tentou executar no bloqueio do carregador.

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

Exemplo

ms173266.collapse_all(pt-br,VS.110).gifDescrição

O exemplo a seguir mostra como evitar bloqueio do carregador por código em movimento de DllMain no construtor de um objeto global.

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

ms173266.collapse_all(pt-br,VS.110).gifCódigo

// 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;
}

Exemplo

ms173266.collapse_all(pt-br,VS.110).gifCódigo

// 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();
}

ms173266.collapse_all(pt-br,VS.110).gifSaída

Module ctor initializing based on global instance of class.

Test called so linker does not throw away unused object.

Consulte também

Conceitos

Mistos Assemblies (nativos e gerenciados)