Partilhar via


Inicialização de assemblies mistos

No Visual C++ .NET e Visual C++ 2003 DLLs compiladas com o /clr opção do compilador poderia não deterministicamente deadlock quando carregado; esse problema foi chamado o DLL misto carregando ou problema de bloquear de carregador. In Visual C++ 2005, quase todos os não-determinismo foi removido do mista a DLL do processo de carregamento. No entanto, há algumas restantes cenários para qual carregador de bloquear (deterministicamente) pode ocorrer.Para obter mais informações sobre esse problema, consulte "Misto DLL carregando problema" noBiblioteca MSDN.

In Visual C++ 2005 ainda há uma restrição dentro de código DllMain não deve acessar o CLR.Isso significa que DllMain não deve fazer nenhum chamadas a funções gerenciadas, direta ou indiretamente; nenhum código gerenciado deve ser declarado ou implementado no DllMain; e nenhuma coleta de lixo ou o carregamento de biblioteca automático deve levar o local no DllMain.

Observação:

O Visual C++ 2003 fornecido _ vcclrit.h para facilitar a inicialização DLL enquanto minimiza a oportunidade de deadlock.Para Visual C++ 2005, usar _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 em Como: Remover a dependência no _vcclrit.h. Menos soluções ideais incluem suprime os avisos definindo _CRT_VCCLRIT_NO_DEPRECATE antes para incluindo _vcclrit.h ou simplesmente ignorar os avisos de substituição.

Causas de bloquear do carregador

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

Quando um assembly que contém somente construções .NET é carregado em um processo, o carregador do CLR pode executar todas as tarefas necessárias de carregamento e inicialização propriamente dito.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 código de acesso ou dados nessa DLL antes que ele foi inicializado e que nenhum código de forma redundante pode carregar a DLL enquanto ela é parcialmente inicializada.Para fazer isso, o carregador do Windows usa uma processo global seção crítica (também chamada de "bloquear de carregador") que impede o acesso não seguro durante a inicialização do módulo.sistema autônomo resultado, o processo de carregamento é vulnerável a vários cenários de deadlock clássica.Para assemblies mistos, os dois cenários a seguir aumentam o risco de deadlock:

  • Primeiro, se os usuários tentarem executar funções compiladas para Microsoft intermediate linguagem (MSIL) quando o carregador bloquear é mantido (a partir de DllMain ou em inicializadores estático, por exemplo), isso pode causar dead bloquear. Considere o caso em que a função MSIL faz referência a um tipo em um assembly que não foram carregado.O CLR tentará carregar automaticamente esse assembly, que pode exigir que o carregador do Windows para bloquear o bloqueio de carregador.Desde que o bloquear de carregador já é mantido pelo código anterior na sequência de telefonar, resultados um deadlock.No entanto, a execução MSIL em deadlock de carregador não garante que um deadlock ocorra, tornando difícil diagnosticar e corrigir esse cenário.Em algumas circunstâncias, sistema autônomo em que contém a DLL do tipo referenciado não nativas construções e todas sistema autônomo suas dependências não contêm nenhum construções nativas, o carregador não é necessário para carregar o assembly .NET do tipo de referência do Windows.Além disso, o conjunto de módulos (assembly) ou suas dependências nativo/.NET misto podem ter já foram carregadas por Outros código.Conseqüentemente, o travado pode ser difícil de prever e pode variar dependendo da configuração da computador de destino.

  • Pressupõe-segundo, ao carregar DLLs nas versões 1.0 e 1.1 do .NET estrutura, o CLR se que o bloquear de carregador não foi mantido e executadas várias ações são inválidas em bloquear de carregador.Supondo que o bloquear do carregador é mantido não é uma suposição válida para puramente .NET DLLs, mas porque misto DLLs executar rotinas de inicialização nativo, eles requerem o carregador do Windows nativo e, portanto, o bloquear de carregador.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ísticas com as versões 1.0 e 1.1 do .NET estrutura.

In Visual C++ 2005, todos os não-determinismo foi removido do mista a DLL do processo de carregamento. Isso foi feito com essas alterações:

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

  • Inicialização gerenciada e não gerenciado é executada em dois estágios separados e distintos.Inicialização não gerenciada é feita pela primeira vez (por meio de DllMain), e gerenciada inicialização ocorre posteriormente, por meio de uma construção compatíveis com .NET chamada um .cctor.O ú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 mais informações.

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

O restante deste documento descreve os cenários restantes para o qual MSIL pode ser executado sob o bloquear de carregador, resoluções para esse 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 sob a qual o código do usuário pode executar MSIL em bloquear de carregador.O desenvolvedor deve garantir que a implementação de código do usuário não tenta executar instruções MSIL sob cada nessas circunstâncias.As subseções a seguir descrevem todas as possibilidades com uma discussão sobre como resolver problemas nos casos mais comuns.

  • DllMain

  • Inicializadores estático

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

  • Localidades personalizadas

DllMain

The DllMain função é um ponto de entrada definido pelo usuário para uma DLL. A menos que o usuário especifique algo em 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 bloquear do carregador é mantida, não fornecido pelo usuário DllMain função deve ser compilada para MSIL. Além disso, nenhuma função na árvore de chamadas 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 concluído para cada função que DllMain chamadas.

Em casos em que essas funções devem chamar uma função que requer uma implementação de MSIL para outros contextos de telefonar, uma estratégia de duplicação pode ser usada onde tanto um .NET e um nativo versão da 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 tentar executar o MSIL diretamente, Compilador C4747 de aviso (nível 1) Isso resultará em. No entanto, o compilador não pode detectar casos onde DllMain chama uma função em outro módulo que por sua vez tenta executar MSIL.

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

Ao inicializar objetos estático

Ao inicializar objetos estático pode resultar em deadlock se for necessário um inicializador de dinâmico.Para casos simples, sistema autônomo quando um estático variável simplesmente é atribuída a um valor conhecido em tempo de compilar, sem inicialização dinâmica é necessária, portanto não há nenhum risco de deadlock.No entanto, estático variáveis inicializadas por chamadas de função, chamadas de construtor ou expressões que não podem ser avaliadas no time exigem código seja executado durante a inicialização do módulo de compilar.

O código a seguir mostra exemplos de estático inicializadores requerem inicialização dinâmica: uma telefonar de função, construção de objeto e a inicialização do ponteiro.(Esses exemplos não são estático, mas são considerados ser 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 deadlock depende se o módulo que o contém é compilado com /clr e se MSIL será executada. Especificamente, se o estático variável é compilado sem /clr (ou reside em um # pragma unmanaged bloco) e o inicializador dinâmico necessário para inicializá-lo resultados na execução de instruções MSIL, deadlock pode ocorrer. Isso ocorre porque, para os módulos compilados sem /clr, a inicialização do estático variáveis é executada pelo DllMain. Por outro lado, estático variáveis compiladas com /clr são inicializadas pelo .cctor, depois conclui o preparar de inicialização não gerenciado e o bloquear de carregador foi liberado.

Há várias soluções para deadlock causados pela inicialização dinâmica do estático variáveis (aproximadamente organizadas em ordem de time necessário para corrigir o problema):

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

  • Todas as funções chamadas pelo estático variável pode ser compilado para código nativo usando o # pragma unmanaged diretiva.

  • Clone manualmente o código que a variável estática depende, fornecendo um .NET e uma versão nativa com nomes diferentes.Os desenvolvedores podem, em seguida, chame a versão nativa de inicializadores estático nativo e chame a versão do .NET em outro lugar.

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

Há várias funções fornecidas pelo usuário do qual bibliotecas dependem na inicialização durante a inicialização.Por exemplo, ao globalmente sobrecarregar operadores em C++, sistema autônomo a new e delete operadores, sistema autônomo versões fornecido pelo usuário são usadas em todos sistema autônomo lugares, incluindo na inicialização de STL e destruição. sistema autônomo resultado, STL e inicializadores estático fornecido pelo usuário irão chamar qualquer versão fornecido pelo usuário desses operadores.

Se as versões fornecido pelo usuário são compiladas para MSIL, em seguida, esses inicializadores serão ser tentando executar instruções MSIL enquanto o bloquear 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 sistema autônomo 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 global, este código de idioma será usado para inicializar todos os fluxos de E/S futuros, inclusive os que são inicializadas estaticamente.Se este objeto de localidade global é compilado para MSIL, em seguida, funções de membro de objeto de localidade compiladas para MSIL podem ser chamadas enquanto o bloquear do carregador é mantido.

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

Os arquivos de fonte que contém todas as definições global de fluxo de E/S podem ser compilados usando o /clr opção. Isso evitará que seus inicializadores estático seja executada em bloquear de carregador.

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

Impedir configuração localidade personalizada sistema autônomo a localidade global até após o bloquear de carregador seja liberado.Em seguida, configurar explicitamente fluxos de E/S criados durante a inicialização com a localidade personalizada.

Impedimentos diagnóstico

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

Implementação em cabeçalhos

Com o Visual C++ .NET e Visual C++ .NET 2003 e selecionar casos com Visual C++ 2005, implementações de função dentro de arquivos de cabeçalho podem complicar o diagnóstico. Funções in-line e o código de modelo exigem que funções seja especificado em um arquivo de cabeçalho.A linguagem C++ especifica a regra de definição de um, que força todas as implementações de funções com o mesmo nome a ser semanticamente equivalente.Conseqüentemente, o vinculador de C++ não precisa fazer quaisquer considerações especiais ao mesclar arquivos de objeto que 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 encaminhar declarações e cenários de opções de otimização diferentes de uso de arquivos de fonte diferente.Isso cria um problema para misto nativo/.NET DLLs.

Porque o mesmo cabeçalho pode ser incluídos tanto por um arquivos CPP com /clr habilitada e desabilitado ou um # incluem pode ser disposto dentro de um # pragma unmanaged bloco, é possível ter ambos os MSIL e nativo versões funções fornecem implementações nos cabeçalhos. MSIL e nativo implementações têm semânticas diferentes em relação à inicialização sob o bloquear de carregador, efetivamente viola a regra de uma definição.Conseqüentemente, quando o vinculador escolhe a maior implementação, ele pode escolher a versão MSIL de uma função, mesmo que ele foi compilado para código nativo em outro local usando a diretiva de não gerenciado # pragma explicitamente.Para garantir que uma versão MSIL de um modelo ou embutido função nunca é chamada em bloquear de carregador, cada definição de cada tal função chamada em bloquear de carregador deve ser modificada com a # pragma unmanaged diretiva. Se o arquivo de cabeçalho é de terceiros, a maneira mais fácil para conseguir isso é enviar e pop a diretiva de não gerenciado # pragma ao redor do # inclui diretiva para o arquivo de cabeçalho incorreto.(See gerenciado, não gerenciado Para obter um exemplo.) No entanto, essa estratégia não funcionará para os cabeçalhos que contêm Outross códigos diretamente devem telefonar .NET APIs.

In Visual C++ 2005, sistema autônomo uma conveniência para lidar com bloquear de carregador de usuários, o vinculador escolherá implementação nativa sobre gerenciado quando apresentado com ambos. Isso evita problemas acima.No entanto, há duas exceções a essa regra nesta versão devido a problemas não resolvidos dois com o compilador:

  • A telefonar é para uma linha interna função é através de um ponteiro de função estática global.Esse cenário é particularmente importante porque funções virtual são chamadas através de ponteiros de função global.Por exemplo,
#include "definesfoo.h"
#include "definesclassC.h"

typedef void (*function_pointer_t)();

function_pointer_t foo_p = &foo;

#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();
    foo_p();
}
  • Com compilação direcionada Itanium, há um bug na implementação de todos os ponteiros de função.No trecho de código acima, se foo_p foram definidas localmente dentro during_loaderlock(), a telefonar pode também resolver para um gerenciado implementação.

Diagnosticando no modo de depurar

Todos os diagnósticos de bloquear do carregador de problemas devem ser feitos com compilações de depurar.Compilações lançadas talvez não produza Diagnóstico e as otimizações realizadas no modo de versão podem mascarar parte MSIL em cenários de bloquear do carregador.

Como depurar problemas de bloquear Loader

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

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

  1. Certifique-se de que os símbolos de mscoree.dll e mscorwks.dll estejam disponível.

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

    Segundo, PDBs para mscoree.dll e mscorwks.dll podem ser baixados do Microsoft símbolo servidor.Configurar servidor de símbolo, em aberto a caixa de diálogo de opções de caminho de Pesquisar de símbolo.(No menu Ferramentas, clicar Opções.No painel esquerdo da caixa de diálogo Opções, em aberto os símbolos de nó e clicar depuração.) Adicionar o seguinte caminho de Pesquisar à lista de Pesquisar: http://MSDL.Microsoft.com/baixar/symbols.Adicionar um diretório de cache de símbolo a caixa de texto de cache do servidor de símbolo.Clique em OK.

  2. conjunto depurador modo para o modo somente para nativo.

    Para fazer isso, em aberto a grade de propriedades para o projeto de inicialização na solução.Em subárvore configuração Properties, selecionar o nó depuração.Defina o campo de tipo de depurador para somente nativo.

  3. Inicie o depurador (F5).

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

  5. em aberto a janela pilha de chamadas.(A partir do menu depurar, clicar Windows e, em seguida, telefonar pilha.) Se o ofensivo DllMain ou inicializador estático é identificado com uma seta verde. Se a função afetada não for identificada, as etapas a seguir devem ser utilizadas para localizá-lo.

  6. Abra a janela imediata (no menu depurar, clicar Windows e, em seguida, imediata.)

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

  8. Tipo! dumpstack na janela imediata para obter uma lista completa do interno**/clr** pilha.

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

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

Exemplo

Descrição

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

Neste exemplo, há um objeto gerenciado global cujo construtor contém o objeto gerenciado que era originalmente no 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.

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

Misto (nativo e gerenciado) assemblies