Składniki samoopisujące się i metadane

W przeszłości składnik oprogramowania (.exe lub .dll), który został napisany w jednym języku, nie mógł łatwo użyć składnika oprogramowania napisanego w innym języku. COM przedstawił krok w kierunku rozwiązania tego problemu. Platforma .NET ułatwia współdziałanie składników, umożliwiając kompilatorom emitowanie dodatkowych informacji deklaratywnych do wszystkich modułów i zestawów. Te informacje, nazywane metadanymi, pomagają składnikom bezproblemowo wchodzić w interakcje.

Metadane to informacje binarne opisujące program, który jest przechowywany w przenośnym pliku wykonywalnym (PE) środowiska uruchomieniowego języka wspólnego lub w pamięci. Podczas kompilowania kodu do pliku PE metadane są wstawiane do jednej części pliku, a kod jest konwertowany na wspólny język pośredni (CIL) i wstawiony do innej części pliku. Każdy typ i element członkowski, do którego odwołuje się moduł lub zestaw, jest opisany w metadanych. Po wykonaniu kodu środowisko uruchomieniowe ładuje metadane do pamięci i odwołuje się do niego, aby odnaleźć informacje o klasach, elementach członkowskich, dziedziczeniu i tak dalej.

Metadane opisują każdy typ i element członkowski zdefiniowany w kodzie w sposób neutralny dla języka. Metadane przechowują następujące informacje:

  • Opis zestawu.

    • Tożsamość (nazwa, wersja, kultura, klucz publiczny).

    • Wyeksportowane typy.

    • Inne zestawy, od których zależy ten zestaw.

    • Uprawnienia zabezpieczeń potrzebne do uruchomienia.

  • Opis typów.

    • Zaimplementowano nazwy, widoczność, klasę bazową i interfejsy.

    • Składowe (metody, pola, właściwości, zdarzenia, typy zagnieżdżone).

  • Atrybuty.

    • Dodatkowe elementy opisowe modyfikujące typy i składowe.

Zalety metadanych

Metadane są kluczem do prostszego modelu programowania i eliminuje konieczność użycia plików języka IDL (Interface Definition Language), plików nagłówków lub dowolnej zewnętrznej metody odwołania do składników. Metadane umożliwiają językom platformy .NET automatyczne opisywanie siebie w sposób neutralny dla języka, niezaznakowany zarówno przez dewelopera, jak i użytkownika. Ponadto metadane są rozszerzalne za pomocą atrybutów. Metadane zapewniają następujące główne korzyści:

  • Samoopisujące się pliki.

    Moduły i zestawy środowiska uruchomieniowego języka wspólnego opisują się samodzielnie. Metadane modułu zawierają wszystkie elementy potrzebne do interakcji z innym modułem. Metadane automatycznie udostępniają funkcje języka IDL w modelu COM, dzięki czemu można użyć jednego pliku zarówno do definicji, jak i implementacji. Moduły i zestawy środowiska uruchomieniowego nie wymagają nawet rejestracji w systemie operacyjnym. W związku z tym opisy używane przez środowisko uruchomieniowe zawsze odzwierciedlają rzeczywisty kod w skompilowanym pliku, co zwiększa niezawodność aplikacji.

  • Współdziałanie języka i łatwiejsze projektowanie oparte na składnikach.

    Metadane zawierają wszystkie informacje wymagane do dziedziczenia klasy z pliku PE napisanego w innym języku. Możesz utworzyć wystąpienie dowolnej klasy napisanej w dowolnym języku zarządzanym (dowolnym języku przeznaczonym dla środowiska uruchomieniowego języka wspólnego) bez obaw o jawne marshalling lub użycie niestandardowego kodu współdziałania.

  • Atrybuty.

    Platforma .NET umożliwia deklarowanie określonych rodzajów metadanych nazywanych atrybutami w skompilowanym pliku. Atrybuty można znaleźć na platformie .NET i kontrolować bardziej szczegółowo sposób działania programu w czasie wykonywania. Ponadto można emitować własne niestandardowe metadane do plików platformy .NET za pomocą atrybutów niestandardowych zdefiniowanych przez użytkownika. Aby uzyskać więcej informacji, zobacz Atrybuty.

Struktura pliku PE i metadanych

Metadane są przechowywane w jednej sekcji przenośnego pliku wykonywalnego (PE) platformy .NET, podczas gdy wspólny język pośredni (CIL) jest przechowywany w innej sekcji pliku PE. Część pliku stanowiąca metadane zawiera szereg struktur danych w postaci tabeli i stosu. Część CIL zawiera tokeny CIL i metadanych odwołujące się do części metadanych pliku PE. Tokeny metadanych mogą wystąpić podczas używania narzędzi, takich jak dezasembler IL (Ildasm.exe), aby na przykład wyświetlić kod CIL.

Tabele i stosy metadanych

Każda tabela metadanych zawiera informacje o elementach programu. Na przykład jedna tabela metadanych opisuje klasy w kodzie, inna opisuje pola itd. Jeśli w kodzie istnieje dziesięć klas, tabela klas będzie zawierała dziesięć wierszy, po jednym dla każdej klasy. Tabele metadanych odwołują się do innych tabel i stosów. Na przykład tabela metadanych klas odwołuje się do tabeli metod.

W metadanych informacje są przechowywane w czterech strukturach stosów: ciągu tekstowym, dużym obiekcie binarnym, ciągu tekstowym użytkownika i identyfikatorze GUID. Wszystkie ciągi tekstowe używane do nazywania typów i elementów członkowskich są przechowywane w stosie będącym ciągiem tekstowym. Na przykład tabela metod nie przechowuje bezpośrednio nazwy konkretnej metody, ale wskazuje nazwę metody zapisaną w stercie będącej ciągiem tekstowym.

Tokeny metadanych

Każdy wiersz każdej tabeli metadanych jest jednoznacznie identyfikowany w części CIL pliku PE za pomocą tokenu metadanych. Tokeny metadanych są koncepcyjnie podobne do wskaźników, utrwalone w wzorniku, które odwołują się do określonej tabeli metadanych.

Token metadanych jest czterobajtową liczbą. Pierwszy bajt określa tabelę metadanych, do której odwołuje się dany token (metoda, typ itd.). Pozostałe trzy bajty określają wiersz w tabeli metadanych, który odpowiada opisywanemu elementowi programistycznemu. Jeśli zdefiniujesz metodę w języku C# i skompilujesz ją w pliku PE, w części CIL pliku PE może istnieć następujący token metadanych:

0x06000004

Górny bajt (0x06) wskazuje, że jest to token MethodDef . Dolne trzy bajty (000004) informują środowisko uruchomieniowe języka wspólnego o wyszukaniu w czwartym wierszu tabeli MethodDef informacji opisujących tę definicję metody.

Metadane w pliku PE

Gdy program jest kompilowany dla środowiska uruchomieniowego języka wspólnego, ulega przekształceniu na plik PE składający się z trzech części. W tabeli poniżej opisano zawartość każdej części.

Sekcja PE Zawartość sekcji PE
Nagłówek PE Indeks głównych sekcji pliku PE i adres punktu wejścia.

Na podstawie tych informacji środowisko uruchomieniowe identyfikuje plik jako plik PE i ustala podczas ładowania programu do pamięci, gdzie się rozpoczyna wykonywanie.
Instrukcje dotyczące wzornika Instrukcje języka pośredniego firmy Microsoft (CIL) tworzące kod. Wiele instrukcji CIL jest dołączonych do tokenów metadanych.
Metadane Tabele i stosy metadanych. Na podstawie tej sekcji środowisko uruchomieniowe rejestruje informacje o każdym typie i elemencie członkowskim istniejącym w kodzie. Ta sekcja zawiera również niestandardowe atrybuty i informacje o zabezpieczeniach.

Użycie metadanych w czasie wykonywania

Aby lepiej zrozumieć metadane i rolę w środowisku uruchomieniowym języka wspólnego, warto skonstruować prosty program i zilustrować wpływ metadanych na jego czas wykonywania. Poniższy przykład kodu przedstawia dwie metody wewnątrz klasy o nazwie MyApp. Metoda Main jest punktem wejścia programu, a Add metoda po prostu zwraca sumę dwóch argumentów całkowitych.

Public Class MyApp
   Public Shared Sub Main()
      Dim ValueOne As Integer = 10
      Dim ValueTwo As Integer = 20
      Console.WriteLine("The Value is: {0}", Add(ValueOne, ValueTwo))
   End Sub

   Public Shared Function Add(One As Integer, Two As Integer) As Integer
      Return (One + Two)
   End Function
End Class
using System;
public class MyApp
{
   public static int Main()
   {
      int ValueOne = 10;
      int ValueTwo = 20;
      Console.WriteLine("The Value is: {0}", Add(ValueOne, ValueTwo));
      return 0;
   }
   public static int Add(int One, int Two)
   {
      return (One + Two);
   }
}

Po uruchomieniu kodu środowisko uruchomieniowe ładuje moduł do pamięci i sprawdza metadane dla tej klasy. Po załadowaniu środowisko uruchomieniowe wykonuje obszerną analizę strumienia wspólnego języka pośredniego (CIL) metody, aby przekonwertować go na szybkie instrukcje maszyny natywnej. Środowisko uruchomieniowe używa kompilatora just in time (JIT) w celu przekonwertowania instrukcji CIL na natywny kod maszynowy jednej metody w razie potrzeby.

W poniższym przykładzie pokazano część funkcji CIL utworzoną z funkcji poprzedniego Main kodu. Listę CIL i metadanych można wyświetlić z dowolnej aplikacji .NET przy użyciu dezasemblera CIL (Ildasm.exe).

.entrypoint
.maxstack  3
.locals ([0] int32 ValueOne,
         [1] int32 ValueTwo,
         [2] int32 V_2,
         [3] int32 V_3)
IL_0000:  ldc.i4.s   10
IL_0002:  stloc.0
IL_0003:  ldc.i4.s   20
IL_0005:  stloc.1
IL_0006:  ldstr      "The Value is: {0}"
IL_000b:  ldloc.0
IL_000c:  ldloc.1
IL_000d:  call int32 ConsoleApplication.MyApp::Add(int32,int32) /* 06000003 */

Kompilator JIT odczytuje CIL dla całej metody, analizuje ją dokładnie i generuje wydajne instrukcje natywne dla metody . W IL_000dpliku napotkano token metadanych dla Add metody (/*06000003 */), a środowisko uruchomieniowe używa tokenu do zapoznania się z trzecim wierszem tabeli MethodDef .

W poniższej tabeli przedstawiono część tabeli MethodDef , do których odwołuje się token metadanych, który opisuje metodę Add . Podczas gdy w tym zestawie istnieją inne tabele metadanych i mają własne unikatowe wartości, omawiana jest tylko ta tabela.

Wiersz Względny adres wirtualny (RVA) Opóźnienie ImplFlags Flagi Nazwisko

(Wskazuje stertę ciągów).
Signature (wskazuje stertę obiektów blob).
1 0x00002050 IL

Zarządzana
Publiczne

Ponowne użycie rozwiązania

Specialname

RTSpecialName

.Konstruktor
.ctor (konstruktor)
2 0x00002058 IL

Zarządzana
Publiczne

Static

Ponowne użycie rozwiązania
Główne String
3 0x0000208c IL

Zarządzana
Publiczne

Static

Ponowne użycie rozwiązania
Dodaj int, int, int, int

Każda kolumna tabeli zawiera ważne informacje o kodzie. Kolumna RVA umożliwia środowisku uruchomieniowemu obliczenie początkowego adresu pamięci CIL definiującego tę metodę. Kolumny ImplFlags i Flags zawierają maski bitów opisujące metodę (na przykład niezależnie od tego, czy metoda jest publiczna, czy prywatna). Kolumna Name indeksuje nazwę metody ze sterta ciągu. Kolumna Signature indeksuje definicję podpisu metody w stercie obiektu blob.

Środowisko uruchomieniowe oblicza żądany adres przesunięcia z kolumny RVA w trzecim wierszu i zwraca ten adres do kompilatora JIT, który następnie przechodzi do nowego adresu. Kompilator JIT kontynuuje przetwarzanie CIL pod nowym adresem, dopóki nie napotka innego tokenu metadanych i proces zostanie powtórzony.

Przy użyciu metadanych środowisko uruchomieniowe ma dostęp do wszystkich informacji potrzebnych do załadowania kodu i przetworzenia go do instrukcji maszyny natywnej. W ten sposób metadane umożliwiają samodzielne opisywanie plików i, wraz z typowym systemem typów, dziedziczenie między językami.