czwartek, 5 czerwca 2014

Managed code vs unmanaged code

Słyszeliście czasami jak sobie dwóch kolegów programistów gada o kodzie zarządzanym/niezarządzanym, że w jednym to tak to działa a w drugim tak, że tutaj to a tutaj tamto, a wy nie bardzo wiedzieliście o co chodzi? No niby znacie .NET framework, prawda? Z racji serii wpisów traktujących o podstawach .NET framework, t tym poście postaram się przedstawić różnicę między kodem zarządzanym (managed code) a niezarządzanym (unmanaged code) w .NET Framework. A post będzie krótki bo i też w zasadzie nie ma o czym gadać a temat prosty i przyjemny. 

Gdy piszemy aplikację, kompilujemy ją i uruchamiamy, niezależnie od tego czy jest to aplikacja desktopowa, aplikacja internetowa czy jeszcze co tam sobie wymyślimy, zawsze działa ona pod kontrolą CLR (Common Language Runtime) o którym pisałem w poprzednim poście. Jest to mechanizm, który kontroluje naszą aplikację, zarządza garbage collectorem, sprawdza czy kod jest bezpieczny i możliwy do uruchomienia, itd. Zasada jest prosta: każdy kod, którą działa pod kontrolą CLR jest nazywany kodem zarządzanym "managed code". Jeśli piszemy jakikolwiek kod w C# lub VB.NET to zawsze będzie to "managed code", innymi słowy będzie to zawsze kod zarządzany przez CLR. 
Unmanaged code jest z kolei kompilowany bezpośrednio do kodu maszynowego. W .NET Framework kod niezarządzany można tworzyć jedynie przy użyciu Visual C++ (oczywiście można również kompilować do kodu zarządzanego, jeśli chcemy np. aby garbage collector załatwiał za nas sprawy związane z czyszczeniem pamięci po nieużywanych obiektach, czy do weryfikacji kodu). Pomyślmy sobie o aplikacji skype jako o przykładzie "unmanage code" (napisany w C/C++). Skype możemy oczywiście uruchomić z poziomu kodu zarządzanego, np. naszej aplikacji napisanej w WinForms, jednak CLR nie ma żadnej kontroli nad kodem niezarządzanym, a co za tym idzie nie ma żadnej kontroli nad naszą aplikacją Skype. Wyobraźmy sobie teraz, że uruchomiliśmy skype z naszej aplikacji, która po chwili łapie jakiś nieobsłużony wyjątek i się wywala - zostanie ona zamknięta czy zrestartowana czy po prostu wywali się z jakiegoś innego powodu. A co z naszym Skypem? - zostanie nadal uruchomiony, bo CLR nie ma nad nim żadnej kontroli. 

IL, CLR, CTS, CLS - czyli jak działa .NET framework

Czy wiecie jak działa .NET Framework? Czy zastanawialiście się, co powoduje, że kod złożony z liter i cyfr, kod który piszecie zamienia się automagicznie na aplikację widoczną gołym okiem. Często w niektórych pytaniach czy odpowiedziach na StackOverflow widziałem jak specjaliści wspominają o IL, CLR, CTS czy CLS. Ba nawet idąc na rozmowę kwalifikacyjną możesz dostać takie pytanie! Przecież to podstawy .NET framework! Ja sam (co wstyd się przyznać) gdy teraz przypominałem sobie wiedzę na ten temat aby popełnić ten post, dodatkowo dowiedziałem się rzeczy, o których wcześniej nie wiedziałem. Jak to się mówi: "Człowiek uczy się całe życie". Jak napisałem wcześniej, ostatnio postanowiłem to nadrobić! Postaram się wyjaśnić to w niniejszym poście co te skróty użyte w tytule oznaczają.

Aby mieć jasny obraz jak wszystko wygląda, spójrzmy najpierw na poniższy schemat zaczerpnięty z wikipedii:

Common Language Runtime diagram.svg
"Common Language Runtime diagram" autorstwa Leif Arne Storset - Praca własna, based on ASCII diagram found at Wikipedia:en:Common Language Runtime. Licencja CC BY-SA 3.0 na podstawie Wikimedia Commons.

Oraz lekko zmodyfikowana, moja własna wersja tego schematu:



Teraz, postaram się zwięźle opisać to co widać na schemacie powyżej, czyli jak to wszystko działa.
  • piszemy swój kod wykorzystując c# bądź vb.net bądź inny język .NET framework (chociaż niekoniecznie)
  • nasz kod musimy skompilować, więc robi to dla nas kompilator, odpowiedni dla danego języka
  • kompilator tworzy tzw. pośredni kod - common intermediate language (CIL bądź IL). Można go rozumieć jako "pół-skompilowany kod" dlatego, że mimo, iż zawiera już pewne instrukcje to kod jeszcze być skompilowany pod odpowiednia platformę i zoptymalizowany pod odpowiednie CPU podczas uruchamiania zwanego runtime. IL jest bezpośrednio tłumaczony dalej na kod bajtowy.
  • następnie nasz IL trafia do CLR czyli Common Language Runtime. To miejsce jest właściwie sercem całego .NET framework. Po otrzymaniu kodu, IL "wrzuca go" do komponentu zwanego JIT (Just in time compiler), który zamienia kod bajtowy na serię instrukcji dla specyficznej platformy. Następnie takie instrukcje są wykonywane. 
Powyższy opis pokazuje przez jakie poszczególne komponenty należy przejść aby nasz kod źródłowy zamienić na kod natywny. Jednak niektóre komponenty muszą być uszczegółowione. Zatem poniżej jeszcze króciutka wzmianka o niektórych komponentach i ich zadaniach:
Common Language Runtime:

  • wbudowany garbage collector - możemy sobie pomyśleć o nim jak o pewnym wątku chodzącym w tle i sprawdzającym, które obiekty w których obszary pamięci są nieużywane i mogą być usunięte z pamięci.
  • Zamienia kod pośredni na kod natywny
  • wbudowany CAS (Code Access Security) - mechanizm kontroli bezpieczeństwa aplikacji oparty na uprawnieniach kodu
  • CV (Code verification) - sprawdza poprawność kodu, np, czy metoda została wywołana z odpowiednią ilością parametrów

CTS (Common type system)

  • jest standardem, który opisuje jak poszczególne typy danych reprezentowane są w pamięci. dzięki któremu możemy wywoływać odpowiedni kod dowolnego języka .NET framework. Innymi słowy, komponent ten zapewnia, że jeśli mamy pewien typ danych zdefiniowany w 2 różnych językach to po kompilacji ten typ danych będzie rozpoznawany jako wspólny typ, dzięki czemu można łatwo komunikować się między dwoma językami. 
CLS Common Language Specification
  • jest to specyfikacja, która zawiera zbiór pewnych zasad i reguł określających jak kod aplikacji może być zamieniony na kod bytowy. Brzmi tragicznie - ale co to właściwie oznacza w przypadku .NET Framework? Oznacza to, że pisząc nasz kod zgodnie z pewnymi określonymi i konkretnymi zasadami w dowolnym języku, będzie on kompatybilny z .NET Framework. Przykład: C# jest językiem w którym rozróżniana jest wielkość liter, VB.NET jest językiem, w którym wielkość liter nie ma znaczenia. Gdy w C# zadeklarujemy 2 zmienne o takiej samej nazwie ale różniące się wielkością liter, kod skompiluje się - ale będzie niepoprawny dla VB.NET. Jest to przykład tzw. CLS-non-compliant code.