24/12/2011

Życzenia świąteczne

Home



Z okazji Świąt Bożego Narodzenia życzę czytelnikom i czytelniczkom wszystkiego dobrego, żeby najbliższe dni spędzili w wymarzony sobie sposób, z bliskimi sobie ludźmi, a w nowym roku pomyślności i wielu ciekawych wpisów na tym blogu :)

Serdecznie pozdrawiam,
Michał Komorowski

01/12/2011

Wczytywanie podzespołów do domeny aplikacyjnej

Home

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
{
  ...
}
...

Problem z półką

Home

Jakiś czas temu próbując wykonać operację merge w TFS napotkałem na bardzo irytujący problem pod tytułem:

TF203015 The Item '' has an incompatible pending change.

Nie robiłem nic bardzo skomplikowanego. Najpierw pobrałem do gałęzi A zmiany umieszczone na półce (ang. shelve). Następnie, przy pomocy polecenia merge, chciałem do nich dodać zmiany z changeset'a z gałęzi B i w tym momencie pojawił się powyższy komunikat. Sprawdziłem też odwrotną kolejność czyli najpierw merge z gałęzi B do A, a potem pobranie kodu z półki ale błąd również wystąpił. Innymi słowy, zamiast zgłosić konflikt i umożliwić jego rozwiązanie TFS wypiął się i rzucił błędem.

Nie chciałem wykonywać "ręcznego" łączenia plików ponieważ to błędogenne i niewygodne. Zacząłem szukać rozwiązania i znalazłem sposób na obejście problemu. Daleki od ideału, ale lepszy rydz niż nic. Postąpiłem w następujący sposób:
  • Zainstalowałem Team Foundation Server Power Tools
  • Najpierw wykonałem operację merge z gałęzi B do gałęzi A.
  • Uruchomiłem Visual Studio 2010 Command Prompt.
  • Przeszedłem do katalogu z gałęzią A.
  • Wpisałem komendę tfpt unshelve
  • Wybrałem swoja półkę.
  • Rozwiązałem konflikty.
Jak widać jeśli korzystamy z komendy tfpt to zamiast otrzymać błąd dostaniemy listę wykrytych konfliktów i możliwość ich rozwiązania. Można? Ano można.