Metadados e componentes autodescritivos

No passado, um componente de software (.exe ou .dll) escrito em uma linguagem não podia usar facilmente um componente de software escrito em outra linguagem. COM foi um passo para a solução desse problema. O .NET facilita ainda mais a interoperação entre componentes permitindo que compiladores emitam informações declarativas adicionais sobre todos os módulos e assemblies. Essas informações, chamadas de metadados, ajudam os componentes a interagirem perfeitamente.

Metadados são informações binárias que descrevem o seu programa, armazenadas em um arquivo PE (Portable Executable) do Common Language Runtime ou na memória. Quando você compila seu código em um arquivo PE, os metadados são inseridos em uma parte do arquivo; seu código é convertido em Common Intermediate Language (CIL) e inserido em outra parte do arquivo. Cada tipo e membro definido e referenciado em um módulo ou assembly é descrito em metadados. Quando o código é executado, o runtime carrega os metadados na memória e os referencia para descobrir informações sobre suas classes de código, membros, herança etc.

Os metadados descrevem cada tipo e membro definido no seu código de maneira neutra em relação à linguagem. Os metadados armazenam as seguintes informações:

  • Descrição do assembly.

    • Identidade (nome, versão, cultura, chave pública).

    • Os tipos que são exportados.

    • Outros assemblies dos quais esse assembly dependa.

    • Permissões de segurança necessárias à execução.

  • Descrição dos tipos.

    • Nome, visibilidade, classe base e interfaces implementadas.

    • Membros (métodos, campos, propriedades, eventos, tipos aninhados).

  • Atributos.

    • Elementos descritivos adicionais que modificam tipos e membros.

Benefícios dos metadados

Os metadados são a chave para um modelo de programação mais simples e eliminam a necessidade de arquivos IDL (Interface Definition Language), arquivos de cabeçalho ou qualquer método externo de referência a componente. Os metadados permitem que linguagens .NET se descrevam automaticamente com neutralidade de idioma sem que o desenvolvedor e o usuário vejam. Além disso, metadados são extensíveis pelo uso de atributos. Os metadados oferecem os seguintes benefícios principais:

  • Arquivos autodescritivos.

    Módulos e assemblies do Common Language Runtime são autodescritivos. Os metadados de um módulo contêm tudo de que ele precisa para interagir com outro módulo. Como os metadados fornecem automaticamente a funcionalidade de IDL em COM, você pode usar um arquivo para definição e implementação. Módulos e assemblies do runtime sequer exigem o registro no sistema operacional. Como resultado, as descrições usadas pelo runtime sempre refletem o código real no arquivo compilado, o que aumenta a confiabilidade do aplicativo.

  • Interoperabilidade de linguagem e facilidade de design com base em componentes.

    Os metadados fornecem todas as informações necessárias sobre código compilado para você herdar uma classe de um arquivo PE escrita em uma linguagem diferente. Você pode criar uma instância de qualquer classe escrita em qualquer linguagem gerenciada (qualquer linguagem que segmente o Common Language Runtime), sem se preocupar com o marshalling explícito ou com o uso de código de interoperabilidade personalizado.

  • Atributos.

    O .NET permite declarar tipos específicos de metadados, chamados atributos, no seu arquivo compilado. Os atributos podem ser encontrados em todo o .NET e são usados para controlar mais detalhadamente como o seu programa se comporta no tempo de execução. Além disso, você pode emitir seus próprios metadados personalizados em arquivos do .NET por meio de atributos personalizados definidos pelo usuário. Para obter mais informações, consulte Atributos.

Metadados e a estrutura de arquivos PE

Os metadados são armazenados em uma seção de um arquivo executável portátil (PE) do .NET, enquanto a Common Intermediate Language (CIL) é armazenada em outra seção do arquivo PE. A parte de metadados do arquivo contém uma série de estruturas de tabela e de dados do heap. A parte da CIL contém tokens CIL e de metadados que fazem referência à parte de metadados do arquivo PE. Talvez você encontre tokens de metadados ao usar ferramentas como o Desmontador de IL (Ildasm.exe) para ver a CIL do seu código, por exemplo.

Tabelas e heaps de metadados

Cada tabela de metadados contém informações sobre os elementos do seu programa. Por exemplo, uma tabela de metadados descreve as classes em seu código, outra descreve os campos e assim por diante. Se existirem dez classes em seu código, a tabela de classes terá dez linhas, uma para cada classe. Tabelas de metadados referenciam outras tabelas e heaps. Por exemplo, a tabela de metadados de classes referencia a tabela de métodos.

Metadados também armazenam informações em quatro estruturas de heap: cadeia de caracteres, blob, cadeia de caracteres de usuário e GUID. Todas as cadeias de caracteres usadas para nomear tipos e membros são armazenadas no heap da cadeia de caracteres. Por exemplo, uma tabela de métodos não armazena diretamente o nome de um método específico, mas aponta para o nome do método armazenado no heap da cadeia de caracteres.

Tokens de metadados

Cada linha de cada tabela de metadados é identificada com exclusividade na parte da CIL do arquivo PE por um token de metadados. Os tokens de metadados são conceitualmente semelhantes a ponteiros; são persistidos na CIL e fazem referência a uma tabela de metadados específica.

Um token de metadados é um número de quatro bytes. O byte superior denota a tabela de metadados a que um token específico se refere (método, tipo etc.). Os três bytes restantes especificam a linha na tabela de metadados que corresponde ao elemento de programação descrito. Se você definir um método em C# e compilá-lo em um arquivo PE, o seguinte token de metadados poderá existir na parte da CIL do arquivo PE:

0x06000004

O byte superior (0x06) indica que esse é um token MethodDef. Os três bytes inferiores (000004) pedem para o Common Language Runtime examinar na quarta linha da tabela MethodDef em busca das informações que descrevem essa definição de método.

Metadados em um arquivo PE

Quando um programa é compilado para o Common Language Runtime, ele é convertido em um arquivo PE que consiste em três partes. A tabela a seguir descreve o conteúdo de cada parte.

Seção PE Conteúdo da seção PE
Cabeçalho PE O índice das seções principais do arquivo PE e o endereço do ponto de entrada.

O ambiente de runtime usa essas informações para identificar o arquivo como um arquivo PE e determinar onde a runtime começa ao carregar o programa na memória.
Instruções da CIL As instruções da linguagem intermediária comum (CIL) da Microsoft que compõem seu código. Muitas instruções da CIL vêm acompanhadas de tokens de metadados.
Metadados Tabelas e heaps de metadados. O runtime usa esta seção para registrar informações todos os tipos e membros em seu código. Esta seção também inclui atributos personalizados e informações de segurança.

Uso de metadados em tempo de execução

Para compreender melhor os metadados e sua função no Common Language Runtime, pode ser útil criar um programa simples e ilustrar como os metadados afetam sua própria vida de tempo de execução. O exemplo de código a seguir mostra dois métodos dentro de uma classe chamada MyApp. O método Main é o ponto de entrada do programa, e o método Add apenas retorna a soma dos dois argumentos inteiros.

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);
   }
}

Quando o código é executado, o runtime carrega o módulo na memória e consulta os metadados dessa classe. Após ter sido carregado, o runtime executa uma análise abrangente do fluxo da Common Intermediate Language (CIL) do método para convertê-lo em instruções de máquina nativas rápidas. O runtime usa um compilador just-in-time (JIT) para converter as instruções da CIL em um código de máquina nativo, um método de cada vez, conforme necessário.

O exemplo a seguir mostra uma parte da CIL produzida a partir da função Main do código anterior. Você pode ver a CIL e os metadados de qualquer aplicativo .NET usando o Desmontador de IL (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 */

O compilador JIT lê a CIL para o método inteiro e a analisa minuciosamente, além de gerar instruções nativas eficientes para o método. No IL_000d, um token de metadados do método Add (/* 06000003 */) é encontrado e o runtime usa o token para consultar a terceira linha da tabela MethodDef.

A tabela a seguir mostra parte da tabela MethodDef referenciada pelo token de metadados que descreve o método Add. Embora haja outras tabelas de metadados nesse assembly e elas tenham seus próprios valores exclusivos, somente essa tabela é examinada.

Linha RVA (endereço virtual relativo) ImplFlags Flags Nome

(Aponta para o heap da cadeia de caracteres.)
Assinatura (Aponta para o heap de blob.)
1 0x00002050 IL

Gerenciado
Público

ReuseSlot

SpecialName

RTSpecialName

.ctor
.ctor (construtor)
2 0x00002058 IL

Gerenciado
Público

Estático

ReuseSlot
Principal String
3 0x0000208c IL

Gerenciado
Público

Estático

ReuseSlot
Adicionar int, int, int

Cada coluna da tabela contém informações importantes sobre seu código. A coluna RVA permite que o runtime calcule o endereço de memória inicial da CIL que define esse método. As colunas ImplFlags e Flags contêm bitmasks que descrevem o método (por exemplo, se o método é público ou particular). A coluna Nome indexa o nome do método com base no heap da cadeia de caracteres. A coluna Assinatura indexa a definição da assinatura do método no heap de blob.

O runtime calcula o endereço de deslocamento desejado com base na coluna RVA na terceira linha e retorna esse endereço para o compilador JIT, que depois passa para o novo endereço. O compilador JIT continua processando a CIL no novo endereço até encontrar outro token de metadados, ocasião em que o processo é repetido.

Usando metadados, o ambiente de runtime tem acesso a todas as informações necessárias para carregar seu código e processá-lo em instruções de máquina nativas. Dessa maneira, os metadados permitem arquivos autodescritivos e, com o CTS, a herança entre linguagens.