Rozwiązywanie załadowań zestawów
Platforma .NET udostępnia zdarzenie dla aplikacji, które wymagają większej AppDomain.AssemblyResolve kontroli nad ładowaniem zestawów. Dzięki obsłudze tego zdarzenia aplikacja może załadować zestaw do kontekstu ładowania spoza normalnych ścieżek sondowania, wybrać kilka wersji zestawu do załadowania, emitować zestaw dynamiczny i zwracać go itd. Ten temat zawiera wskazówki dotyczące obsługi AssemblyResolve zdarzenia.
Uwaga
W przypadku rozpoznawania obciążeń zestawów w kontekście tylko odbicia należy użyć AppDomain.ReflectionOnlyAssemblyResolve zdarzenia.
Jak działa zdarzenie AssemblyResolve
Podczas rejestrowania programu obsługi dla AssemblyResolve zdarzenia program obsługi jest wywoływany za każdym razem, gdy środowisko uruchomieniowe nie będzie wiązać się z zestawem według nazwy. Na przykład wywołanie następujących metod z kodu użytkownika może spowodować AssemblyResolve wywołanie zdarzenia:
AppDomain.Load Przeciążenie metody lub Assembly.Load przeciążenie metody, którego pierwszy argument jest ciągiem reprezentującym nazwę wyświetlaną zestawu do załadowania (czyli ciąg zwracany przez Assembly.FullName właściwość).
AppDomain.Load Przeciążenie metody lub Assembly.Load przeciążenie metody, którego pierwszy argument jest obiektem AssemblyName identyfikującym zestaw do załadowania.
Assembly.LoadWithPartialName Przeciążenie metody.
Przeciążenie AppDomain.CreateInstance metody lub AppDomain.CreateInstanceAndUnwrap , które tworzy wystąpienie obiektu w innej domenie aplikacji.
Co robi procedura obsługi zdarzeń
Procedura obsługi zdarzenia AssemblyResolve odbiera nazwę wyświetlaną zestawu do załadowania ResolveEventArgs.Name we właściwości . Jeśli program obsługi nie rozpoznaje nazwy zestawu, zwraca wartość null
(C#), Nothing
(Visual Basic) lub nullptr
(Visual C++).
Jeśli program obsługi rozpoznaje nazwę zestawu, może załadować i zwrócić zestaw spełniający żądanie. Poniższa lista zawiera opis niektórych przykładowych scenariuszy.
Jeśli program obsługi zna lokalizację wersji zestawu, może załadować zestaw przy użyciu Assembly.LoadFrom metody lub Assembly.LoadFile i może zwrócić załadowany zestaw w przypadku powodzenia.
Jeśli program obsługi ma dostęp do bazy danych zestawów przechowywanych jako tablice bajtów, może załadować tablicę bajtów przy użyciu jednego z Assembly.Load przeciążeń metody, które przyjmują tablicę bajtów.
Program obsługi może wygenerować zestaw dynamiczny i zwrócić go.
Uwaga
Program obsługi musi załadować zestaw do kontekstu load-from, do kontekstu ładowania lub bez kontekstu. Jeśli program obsługi ładuje zestaw do kontekstu tylko odbicia przy użyciu Assembly.ReflectionOnlyLoad metody lub Assembly.ReflectionOnlyLoadFrom , próba załadowania, która wywołała AssemblyResolve zdarzenie, zakończy się niepowodzeniem.
Jest to odpowiedzialność programu obsługi zdarzeń w celu zwrócenia odpowiedniego zestawu. Procedura obsługi może przeanalizować nazwę wyświetlaną żądanego zestawu, przekazując ResolveEventArgs.Name wartość właściwości do konstruktora AssemblyName(String) . Począwszy od programu .NET Framework 4, program obsługi może użyć ResolveEventArgs.RequestingAssembly właściwości , aby określić, czy bieżące żądanie jest zależnością innego zestawu. Te informacje mogą pomóc w zidentyfikowaniu zestawu, który spełni zależność.
Program obsługi zdarzeń może zwrócić inną wersję zestawu niż żądana wersja.
W większości przypadków zestaw zwracany przez program obsługi pojawia się w kontekście ładowania, niezależnie od kontekstu, do którego program obsługi go ładuje. Jeśli na przykład program obsługi używa Assembly.LoadFrom metody w celu załadowania zestawu do kontekstu load-from, zestaw pojawi się w kontekście ładowania, gdy program obsługi zwróci go. Jednak w następującym przypadku zestaw jest wyświetlany bez kontekstu, gdy program obsługi zwróci go:
Procedura obsługi ładuje zestaw bez kontekstu.
Właściwość ResolveEventArgs.RequestingAssembly nie ma wartości null.
Zestaw żądający (czyli zestaw zwracany przez ResolveEventArgs.RequestingAssembly właściwość) został załadowany bez kontekstu.
Aby uzyskać informacje o kontekstach, zobacz Assembly.LoadFrom(String) przeciążenie metody.
Wiele wersji tego samego zestawu można załadować do tej samej domeny aplikacji. Ta praktyka nie jest zalecana, ponieważ może prowadzić do problemów z przypisywaniem typów. Zobacz Najlepsze rozwiązania dotyczące ładowania zestawów.
Co program obsługi zdarzeń nie powinien wykonywać
Podstawową regułą obsługi AssemblyResolve zdarzenia jest to, że nie należy próbować zwracać zestawu, którego nie rozpoznajesz. Podczas pisania programu obsługi należy wiedzieć, które zestawy mogą powodować wywoływanie zdarzenia. Procedura obsługi powinna zwracać wartość null dla innych zestawów.
Ważne
Począwszy od programu .NET Framework 4, AssemblyResolve zdarzenie jest wywoływane dla zestawów satelitarnych. Ta zmiana ma wpływ na program obsługi zdarzeń napisany dla starszej wersji programu .NET Framework, jeśli program obsługi próbuje rozwiązać wszystkie żądania ładowania zestawów. Programy obsługi zdarzeń, które ignorują zestawy, których nie rozpoznają, nie mają wpływu na tę zmianę: zwracają null
one i są przestrzegane normalne mechanizmy rezerwowe.
Podczas ładowania zestawu program obsługi zdarzeń nie może używać żadnego AppDomain.Load przeciążenia metody lub Assembly.Load , które mogą powodować AssemblyResolve rekursywne wywoływanie zdarzenia, ponieważ może to prowadzić do przepełnienia stosu. (Zobacz listę podaną wcześniej w tym temacie). Dzieje się tak nawet w przypadku podania obsługi wyjątków dla żądania ładowania, ponieważ nie jest zgłaszany żaden wyjątek do momentu zwrócenia wszystkich procedur obsługi zdarzeń. W związku z tym poniższy kod powoduje przepełnienie stosu, jeśli MyAssembly
nie zostanie znalezione:
using System;
using System.Reflection;
class BadExample
{
static void Main()
{
AppDomain ad = AppDomain.CreateDomain("Test");
ad.AssemblyResolve += MyHandler;
try
{
object obj = ad.CreateInstanceAndUnwrap(
"MyAssembly, version=1.2.3.4, culture=neutral, publicKeyToken=null",
"MyType");
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
}
}
static Assembly MyHandler(object source, ResolveEventArgs e)
{
Console.WriteLine("Resolving {0}", e.Name);
// DO NOT DO THIS: This causes a StackOverflowException
return Assembly.Load(e.Name);
}
}
/* This example produces output similar to the following:
Resolving MyAssembly, Version=1.2.3.4, Culture=neutral, PublicKeyToken=null
Resolving MyAssembly, Version=1.2.3.4, Culture=neutral, PublicKeyToken=null
...
Resolving MyAssembly, Version=1.2.3.4, Culture=neutral, PublicKeyToken=null
Resolving MyAssembly, Version=1.2.3.4, Culture=neutral, PublicKeyToken=null
Process is terminated due to StackOverflowException.
*/
Imports System.Reflection
Class BadExample
Shared Sub Main()
Dim ad As AppDomain = AppDomain.CreateDomain("Test")
AddHandler ad.AssemblyResolve, AddressOf MyHandler
Try
Dim obj As object = ad.CreateInstanceAndUnwrap(
"MyAssembly, version=1.2.3.4, culture=neutral, publicKeyToken=null",
"MyType")
Catch ex As Exception
Console.WriteLine(ex.Message)
End Try
End Sub
Shared Function MyHandler(ByVal source As Object, _
ByVal e As ResolveEventArgs) As Assembly
Console.WriteLine("Resolving {0}", e.Name)
// DO NOT DO THIS: This causes a StackOverflowException
Return Assembly.Load(e.Name)
End Function
End Class
' This example produces output similar to the following:
'
'Resolving MyAssembly, Version=1.2.3.4, Culture=neutral, PublicKeyToken=null
'Resolving MyAssembly, Version=1.2.3.4, Culture=neutral, PublicKeyToken=null
'...
'Resolving MyAssembly, Version=1.2.3.4, Culture=neutral, PublicKeyToken=null
'Resolving MyAssembly, Version=1.2.3.4, Culture=neutral, PublicKeyToken=null
'
'Process is terminated due to StackOverflowException.
using namespace System;
using namespace System::Reflection;
ref class Example
{
internal:
static Assembly^ MyHandler(Object^ source, ResolveEventArgs^ e)
{
Console::WriteLine("Resolving {0}", e->Name);
// DO NOT DO THIS: This causes a StackOverflowException
return Assembly::Load(e->Name);
}
};
void main()
{
AppDomain^ ad = AppDomain::CreateDomain("Test");
ad->AssemblyResolve += gcnew ResolveEventHandler(&Example::MyHandler);
try
{
Object^ obj = ad->CreateInstanceAndUnwrap(
"MyAssembly, version=1.2.3.4, culture=neutral, publicKeyToken=null",
"MyType");
}
catch (Exception^ ex)
{
Console::WriteLine(ex->Message);
}
}
/* This example produces output similar to the following:
Resolving MyAssembly, Version=1.2.3.4, Culture=neutral, PublicKeyToken=null
Resolving MyAssembly, Version=1.2.3.4, Culture=neutral, PublicKeyToken=null
...
Resolving MyAssembly, Version=1.2.3.4, Culture=neutral, PublicKeyToken=null
Resolving MyAssembly, Version=1.2.3.4, Culture=neutral, PublicKeyToken=null
Process is terminated due to StackOverflowException.
*/
Prawidłowy sposób obsługi rozwiązania AssemblyResolve
Podczas rozpoznawania zestawów z AssemblyResolve programu obsługi zdarzeń w końcu zostanie zgłoszony, StackOverflowException jeśli program obsługi używa Assembly.Load wywołań metody lub AppDomain.Load . Zamiast tego należy użyć LoadFile metod lub LoadFrom , ponieważ nie zgłaszają AssemblyResolve
zdarzenia.
Wyobraź sobie, że MyAssembly.dll
znajduje się w pobliżu wykonywanego zestawu, w znanej lokalizacji, można go rozpoznać przy użyciu Assembly.LoadFile
podanej ścieżki do zestawu.
using System;
using System.IO;
using System.Reflection;
class CorrectExample
{
static void Main()
{
AppDomain ad = AppDomain.CreateDomain("Test");
ad.AssemblyResolve += MyHandler;
try
{
object obj = ad.CreateInstanceAndUnwrap(
"MyAssembly, version=1.2.3.4, culture=neutral, publicKeyToken=null",
"MyType");
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
}
}
static Assembly MyHandler(object source, ResolveEventArgs e)
{
Console.WriteLine("Resolving {0}", e.Name);
var path = Path.GetFullPath("../../MyAssembly.dll");
return Assembly.LoadFile(path);
}
}
Imports System.IO
Imports System.Reflection
Class CorrectExample
Shared Sub Main()
Dim ad As AppDomain = AppDomain.CreateDomain("Test")
AddHandler ad.AssemblyResolve, AddressOf MyHandler
Try
Dim obj As Object = ad.CreateInstanceAndUnwrap(
"MyAssembly, version=1.2.3.4, culture=neutral, publicKeyToken=null",
"MyType")
Catch ex As Exception
Console.WriteLine(ex.Message)
End Try
End Sub
Shared Function MyHandler(ByVal source As Object,
ByVal e As ResolveEventArgs) As Assembly
Console.WriteLine("Resolving {0}", e.Name)
Dim fullPath = Path.GetFullPath("../../MyAssembly.dll")
Return Assembly.LoadFile(fullPath)
End Function
End Class
using namespace System;
using namespace System::IO;
using namespace System::Reflection;
ref class Example
{
internal:
static Assembly^ MyHandler(Object^ source, ResolveEventArgs^ e)
{
Console::WriteLine("Resolving {0}", e->Name);
String^ fullPath = Path::GetFullPath("../../MyAssembly.dll");
return Assembly::LoadFile(fullPath);
}
};
void main()
{
AppDomain^ ad = AppDomain::CreateDomain("Test");
ad->AssemblyResolve += gcnew ResolveEventHandler(&Example::MyHandler);
try
{
Object^ obj = ad->CreateInstanceAndUnwrap(
"MyAssembly, version=1.2.3.4, culture=neutral, publicKeyToken=null",
"MyType");
}
catch (Exception^ ex)
{
Console::WriteLine(ex->Message);
}
}