Partilhar via


Metadados e componentes autodescritivos

No passado, um componente de software (.exe ou .dll) que foi escrito em um idioma não podia facilmente usar um componente de software que foi escrito em outro idioma. A OCM constituiu um passo no sentido da resolução deste problema. O .NET torna a interoperação de componentes ainda mais fácil, permitindo que os compiladores emitam informações declarativas adicionais em todos os módulos e assemblies. Essas informações, chamadas de metadados, ajudam os componentes a interagir perfeitamente.

Metadados são informações binárias que descrevem seu programa que é armazenado em um arquivo executável portátil (PE) 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 e seu código é convertido em linguagem intermediária comum (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 tempo de execução carrega metadados na memória e faz referência a eles para descobrir informações sobre as classes, membros, herança e assim por diante do código.

Os metadados descrevem cada tipo e membro definido no seu código de uma forma neutra em termos de linguagem. Os metadados armazenam as seguintes informações:

  • Descrição da montagem.

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

    • Os tipos que são exportados.

    • Outras assembleias de que esta assembleia depende.

    • Permissões de segurança necessárias para executar.

  • 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 de componentes. Os metadados permitem que as linguagens .NET se descrevam automaticamente de uma maneira neutra em termos de linguagem, sem serem vistas tanto pelo desenvolvedor quanto pelo usuário. Além disso, os metadados são extensíveis através do uso de atributos. Os metadados oferecem os seguintes benefícios principais:

  • Arquivos autodescritivos.

    Os módulos e assemblies do Common Language Runtime são autodescritos. Os metadados de um módulo contêm tudo o que é necessário para interagir com outro módulo. Os metadados fornecem automaticamente a funcionalidade do IDL em COM, para que você possa usar um arquivo para definição e implementação. Módulos de tempo de execução e assemblies nem sequer exigem registro com o sistema operacional. Como resultado, as descrições usadas pelo tempo de execução sempre refletem o código real em seu arquivo compilado, o que aumenta a confiabilidade do aplicativo.

  • Interoperabilidade linguística e design baseado em componentes mais fácil.

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

  • Atributos.

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

Metadados e a estrutura do arquivo PE

Os metadados são armazenados em uma seção de um arquivo executável portátil (PE) .NET, enquanto a linguagem intermediária comum (CIL) é armazenada em outra seção do arquivo PE. A parte de metadados do arquivo contém uma série de estruturas de dados de tabela e pilha. A parte CIL contém CIL e tokens de metadados que fazem referência à parte de metadados do arquivo PE. Você pode encontrar tokens de metadados ao usar ferramentas como o IL Disassembler (Ildasm.exe) para exibir 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 tabela descreve os campos e assim por diante. Se você tiver dez classes em seu código, a tabela de classes terá dezenas de linhas, uma para cada classe. As tabelas de metadados fazem referência a outras tabelas e heaps. Por exemplo, a tabela de metadados para classes faz referência à tabela para métodos.

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

Tokens de metadados

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

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

0x06000004

O byte superior (0x06) indica que este é um token MethodDef . Os três bytes inferiores (000004) informam ao common language runtime para procurar na quarta linha da tabela MethodDef as 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.

Secção PE Conteúdo da secção PE
Cabeçalho PE O índice das secções principais do ficheiro PE e o endereço do ponto de entrada.

O tempo de execução usa essas informações para identificar o arquivo como um arquivo PE e para determinar onde a execução começa ao carregar o programa na memória.
Instruções CIL As instruções de idioma intermediário (CIL) da Microsoft que compõem seu código. Muitas instruções CIL são acompanhadas por tokens de metadados.
Metadados Tabelas e pilhas de metadados. O tempo de execução usa esta seção para registrar informações sobre cada tipo e membro 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 entender melhor os metadados e seu papel no Common Language Runtime, pode ser útil construir um programa simples e ilustrar como os metadados afetam sua vida útil em tempo de execução. O exemplo de código a seguir mostra dois métodos dentro de uma classe chamada MyApp. O Main método é o ponto de entrada do programa, enquanto o Add método simplesmente retorna a soma de 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 tempo de execução carrega o módulo na memória e consulta os metadados para essa classe. Uma vez carregado, o tempo de execução executa uma análise extensiva do fluxo de linguagem intermediária comum (CIL) do método para convertê-lo em instruções nativas rápidas da máquina. O tempo de execução usa um compilador just-in-time (JIT) para converter as instruções CIL em código de máquina nativo, um método de cada vez, conforme necessário.

O exemplo a seguir mostra parte da CIL produzida a partir da função do Main código anterior. Você pode exibir a CIL e os metadados de qualquer aplicativo .NET usando o CIL Disassembler (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ê o CIL para todo o método, analisa-o minuciosamente e gera instruções nativas eficientes para o método. Em IL_000d, um token de metadados para o Add método (/*06000003 */) é encontrado e o tempo de execução 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 Add método. Enquanto outras tabelas de metadados existem neste assembly e têm seus próprios valores exclusivos, apenas esta tabela é discutida.

Linha Endereço virtual relativo (RVA) ImplFlags Sinalizadores Nome

(Aponta para a pilha de cadeia de caracteres.)
Assinatura (aponta para a pilha de blob.)
1 0x00002050 IL

Não gerido
Público

ReuseSlot

Nome especial

RTSpecialName

.ctor
.ctor (construtor)
2 0x00002058 IL

Não gerido
Público

Estático

ReuseSlot
Principal String
3 0x0000208c IL

Não gerido
Público

Estático

ReuseSlot
Adicionar int, int, int

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

O tempo de execução calcula o endereço de deslocamento desejado da coluna RVA na terceira linha e retorna esse endereço para o compilador JIT, que prossegue para o novo endereço. O compilador JIT continua a processar CIL no novo endereço até encontrar outro token de metadados e o processo é repetido.

Usando metadados, o tempo de execução tem acesso a todas as informações necessárias para carregar seu código e processá-lo em instruções nativas da máquina. Desta forma, os metadados permitem arquivos autodescritivos e, juntamente com o sistema de tipo comum, herança entre idiomas.