托管类型 (C++/CLI)

Visual C++ 允许通过托管类型访问 .NET 功能,这些类型为公共语言运行时的功能提供支持,并且受制于运行时的优点和限制。

托管类型和 main 函数

使用 /clr 编写应用程序时,main() 函数的参数不能为托管类型。

正确的签名示例为:

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

对应于 C++ 本机类型的 .NET Framework 类型

下表显示内置 Visual C++ 类型的关键字,即 System 命名空间中预定义类型的别名。

Visual C++ 类型 .NET Framework 类型
void System.Void
bool System.Boolean
signed char System.SByte
unsigned char System.Byte
wchar_t System.Char
shortsigned short System.Int16
unsigned short System.UInt16
intsigned intlongsigned long System.Int32
unsigned intunsigned long System.UInt32
__int64signed __int64 System.Int64
unsigned __int64 System.UInt64
float System.Single
doublelong double System.Double

有关默认为 signed charunsigned char 的编译器选项的详细信息,请参阅 /J(默认 char 类型为 unsigned

嵌套在本机类型中的值类型的版本问题

请考虑用于生成客户端程序集的已签名(强名称)程序集组件。 该组件包含一个值类型,在客户端中作为本机联合、类或数组的成员的类型。 如果组件的未来版本更改值类型的大小或布局,则必须重新编译客户端。

使用 sn.exe (sn -k mykey.snk) 创建密钥文件。

示例

以下示例是组件。

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

此示例是客户端:

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

此示例产生以下输出:

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

注释

但是,如果将另一个成员添加到 nested_value_types.cpp 中的 struct S(例如,double d;)并重新编译组件而不重新编译客户端,则结果是未经处理的异常(System.IO.FileLoadException 类型)。

如何测试相等性

在下面的示例中,使用 Managed Extensions for C++ 的相等性测试基于句柄引用的内容。

示例

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

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

此程序的 IL 显示返回值是通过调用 op_Equality 实现的。

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

如何诊断和修复程序集兼容性问题

当在编译时引用的程序集版本与运行时引用的程序集版本不匹配时,可能会出现各种问题。

编译程序集时,可以使用 #using 语法引用其他程序集。 在编译期间,编译器会访问这些程序集。 这些程序集中的信息用于做出优化决策。

但是,如果引用的程序集已更改并重新编译,则还要重新编译依赖于它的引用程序集。 否则,程序集可能变得不兼容。 对于新的程序集版本,最初有效的优化决策可能不正确。 由于这些不兼容性,可能会发生各种运行时错误。 在这种情况下,不会生成任何特定异常。 在运行时报告失败的方式取决于导致问题的代码更改的性质。

只要为产品的已发布版本重新生成整个应用程序,这些错误在最终的生产代码中就不应该是问题。 向公众发布的程序集应标有正式版本号,这将确保避免这些问题。 有关详细信息,请参阅程序集版本控制

诊断和修复不兼容性错误

在引用其他程序集的代码中,可能会遇到运行时异常或其他错误条件。 如果无法确定其他原因,问题可能是程序集过期。

  1. 首先,隔离并重现异常或其他错误条件。 由于过时异常而发生的问题应可重现。

  2. 检查应用程序中引用的任何程序集的时间戳。

  3. 如果任何引用程序集的时间戳晚于应用程序上次编译的时间戳,则应用程序已过期。 如果已过期,请使用最新的程序集重新编译应用程序,并在必要时编辑代码。

  4. 重新运行应用程序,执行重现问题的步骤,并验证是否未发生异常。

示例

以下程序演示了问题:它首先降低了方法的可访问性,然后尝试在不重新编译的情况下在另一个程序集中访问该方法。 首先编译 changeaccess.cpp。 它是将更改的引用程序集。 然后编译 referencing.cpp。 它应成功编译。 接下来,降低调用方法的可访问性。 使用编译器选项 /DCHANGE_ACCESS 重新编译 changeaccess.cpp。 它使 access_me 方法处于 protected 状态,而不是 public,因此不能从 Test 或其导数以外调用。 如果不重新编译 referencing.exe,请重新运行应用程序。 将出现 MethodAccessException

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

};

下面是引用程序集的源:

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

另请参阅

使用 C++/CLI (Visual C++) 进行 .NET 编程
与其他 .NET 语言的互操作性 (C++/CLI)
托管类型 (C++/CLI)
#using 指令