Partilhar via


Inicialização de assemblies mistos

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

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

Dica

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

Faz com que o bloqueio de carregador

Com a apresentação da plataforma do .NET há dois mecanismos distintos para carregar um módulo de execução (EXE ou DLL): um para aplicativos do windows, que é usado para os módulos não gerenciado, e um para o .NET Common Language Runtime (CLR) que carrega os assemblies do .NET. Os centros mistos do problema de carga da DLL em torno de carregador do sistema operacional Microsoft Windows.

Quando um assembly que contém apenas as construções do .NET é carregado em um processo, o carregador de CLR pode executar qualquer carga necessária e a tarefas de inicialização. No entanto, para assemblies mistos, porque 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 código de acesso ou dados em que o DLL antes de ser inicializado, e que nenhum código redundante pode carregar a DLL quando ele é inicializado parcialmente. Para fazer isso, o carregador do windows usa uma seção crítica processo global (chamada que frequência o bloqueio carregador”) que impede o acesso inseguros durante a inicialização do módulo. No, o processo de carga é vulnerável a muitos cenários clássicos de deadlock. Para assemblies mistos, os dois seguintes cenários aumenta o risco de deadlock:

  • Primeiro, se os usuários tentarem executar funções criadas a linguagem intermediária da Microsoft (MSIL) quando o bloqueio de carregador será mantido (de DllMain ou em inicializadores estáticos, por exemplo), isso pode causar deadlock. Considere o caso em que a função MSIL de um tipo em um assembly que ainda não foi carregado. CLR tentará carregar automaticamente esse assembly, que pode exigir o carregador do windows bloquear o bloqueio de carregador. Desde que o bloqueio de carregador é mantido já pelo código anterior na sequência de chamadas, um deadlock. No entanto, execute MSIL no bloqueio de carregador não garante que um deadlock ocorrerá esse cenário, tornando difícil diagnosticar e corrigir. Em algumas circunstâncias, como quando a DLL do tipo referenciado não contém nenhuma construção nativo e todas as suas dependências não contêm nenhuma construção nativos, o carregador do windows não é necessário carregar o assembly .NET. do tipo referenciado. Além disso, o assembly necessário ou suas dependências mistas de native/.NET podem já tenha sido carregados pelo outro código. Consequentemente, o bloqueio pode ser difícil de previsão, e pode variar dependendo da configuração do computador de destino.

  • Segundo, ao carregar a DLL nas versões 1,0 e 1,1 do.NET Framework, CLR assumirá que o bloqueio de carregador não esteve ocupado e executar várias ações que não são válidos no bloqueio de carregador. Suponha que o carregador o qual o bloqueio é mantido não é válido para uma suposição puramente DLL .NET, mas, como as dlls mistos executam rotinas de inicialização nativos, exigem o carregador nativo do windows e em virtude disso o bloqueio de carregador. Em virtude disso, mesmo se o desenvolvedor não estava tentando executar funções de MSIL durante a inicialização da DLL, havia ainda uma pequena chance de deadlock não determinísticas com versões 1,0 e 1,1 do.NET Framework.

Qualquer não determinismo foi removido do processo de carga misto da DLL. Isso foi realizado com essas alterações:

  • CLR não faz suposições falsas durante o carregamento de modo a DLL.

  • A inicialização não gerenciado e gerenciado é executada em dois separados e em fases distintas. A inicialização ocorre primeiro (não gerenciado por meio de DllMain), e a inicialização gerenciado ocorre posteriormente, por meio de uma construção de .NET-supported chamada .cctor. O último é completamente transparente ao usuário a menos que /Zl ou /NODEFAULTLIB são usados. Consulte/NODEFAULTLIB (ignorar bibliotecas) e /Zl (omitir nome da biblioteca padrão) para obter mais informações.

O bloqueio de carregador ainda pode acontecer, mas agora ocorre reprodutìvel, e é detectado. Se DllMain contém instruções de MSIL, o compilador gerará Aviso do compilador (nível 1) C4747de aviso. Além disso, o CRT ou CLR tentarão detectar e relatar tenta executar MSIL no bloqueio de carregador. A detecção de CRT resultar em erro em tempo de execução de diagnóstico R6033 C do tempo de execução.

O restante deste documento descreve os cenários para que MSIL pode executar sob o bloqueio de carregador, resoluções 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 na qual o código de usuário pode executar MSIL no bloqueio de carregador. O desenvolvedor deve assegurar que a implementação de código do usuário não tente executar instruções de MSIL em cada uma dessas condições. As subseções a seguir 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 personalizados

DllMain

A função de DllMain é um ponto de entrada definido pelo usuário para uma DLL. A menos que o usuário especifique de outra forma, DllMain é invocado cada vez que um processo ou um thread for anexado ou desanexado da DLL contentor. Considerando que essa invocação pode ocorrer quando o bloqueio de carregador é mantido, nenhuma função fornecida pelo usuário de DllMain deve ser compilada a MSIL. Além disso, nenhuma função na árvore de chamada enraizada em DllMain pode ser compilado a MSIL. Para resolver problemas aqui, o bloco de código que define DllMain deve ser modificado com #pragma unmanaged. O mesmo devem ser feitas para cada função que DllMain chama.

Nos casos em que 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 um .NET e uma versão nativo da mesma função são criados.

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

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

Consulte “impedimentos no diagnóstico” para obter mais informações sobre esse cenário.

Inicializando objetos estáticos

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

O código a seguir mostra exemplos de inicializadores estáticos que exigem a inicialização dinâmico: uma chamada de função, construir do objeto, e uma inicialização do ponteiro. (Esses exemplos não é estático, mas deveriam ser definidas 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 contentora for criado 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 para inicializá-lo resulta na execução de instruções de MSIL deadlock, poderá ocorrer. Isso ocorre porque, em módulos compilados sem /clr, a inicialização de variáveis estáticas é executada por DllMain. Em contraste, as variáveis estáticas compilados com /clr são inicializados por .cctor, depois que a fase de inicialização não gerenciados foi concluída e o bloqueio de carregador esteve liberado.

Há várias soluções de deadlock causado pela inicialização dinâmico de variáveis estáticas (organizados basicamente por ordem de tempo necessário para corrigir o problema):

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

  • Todas as funções chamadas pela variável estática pode ser compilado em código nativo usando a política de unmanaged de #pragma.

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

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

Há várias funções fornecidas pelo usuário das bibliotecas que dependem da inicialização durante a inicialização. Por exemplo, quando global sobrecarregar operadores em C++ como os operadores de new e de delete , fornecido por versões é usado em todos lugares, inclusive na inicialização STL e a destruição. No resultado, STL e fornecido por inicializadores estáticos invocará alguns fornecido por versões desses operadores.

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

Consulte “impedimentos no diagnóstico” para obter mais informações sobre esse cenário.

Localidades personalizados

Se o usuário fornece uma localidade global personalizado, essa localidade será usada para inicializar todos os fluxos futuros de E/S, inclusive aquelas que são inicializadas estaticamente. Se esse objeto global da localidade é compilado a MSIL, as funções de membro do objeto criadas com a MSIL podem ser invocadas quando o bloqueio de carregador é mantido.

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

Os arquivos de origem que contém todas as definições globais do fluxo de E/S podem ser compilados usando a opção de /clr . Isso impedirá que seus inicializadores estáticos sejam executados no bloqueio de carregador.

As definições de função personalizadas da localidade pode ser compilado em código nativo usando a política de unmanaged de #pragma.

Abstenha-se de definir a localidade personalizado como a localidade global até depois de bloqueio de carregador é liberada. Em seguida configurar explicitamente os fluxos de E/S criados durante a inicialização com a localidade personalizado.

Impedimentos no diagnóstico

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

Implementação em cabeçalhos

Em casos, selecione as implementações da função nos arquivos de cabeçalho podem complicar o diagnóstico. As funções embutidas e o modelo afeta ambas exigem que as funções são especificadas em um arquivo de cabeçalho. A linguagem C++ especifica a definição de uma regra, que força todas as implementações das funções com o mesmo nome para ser semanticamente equivalente. Em virtude disso, o vinculador C++ não precisa tomar nenhuma consideração especial para mesclar os arquivos de objeto que têm implementações duplicados de uma função específica.

No Visual C++ .NET e Visual C++ .NET 2003, o vinculador escolhe apenas o maior dessas definições semanticamente equivalentes, para acomodar declarações e cenários de avanço rápido quando as opções diferentes de otimização são usadas para arquivos de origem diferentes. Isso cria um problema para dlls combinados de native/.NET.

Como o mesmo cabeçalho pode ser incluído em arquivos de CPP com /clr habilitado e desabilitou, ou um #include pode ser encapsulada 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 nativas têm semânticas diferentes em relação à inicialização no bloqueio de carregador, que viola efetivamente a uma regra de definição. Em virtude disso, quando o vinculador escolhe a implementação a maior, pode escolher a versão de MSIL de uma função, mesmo se foi explicitamente compilado em código nativo usando em outra parte da política não gerenciado de #pragma. Para assegurar que uma versão de MSIL de um modelo ou de uma função embutida ser chamada nunca no bloqueio de carregador, cada definição de cada uma dessas função chamada no bloqueio de carregador deve ser alterada com a política de unmanaged de #pragma. Se o arquivo de cabeçalho é de terceiros, a maneira mais fácil de fazer isso é enviar e exibida a política não gerenciado de #pragma o redor da política de #include para o arquivo de cabeçalho incorretos. (Consulte gerenciado, não gerenciado para obter um exemplo.) No entanto, essa estratégia não funcionará para os cabeçalhos que contêm outro código que deve chamar diretamente as APIs do .NET.

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

  • O é chamada a uma função embutida é por meio de um ponteiro estático global da função. Este cenário é especialmente indicado como as funções virtuais são chamadas por ponteiros globais da função. 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();
}
  • A compilação itanium destinada, há um bug na implementação de todos os ponteiros da função. No exemplo anterior, se o myObject_p foi definido localmente dentro de during_loaderlock(), o chamada também pode resolver uma implementação gerenciada.

Diagnóstico em modo de depuração

Todos os diagnósticos de problemas de bloqueio de carregador deve ser feito com construções de depuração. As construções de versão não podem produzir de diagnóstico, e as otimizações executadas no modo de versão podem mascarar qualquer de MSIL em cenários de bloqueio de carregador.

Como depurar problemas de bloqueio de carregador

O CLR de diagnóstico que gerencia quando uma função de CLR é MSIL faz chamadas suspender a execução. Por sua vez, isso faz com que o depurador de modo misto do Visual C++ para ser suspenso também ao executar o debuggee em processo. No entanto, ao anexar ao processo, não é possível obter um $callstack gerenciada para o debuggee usando o depurador misto.

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

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

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

    Segundo, o PDBs para mscoree.dll e mscorwks.dll pode ser baixado do servidor do símbolo da Microsoft. Para configurar o servidor do símbolo, abra a caixa de diálogo opções do caminho de pesquisa de símbolo. (No menu ferramentas, opções de clique em. No painel esquerdo da caixa de diálogo opções, abra o nó de depuração e clique símbolos.) Adicione o seguinte caminho de pesquisa à lista de pesquisa: http://msdl.microsoft.com/download/symbols. Adicionar um diretório de cache do símbolo na caixa de texto do cache do servidor de símbolo. Clique em OK.

  2. Definir o modo do depurador para o modo nativo somente.

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

  3. Iniciar o depurador (F5).

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

  5. Abra a janela pilha de chamadas. (No menu depurar, do windows em, na pilha de chamadas.) Se DllMain de incorretos ou o inicializador estático são identificados com uma seta verde. Se a função de incorretos não é identificada, as seguintes etapas devem ser realizadas para localizá-la.

  6. Abra a janela imediata (no menu depurar, do windows em seguida, imediatos.)

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

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

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

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

Exemplo

Descrição

O exemplo a seguir mostra como evitar o bloqueio de carregador por código movimentação de DllMain no construtor de um objeto global.

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

Có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

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

Saída

Module ctor initializing based on global instance of class.

Test called so linker does not throw away unused object.

Consulte também

Conceitos

Assemblies mistos (nativos e gerenciados)