Inicjalizacja zestawów mieszanych
Program Visual C++ .NET i Visual C++ 2003 bibliotek DLL skompilowany z /clr opcję kompilatora może nie deterministically zakleszczenie po załadowaniu; Ten problem był nazywany mieszanych DLL ładowania lub problem blokady modułu ładującego.Prawie wszystkie non determinizm został usunięty z mieszanego procesu ładowania biblioteki DLL.Istnieje jednak kilka pozostałych scenariuszy, dla których program ładujący blokady (deterministically) mogą wystąpić.Aby uzyskać więcej informacji na temat tego problemu, zobacz "Mieszane DLL ładowania Problem" w MSDN Library.
Kod w ramach DllMain nie muszą uzyskać dostępu do środowiska CLR.Oznacza to, że DllMain powinien zrobić nie wywołania funkcji zarządzanych, bezpośrednio lub pośrednio; nie kodu zarządzanego powinny być zadeklarowane lub realizowane w DllMain; i nie wyrzucania elementów bezużytecznych lub ładowania biblioteki automatyczne powinno odbywać się w ramach DllMain.
[!UWAGA]
Program Visual C++ 2003 pod warunkiem _vcclrit.h w celu ułatwienia Inicjowanie DLL minimalizując możliwość zakleszczenia.Przy użyciu _vcclrit.h nie jest już konieczne i powoduje zaniechania ostrzeżenia do wyprodukowania podczas kompilacji.Zalecana strategia jest aby usunąć zależności ten plik przy użyciu kroków w Removing Deprecated Header File _vcclrit.h.Mniej idealne rozwiązania obejmują pomijanie ostrzeżenia, definiując _CRT_VCCLRIT_NO_DEPRECATE przed _vcclrit.h łącznie lub jedynie ignorowanie ostrzeżeń oczekiwany.
Przyczyny blokady modułu ładującego
Wraz z wprowadzeniem platformy .NET są dwa różne mechanizmy ładowania modułu wykonanie (EXE lub DLL): jeden dla systemu Windows, który jest używany dla modułów niezarządzane, a drugi dla platformy .NET Common Language Runtime (CLR) które ładuje zestawów .NET.Mieszane problem ładowania DLL skupia się wokół modułu ładującego system operacyjny Microsoft Windows.
Po złożeniu zawierające tylko .NET konstrukcje jest załadowany do procesu, moduł ładujący CLR można wykonywać wszystkie niezbędne zadania ładowania i inicjowania sam.Jednak dla mieszanych zestawów, ponieważ mogą one zawierać kodu macierzystego i danych, moduł ładujący systemu Windows musi być także używany.
Moduł ładujący systemu Windows gwarantuje, że kod nie można kodu dostępu lub danych w tym pliku DLL przed został zainicjowany, i że żaden kod nadmiarowo można załadować biblioteki DLL, podczas gdy jest częściowo zainicjowany.Aby to zrobić, moduł ładujący systemu Windows używa procesu globalnego sekcji krytycznej (często nazywane "blokadę modułu ładującego"), uniemożliwiający dostęp niebezpieczne podczas inicjowania modułu.W efekcie proces ładowania jest zagrożony wiele scenariuszy klasyczne zakleszczenia.Dla zestawów mieszanych poniższe dwa scenariusze zwiększać ryzyko zakleszczenia:
Po pierwsze, jeśli użytkownicy próbują wykonać funkcji opracowanych w celu języka Microsoft intermediate language (MSIL), gdy blokada modułu ładującego (z DllMain lub w inicjatory statycznych, na przykład), może to spowodować zakleszczenie.Należy rozważyć przypadek, w którym funkcja MSIL odwołuje się do typu w zestawie, do którego nie został załadowany.Środowisko CLR spróbuje automatycznie załadować tego zestawu, co może wymagać moduł ładujący systemu Windows do blokowania na blokadę modułu ładującego.Ponieważ blokadę modułu ładującego jest już w posiadaniu kod wcześniej w sekwencję wywołań, wyniki zakleszczenia.Jednak wykonywanie MSIL pod blokady modułu ładującego nie gwarantuje wystąpienia zakleszczenia, utrudniając zdiagnozować i naprawić tego scenariusza.W pewnych okolicznościach takie jak gdzie DLL typu odwołanie zawiera nie macierzystego konstrukcje i wszystkimi jego zależnościami zawierają nie konstrukcje macierzystego Windows loader nie jest wymagane załadować zestawu .NET typu odwołania.Ponadto wymaganego zestawu lub jego zależności mieszanych w trybie macierzystym/.NET może zostały już załadowane przez inny kod.W związku z tym zakleszczenia może być trudne do przewidzenia i mogą się różnić w zależności od konfiguracji komputera docelowego.
Po drugie podczas ładowania biblioteki dll w wersjach 1.0 i 1.1 systemu.NET Framework, CLR założyć, że blokadę modułu ładującego nie odbyło się i wykonać kilka akcji, które są nieprawidłowe w obszarze blokady modułu ładującego.Zakładając, że nie odbywa się blokadę modułu ładującego jest prawidłowy założeń czysto dll .NET, ale ponieważ mieszanych biblioteki DLL inicjowania macierzystego procedur, wymagają one macierzystego moduł ładujący systemu Windows i dlatego blokadę modułu ładującego.W związku z tym nawet jeśli autora nie próbował wykonać żadnych funkcji MSIL podczas inicjowania biblioteki DLL, było jeszcze mała możliwość zakleszczenia niedeterministyczne w wersjach 1.0 i 1.1 systemu.NET Framework.
Wszystkich innych niż determinizm został usunięty z mieszanego procesu ładowania biblioteki DLL.Osiągnięto to z tych zmian:
Środowisko CLR nie tworzy już fałszywe założenia podczas ładowania bibliotek DLL mieszanych.
Inicjowanie niezarządzane i zarządzane jest wykonywane w dwóch etapach odrębne i niezależne.Inicjowanie niezarządzanych odbywa się po raz pierwszy (za pośrednictwem DllMain) i inicjowania zarządzanej odbywa się później, poprzez.Obsługiwane NET konstrukcja o nazwie .cctor.Ten ostatni jest całkowicie przezroczyste dla użytkownika chyba że /Zl lub /NODEFAULTLIB są używane.Zobacz/NODEFAULTLIB (Ignoruj biblioteki) i /Zl (Pomiń domyślną nazwę biblioteki) uzyskać więcej informacji.
Blokady modułu ładującego może nadal występują, ale teraz odtwarzalnie występuje i jest wykrywany.Jeśli DllMain zawiera instrukcje MSIL, kompilator generuje ostrzeżenie Ostrzeżenie kompilatora (poziom 1) C4747.Ponadto CRT lub CLR spróbuje wykrywania i zgłaszania próbuje wykonać instrukcje MSIL pod blokady modułu ładującego.Wyniki wykrywania CRT w czasie wykonywania diagnostyki C Run-Time Error R6033.
Pozostała część tego dokumentu opisano scenariusze pozostałe, dla których można wykonać instrukcje MSIL pod blokadę modułu ładującego rozwiązania problemu zgodnie z każdym z tych scenariuszy i technik debugowania.
Scenariusze i rozwiązania problemu
Istnieje kilka różnych sytuacji, w których kod użytkownika można wykonać instrukcje MSIL pod blokady modułu ładującego.Programista musi zapewniać, że implementację kodu użytkownika nie próbuje wykonać instrukcje MSIL zgodnie z każdym z tych okoliczności.Poniżej opisano wszystkie możliwości z omówieniem sposobu rozwiązywania problemów występujących w większości przypadków.
DllMain
Inicjatory statycznych
Dostarczone przez użytkownika funkcji wpływających na starcie
Niestandardowe ustawienia regionalne
DllMain
DllMain Funkcja to punkt wejścia zdefiniowanych przez użytkownika dla biblioteki DLL.Chyba że użytkownik określi inaczej, DllMain jest wywoływana za każdym razem, proces lub wątek dołącza go lub odłączenie od biblioteki DLL zawierającej.Ponieważ to wywołanie może pojawić się natomiast blokadę modułu ładującego odbywa nr dostarczone przez użytkownika DllMain funkcji, powinny zostać opracowane instrukcje MSIL.Ponadto żadnej funkcji w drzewie wywołanie osadzone na DllMain może zostać skompilowany do MSIL.Aby rozwiązać problemy, blok kodu, który definiuje DllMain powinny być modyfikowane za pomocą #pragma unmanaged.To samo powinno odbywać się dla każdej funkcji który DllMain wywołań.
W przypadkach gdzie tych funkcji należy wywołać funkcji, która wymaga implementacja MSIL dla kontekstów innych wywołującego strategię powielania można gdzie tworzone są zarówno .NET i macierzystą wersję tej samej funkcji.
Alternatywnie Jeśli DllMain nie jest wymagane, lub jeśli nie trzeba być wykonywane w obszarze modułu ładującego zablokować, dostarczonych przez użytkownika DllMain wykonania mogą być usunięte, która wyeliminuje problem.
Jeśli DllMain próbuje wykonać instrukcje MSIL bezpośrednio, Ostrzeżenie kompilatora (poziom 1) C4747 spowoduje.Jednak kompilator nie może wykryć przypadków gdzie wywołuje funkcję DllMain w innym module, który z kolei próbuje wykonać instrukcje MSIL.
Aby uzyskać więcej informacji na temat tego scenariusza, zobacz "Przeszkody do diagnozy".
Inicjowanie statyczne obiekty
Inicjowanie statyczne obiektów może spowodować zakleszczenie, jeśli dynamiczne inicjatora jest wymagana.Proste przypadki, gdy zmienna statyczna jest po prostu przypisany do wartość znane w czasie kompilacji nie dynamicznych inicjowania wymagana jest, więc nie ma ryzyka zakleszczenia.Jednak zmienne statyczne zainicjowany przez wywołania funkcji, wywołania konstruktora lub wyrażenia, które nie mogą być oceniane w kompilacji czas wszystkie wymagają kodu do wykonania podczas inicjowania modułu.
Poniższy kod przedstawia przykłady inicjatory statycznych, wymagające zainicjowanie dynamicznej: wywołanie funkcji, obiektów budowlanych i inicjowania wskaźnika. (Przykłady te nie są statyczne, ale są uznawane za określane są w zasięgu globalnym, która ma taki sam skutek).
// dynamic initializer function generated
int a = init();
CObject o(arg1, arg2);
CObject* op = new CObject(arg1, arg2);
To ryzyko zakleszczenie zależy od tego, czy moduł zawierający została skompilowana z /clr i będą realizowane instrukcje MSIL.W szczególności jeśli zmienna statyczna jest kompilowany bez /clr (lub przebywa w #pragma unmanaged bloku), i dynamiczne inicjator wymagane zainicjować wyniki w realizacji instrukcje MSIL, zakleszczenie może wystąpić.To dlatego dla modułów skompilowany bez /clr, inicjowanie zmiennych statycznych jest wykonywana przez DllMain.Natomiast zmienne statyczne skompilowany z /clr są inicjowane przez .cctor, po zakończeniu etapu inicjalizacji niezarządzanych i wydała blokadę modułu ładującego.
Istnieje wiele rozwiązań zakleszczenie spowodowane przez dynamiczne inicjowanie zmiennych statycznych (ułożone mniej więcej w kolejności czas wymagany, aby rozwiązać problem):
Może zostać skompilowany plik źródłowy zawierający zmiennej statycznej z /clr.
Wszystkie funkcje wywoływane przez zmienną statyczne mogą być kompilowany do kodu macierzystego za pomocą #pragma unmanaged dyrektywy.
Ręcznie klon kodzie, że zmienna statyczna zależy, zapewniając zarówno .NET i macierzystą wersję pod różnymi nazwami.Deweloperzy można wywoływać macierzystą wersję z macierzystego inicjatory statycznych i gdzie indziej wezwanie wersji .NET.
Dostarczone przez użytkownika funkcji wpływających na starcie
Istnieje kilka funkcji dostarczone przez użytkownika, od których zależy bibliotek, dla inicjowania podczas uruchamiania.Na przykład, kiedy globalnie przeciążanie operatorów w języku C++ takich jak new i delete operatory, wersje dostarczonych przez użytkownika są stosowane wszędzie, łącznie w inicjalizacji STL i zniszczenia.W rezultacie STL i inicjatory statycznych dostarczonych przez użytkownika będzie wywoływał wszelkich dostarczonych przez użytkownika wersje tych operatorów.
Jeśli wersje dostarczonych przez użytkownika są kompilowane do MSIL, inicjatory te będą podjęto próbę wykonać instrukcje MSIL natomiast odbywa się blokadę modułu ładującego.Funkcja malloc dostarczone przez użytkownika ma takie same skutki.Aby rozwiązać ten problem, żadnego z tych przeciążenia lub definicje dostarczone przez użytkownika muszą być zaimplementowane jako kodu macierzystego za pomocą #pragma unmanaged dyrektywy.
Aby uzyskać więcej informacji na temat tego scenariusza, zobacz "Przeszkody do diagnozy".
Niestandardowe ustawienia regionalne
Jeśli użytkownik udostępnia niestandardowe globalne ustawienia regionalne, to ustawienia regionalne będzie używany do inicjowania wszystkich przyszłych strumieni We/Wy, łącznie z tymi, które są inicjowane statycznie.Jeśli ten obiekt globalnych ustawień regionalnych jest skompilowany do MSIL, opracowanych w celu MSIL funkcji elementów członkowskich obiekt ustawień regionalnych może wywołany natomiast odbywa się blokadę modułu ładującego.
Możliwe są trzy opcje dotyczące rozwiązywania tego problemu:
Pliki źródłowe zawierające wszystkie definicje globalnego strumienia wejścia/wyjścia mogą być kompilowane przy wykorzystaniu /clr opcji.Pozwoli to uniknąć ich statyczne inicjatory wstrzymywane w obszarze blokady modułu ładującego.
Definicji funkcji niestandardowych ustawień regionalnych może być kompilowany do kodu macierzystego za pomocą #pragma unmanaged dyrektywy.
Powstrzymać się od ustawienia regionalne niestandardowych jako globalne ustawienia regionalne aż po zwolnieniu blokady modułu ładującego.Następnie skonfiguruj jawnie utworzone podczas inicjowania niestandardowych ustawień regionalnych strumieni We/Wy.
Przeszkody do diagnozy
W niektórych przypadkach jest trudne do wykrycia źródła zakleszczenia.Następujące podsekcje omówienia tych scenariuszy i sposobów obejścia tych problemów.
Wdrożenie w nagłówkach
W przypadkach select implementacji funkcji wewnątrz nagłówka plików może skomplikować diagnozy.Wbudowane funkcje i kod szablonu wymagają, że funkcje określone w pliku nagłówka. Język C++ Określa jedną regułę definicji, co zmusza wszystkie implementacje funkcje o tej samej nazwie semantycznie równoważne.W związku z tym C++ linker nie potrzebują sporządzać żadnych specjalnych okoliczności, scalając pliki obiektów, których zduplikowane implementacji danej funkcji.
W programie Visual C++ .NET i Visual C++ .NET 2003 linker po prostu wybiera największą z tych definicji semantycznie równoważne, aby pomieścić deklaracje do przodu i scenariusze podczas optymalizacji różne opcje są używane dla plików z innego źródła.Powoduje to problem dla mieszanych w trybie macierzystym/.NET biblioteki dll.
Ponieważ ten sam nagłówek może być zawarty zarówno przez pliki CPP z /clr włączone i wyłączone, lub #include może być wlana #pragma unmanaged bloku, jest możliwe, że zarówno macierzystej wersji funkcji, które zapewniają implementacji w nagłówkach i MSIL.Instrukcje MSIL i implementacji macierzystym mają różne składnie właściwości w odniesieniu do inicjowania w obszarze blokadę modułu ładującego, które skutecznie narusza reguły jedną definicję.W związku z tym gdy program łączący wybiera największą wykonania, może ona zdecydować się wersja MSIL funkcji, nawet jeśli jawnie został skompilowany do kodu macierzystego, gdzie indziej za pomocą dyrektywy niezarządzanych #pragma.W celu zapewnienia, że wersja MSIL szablonu lub wbudowanych funkcji nigdy nie jest nazywana pod blokady modułu ładującego, co definicji każdej takiej funkcji o nazwie pod blokady modułu ładującego musi być zmienione z #pragma unmanaged dyrektywy.W przypadku pliku nagłówka od strony trzeciej, najłatwiejszym sposobem osiągnięcia tego jest naciskać i pop niezarządzanych dyrektywa #pragma wokół #include dyrektywy do niepoprawnego pliku nagłówka. (Zobacz zarządzane, niezarządzane na przykład.) Strategia ta nie będzie działać dla nagłówków, które zawierają innego kodu, które bezpośrednio musi wywoływać interfejsy API platformy .NET.
Dla wygody użytkowników do czynienia z blokady modułu ładującego linker wybierze macierzystego wykonania nad zarządzanych po zaprezentowaniu z obu. Pozwala to uniknąć powyższych problemów. Istnieją jednak dwa wyjątki od tej zasady w tej wersji z powodu dwóch nierozwiązanych problemów z kompilatorem:
- Wywołanie ma na celu wbudowanego funkcja jest za pomocą wskaźnika globalnego funkcji statycznej. W tym scenariuszu jest szczególnie znaczące, ponieważ wirtualne funkcje są wywoływane za pomocą wskaźników funkcji globalnych. Na przykład:
#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();
}
- Z ukierunkowane Itanium kompilacji jest to błąd w realizacji wszystkich wskaźników funkcji. W poprzednim przykładzie Jeśli myObject_p zostały zdefiniowane lokalnie wewnątrz during_loaderlock(), wywołanie może także rozwiązać zarządzaną implementację.
Diagnozowanie w trybie debugowania
Wszystkie diagnozy blokady modułu ładującego, które problemy powinny być wykonane z buduje debugowania.Kompilacje wersji mogą nie dawać diagnostyki i optymalizacje wykonywane w trybie Release może maskować niektóre MSIL w scenariuszach blokady modułu ładującego.
Sposób debugowania problemów blokada modułu ładującego
Diagnostyki, która generuje CLR, gdy wywoływana jest funkcja MSIL powoduje CLR w celu wstrzymania wykonywania.Z kolei wywołuje debugera trybu mieszanego Visual C++ zostaje zawieszony, jak również podczas uruchamiania debugowanym w procesie.Jednakże dołączając do procesu, nie jest możliwe uzyskanie zarządzanego stosu wywołań za obiektem debugowanym za pomocą debugera mieszanych.
Aby zidentyfikować określoną funkcję MSIL, która została wywołana w obszarze blokady modułu ładującego, deweloperzy należy wykonać następujące kroki:
Upewnij się, że dostępne są symbole mscoree.dll i mscorwks.dll.
Można to zrobić na dwa sposoby.Po pierwsze PDB mscoree.dll i mscorwks.dll mogą być dodawane do ścieżki wyszukiwania symboli.W tym celu należy otworzyć okno dialogowe Opcje ścieżka wyszukiwania symbolu. (W menu Narzędzia kliknij polecenie Opcje.W lewym okienku okna dialogowego Opcje Otwórz węzeł debugowanie i kliknąć symbole.) Dodaj ścieżkę do plików PDB mscoree.dll i mscorwks.dll do listy wyszukiwania.Te PDB są instalowane do % VSINSTALLDIR%\SDK\v2.0\symbols.Kliknij przycisk OK.
Po drugie PDB mscoree.dll i mscorwks.dll można pobrać z serwera symboli firmy Microsoft.Aby skonfigurować serwera symboli, Otwórz okno dialogowe Opcje ścieżka wyszukiwania symbolu. (W menu Narzędzia kliknij polecenie Opcje.W lewym okienku okna dialogowego Opcje Otwórz węzeł debugowanie i kliknąć symbole.) Dodaj następującą ścieżkę wyszukiwania do listy wyszukiwania: http://msdl.microsoft.com/download/symbols.Dodaj katalog pamięci podręcznej symbol do pola tekstowego pamięci podręcznej serwera symboli.Kliknij przycisk OK.
Ustaw tryb debugera do trybu macierzystego.
Aby to zrobić, otwórz właściwości siatki dla projekt startowy w roztworze.W obszarze poddrzewo właściwości konfiguracji wybierz węzeł debugowania.Pola typ debugera ustawić tylko w trybie macierzystym.
Uruchom debuger (F5).
Gdy /clr diagnostyki jest generowany, kliknij przycisk Ponów próbę, a następnie kliknij polecenie Break.
Otwórz okno wywołanie stosu. (W menu Debugowanie kliknij Windows, a następnie stos wywołań). Jeśli przestępstwa DllMain lub statycznego inicjatora jest identyfikowana z zieloną strzałką.Jeśli funkcja powodująca problemy nie został zidentyfikowany, poniższe kroki należy go znaleźć.
Otwórz okienko bezpośrednie (w menu Debugowanie kliknij Windows, a następnie natychmiastowe).
Typ .load sos.dll w okienku bezpośrednim załadować SOS debugowania usługi.
Typ! dumpstack w okienku bezpośrednim, aby uzyskać pełną listę wewnętrznego /clr stosu.
Wyszukaj pierwszego wystąpienia (znajdujący się najbliżej końca stosu) albo _CorDllMain (Jeśli DllMain powoduje problem) lub _VTableBootstrapThunkInitHelperStub lub GetTargetForVTableEntry (Jeśli inicjator statycznych powoduje problem). Wpis stosu tuż poniżej tego wywołania jest wywoływanie MSIL zaimplementowana funkcja, którą próbował wykonać w obszarze blokady modułu ładującego.
Przejdź do pliku źródłowego oraz numer zidentyfikowanego w kroku 9 i prawidłowe wiersza problem przy użyciu scenariusze i rozwiązania opisanego w sekcji scenariuszy.
Przykład
Opis
Poniższy przykład pokazuje, jak uniknąć blokady modułu ładującego przenosząc kod z DllMain do konstruktora obiektu globalnego.
W tym przykładzie istnieje globalnego obiektu zarządzanego, którego konstruktor zawiera zarządzanym obiektem, który pierwotnie DllMain.Druga część tej próbki odwołuje się do zestawu, utworzenie wystąpienia obiektu zarządzanego do wywołania konstruktora moduł, który ma inicjowania.
Kod
// 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;
}
Przykład
Kod
// 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();
}
Dane wyjściowe
Module ctor initializing based on global instance of class.
Test called so linker does not throw away unused object.