Tipos gerenciados (C++/CLI)

O Visual C++ permite o acesso aos recursos do .NET por meio de tipos gerenciados, que dão suporte a recursos do common language runtime e estão sujeitos às vantagens e restrições do runtime.

Tipos gerenciados e a função principal

Quando você escreve um aplicativo usando /clr, os argumentos da função main() não podem ser de um tipo gerenciado.

Um exemplo de uma assinatura adequada é:

// managed_types_and_main.cpp
// compile with: /clr
int main(int, char*[], char*[]) {}

Equivalentes do .NET Framework a tipos nativos do C++

A tabela a seguir mostra as palavras-chave para tipos internos do Visual C++, que são aliases de tipos predefinidos no namespace System.

Tipo do Visual C++ Tipo de .NET Framework
void System.Void
bool System.Boolean
signed char System.SByte
unsigned char System.Byte
wchar_t System.Char
short e signed short System.Int16
unsigned short System.UInt16
int, signed int, long e signed long System.Int32
unsigned int e unsigned long System.UInt32
__int64 e signed __int64 System.Int64
unsigned __int64 System.UInt64
float System.Single
double e long double System.Double

Para obter mais informações sobre a opção do compilador para o padrão signed char ou unsigned char, confira /J (o tipo char padrão é unsigned).

Problemas de versão para tipos de valor aninhados em tipos nativos

Considere um componente de assembly assinado (nome forte) usado para criar um assembly de cliente. O componente contém um tipo de valor que é usado no cliente como o tipo para um membro de uma união nativa, uma classe ou uma matriz. Se uma versão futura do componente alterar o tamanho ou o layout do tipo de valor, o cliente precisará ser recompilado.

Crie um arquivo de chave com sn.exe (sn -k mykey.snk).

Exemplo

O exemplo a seguir é o componente.

// nested_value_types.cpp
// compile with: /clr /LD
using namespace System::Reflection;
[assembly:AssemblyVersion("1.0.0.*"),
assembly:AssemblyKeyFile("mykey.snk")];

public value struct S {
   int i;
   void Test() {
      System::Console::WriteLine("S.i = {0}", i);
   }
};

Este exemplo é o cliente:

// nested_value_types_2.cpp
// compile with: /clr
#using <nested_value_types.dll>

struct S2 {
   S MyS1, MyS2;
};

int main() {
   S2 MyS2a, MyS2b;
   MyS2a.MyS1.i = 5;
   MyS2a.MyS2.i = 6;
   MyS2b.MyS1.i = 10;
   MyS2b.MyS2.i = 11;

   MyS2a.MyS1.Test();
   MyS2a.MyS2.Test();
   MyS2b.MyS1.Test();
   MyS2b.MyS2.Test();
}

O exemplo produz essa saída:

S.i = 5
S.i = 6
S.i = 10
S.i = 11

Comentários

No entanto, se você adicionar outro membro a struct S em nested_value_types.cpp (por exemplo, double d;) e recompilar o componente sem recompilar também o cliente, o resultado será uma exceção sem tratamento (do tipo System.IO.FileLoadException).

Como testar quanto a igualdade

No exemplo a seguir, um teste para igualdade que usa Managed Extensions for C++ é baseado em a que os identificadores se referem.

Exemplo

// mcppv2_equality_test.cpp
// compile with: /clr /LD
using namespace System;

bool Test1() {
   String ^ str1 = "test";
   String ^ str2 = "test";
   return (str1 == str2);
}

O IL para este programa mostra que o valor retornado é implementado usando uma chamada para op_Equality.

IL_0012:  call       bool [mscorlib]System.String::op_Equality(string, string)

Como diagnosticar e corrigir problemas de compatibilidade do assembly

Quando a versão de um assembly referenciado em tempo de compilação não corresponde à versão do assembly referenciado em runtime, vários problemas podem ocorrer.

Quando um assembly é compilado, outros assemblies podem ser referenciados com a sintaxe #using. Durante a compilação, esses assemblies são acessados pelo compilador. As informações desses assemblies são usadas para tomar decisões de otimização.

No entanto, se o assembly referenciado for alterado e recompilado, recompile o assembly de referência que depende dele. Caso contrário, os assemblies podem se tornar incompatíveis. As decisões de otimização que eram válidas no início podem não ser corretas para a nova versão do assembly. Vários erros de runtime podem ocorrer devido a essas incompatibilidades. Não há exceção específica produzida nesses casos. A forma como a falha é relatada em runtime depende da natureza da alteração de código que causou o problema.

Esses erros não devem ser um problema no código de produção final, desde que todo o aplicativo seja recriado para a versão lançada do produto. Os assemblies lançados ao público devem ser marcados com um número de versão oficial, o que garantirá que esses problemas sejam evitados. Para obter mais informações, consulte Controle de versão do assembly.

Para diagnosticar e corrigir um erro de incompatibilidade

Você pode encontrar exceções de runtime ou outras condições de erro no código que fazem referência a outro assembly. Se você não conseguir identificar outra causa, o problema poderá ser um assembly desatualizado.

  1. Primeiro, isole e reproduza a exceção ou outra condição de erro. Um problema que ocorre devido a uma exceção desatualizada deve ser reproduzível.

  2. Verifique o carimbo de data/hora de todos os assemblies referenciados em seu aplicativo.

  3. Se os carimbos de data/hora de quaisquer assemblies referenciados forem posteriores ao carimbo de data/hora da última compilação do aplicativo, o aplicativo estará desatualizado. Se o aplicativo estiver desatualizado, recompile-o com os assemblies mais recentes e edite seu código, se necessário.

  4. Execute novamente o aplicativo e as etapas que reproduzem o problema e verifique se a exceção não ocorre.

Exemplo

O seguinte programa ilustra o problema: ele primeiro reduz a acessibilidade de um método e tenta acessar esse método em outro assembly sem recompilar. Compile changeaccess.cpp primeiro. Esse é o assembly referenciado que será alterado. Em seguida, compile referencing.cpp. Ele deve ser compilado com êxito. Depois, reduza a acessibilidade do método chamado. Recompile changeaccess.cpp com a opção /DCHANGE_ACCESS do compilador. Isso torna o método access_meprotected em vez de public, de modo que ele não pode ser chamado de fora de Test ou derivados. Sem recompilar referencing.exe, execute novamente o aplicativo. Um MethodAccessException ocorre.

// changeaccess.cpp
// compile with: /clr:safe /LD
// After the initial compilation, add /DCHANGE_ACCESS and rerun
// referencing.exe to introduce an error at runtime. To correct
// the problem, recompile referencing.exe

public ref class Test {
#if defined(CHANGE_ACCESS)
protected:
#else
public:
#endif

  int access_me() {
    return 0;
  }

};

Aqui está a origem do assembly de referência:

// referencing.cpp
// compile with: /clr:safe
#using <changeaccess.dll>

// Force the function to be inline, to override the compiler's own
// algorithm.
__forceinline
int CallMethod(Test^ t) {
  // The call is allowed only if access_me is declared public
  return t->access_me();
}

int main() {
  Test^ t = gcnew Test();
  try
  {
    CallMethod(t);
    System::Console::WriteLine("No exception.");
  }
  catch (System::Exception ^ e)
  {
    System::Console::WriteLine("Exception!");
  }
  return 0;
}

Confira também

Programação do .NET com C++/CLI (Visual C++)
Interoperabilidade com outras linguagens .NET (C++/CLI)
Tipos gerenciados (C++/CLI)
diretiva #using