Platforma .NET, dzięki mechanizmowi refleksji, pozwala na dynamiczne wczytywanie do programu podzespołów (ang.
assembly). Pozwala to w łatwy sposób pisać rozszerzane przy pomocy pluginów aplikacje i na wiele innych rzeczy. Ostatnio potrzebowałem wykorzystać ten mechanizm do własnych celów. Aby zwiększyć bezpieczeństwo, postanowiłem ładować podzespoły do odzielnych domen aplikacyjnych. W ten sposób, jeśli po załadowaniu podzespołu i wykonaniu jego kodu pojawi się błąd, główna domena aplikacyjna pozostaje nienaruszona.
Użycie osobnej domeny aplikacyjnej przydaje się również kiedy chcemy wczytywać i usuwać załadowane
assembly z pamięci. Problem polega na tym, że po załadowaniu podzespołu do domeny nie ma możliwości aby go z niej usunąć. Można jednak osiągnąć podobny rezultat ładując podzespoły do oddzielnych "roboczych" domen, a potem skorzystać z metody
AppDomain.Unload, która usuwa z pamięci domenę i wszystkie wczytane do niej podzespoły. (To pewne uproszczenie. Jeśli
assembly zostało załadowane do kilku domen to zostanie usunięte dopiero jeśli usuniemy wszystkie domeny ją używające.)
Jak to zrobić? W sieci można znaleźć kilka podejść, ja użyłem w uproszczeniu następującego sposobu:
public static class SeperateDomainAssemblyLoader
{
[Serializable]
private class InternalLoader
{
public void LoadAndProcess(string assemblyPath)
{
Console.WriteLine(AppDomain.CurrentDomain.FriendlyName);
var assembly = Assembly.LoadFrom(assemblyPath);
//...
}
}
private static AppDomain _domain = AppDomain.CreateDomain("SeperateDomainAssemblyLoader");
public static void LoadAndProcess(string assemblyPath)
{
InternalLoader internalLoader = (InternalLoader)(_domain.CreateInstanceFromAndUnwrap(Assembly.GetExecutingAssembly().Location, typeof(InternalLoader).FullName));
internalLoader.LoadAndProcess(assemblyPath);
}
}
i kod testujący:
...
Console.WriteLine(AppDomain.CurrentDomain.FriendlyName);
SeperateDomainAssemblyLoader.LoadAndProcess(somePath);
...
Niestety ku mojemu zdziwieniu program wypisał na ekran dwa razy tą samą nazwę domeny. Jak to możliwe, przecież jak wół stoi, że instancja klasy
InternalLoader została stworzona w osobnej domenie. Uważni czytelnicy już pewnie widzą błąd. Ja też go znalazłem, ale chwilę zajęło mi uzmysłowienie sobie, co robię nie tak.
Zapomniałem o tym, że obiekty pomiędzy domenami aplikacyjnymi przekazywana są domyślnie przez wartość. Co z tego, że utworzyłem obiekt w osobnej domenie, skoro i tak pracowałem z jego kopią. Jeśli
InternalLoader dziedziczyłby z
MarshalByRefObject to pracowałabym nie z prawdziwym obiektem ale z
proxy i wszystko byłoby dobrze. Poprawka jest więc bardzo prosta:
...
private class InternalLoader : MarshalByRefObject
{
...
}
...