< C++ .Net Early Stages 12 | Main | C++ .Net Early Stages 14 >


 

 

Early Stages of the (Visual) C++ .Net 13

(Managed Extensions for C++)

 

 

In this module we start to see some of the C++ .Net implementation using VC++ 2003. Notice the new deprecated keywords used. The following are the topics for this module. The code snippets used in this module are for Visual C++ .Net 2003 and if compiled using Visual C++ .Net 2005 you need to use the /clr:oldSyntax option).

  1. Nested Types

  2. Using Types from Other Languages

  3. Casts and Conversions

  4. Cast Operators

 

Nested Types

 

You can define a type within an existing type. The new type is a nested type. Nested types do not have a visibility; they take the visibility of the enclosing type, which means that a nested type cannot be more accessible than the type that it is defined in. Because a nested type is a member of its enclosing type, you can apply a member access specifier to the nested type. Thus, you can make the nested type less accessible than its enclosing type.

All __gc and __value types can have __gc types, __value types, enums, and delegates as nested types. Managed interfaces cannot be a nested type, neither in a __gc or __value type, nor in another managed interface; managed interfaces cannot have a nested __gc or __value type, other than an enum. The name of a nested type is scoped by the enclosing type, not the assembly, so two enclosing types can have nested types with the same name.

public __gc class Outer

{

  public:

   __gc class Inner

   {

      public:

         __value enum VALUES {One, Two};

   };

  protected:

   __gc class Inner2

   {   };

};

 In this example, there is a public class named Outer, which is visible to code outside of this assembly. Within this class is another class named Inner, which is also visible outside of the assembly, but because it is a public member of the class, it is also accessible outside of the assembly. The Outer class also has a member named Inner2, which is declared as protected protected, so it too is visible outside of the assembly (it gets this visibility from its enclosing type’s visibility), but Inner2 is accessible only by code derived from the enclosing type (within the same assembly or in another assembly). In C++, the name of the nested type is scoped by the C++ resolution operator, ::. Thus, VALUES is named Outer::Inner::VALUES, and code outside of the enclosing type must use this qualified name. However, metadata uses a different naming scheme. The code:

 

Type* t = __typeof(Outer::Inner::VALUES);

Console::WriteLine(t->ToString());

will print

Outer+Inner+VALUES

at the command line. If you are likely to access members through reflection, you should be aware that metadata uses + as the scoping operator. Furthermore, MSIL uses a forward slash as the scoping operator for nested types, so the MSIL for the previous code looks like this:

ldtoken Outer/Inner/VALUES

call class [mscorlib]System.Type[mscorlib]System.Type::GetTypeFromHandle(

      valuetype [mscorlib]System.RuntimeTypeHandle)

callvirt instance string [mscorlib]System.Type::ToString()

call void [mscorlib]System.Console::WriteLine(string)

The accessibility of a nested type is given by the accessibility of the member within the enclosing type. Thus, VALUES has the metadata nested public applied to it because it is a nested member of Inner and it is a public member of this type. If you test the metadata of a nested type, you should test the appropriate IsNested metadata.

Type* t = __typeof(Outer::Inner::VALUES);

Console::WriteLine(S"IsPublic {0}", __box(t->IsPublic));

Console::WriteLine(S"IsNotPublic {0}", __box(t->IsNotPublic));

Console::WriteLine(S"IsNestedPublic {0}", __box(t->IsNestedPublic));

This code gives true for IsNestedPublic but false for the other two tests. Nested types can derive from the outer type, but in C++, the nested type must be defined outside of the class.

public __gc class OuterClass

{

public:

   __gc class InnerClass; // Forward reference

};

 

public __gc class OuterClass::InnerClass : public OuterClass

{     };

A nested type has access to all the members of the enclosing type. Thus, a nested type has access to private and protected static members, and if a nested type has an instance of the enclosing type, it has access to instance members too. However, the converse is not true: the enclosing type does not get access to protected and private members of nested types. A __gc type can define a __nogc type as a nested type, but although you can access the nested type in your C++ code, it will not be visible nor accessible to any other code.

 

Using Types from Other Languages

 

When you import metadata from another assembly, you have no indication about the language that was used to write the code. This ability to use code written in other languages is one of the significant features of .NET. However, different languages have different keywords, so a C# developer can export a type that has the same name as a C++ keyword. C++ has a mechanism to use such types, to identify that the compiler treats them as types and not as the C++ keyword. For example, we could define this class in C#:

// C#

public class friend

{

   private string name;

   public friend(string n) {name = n;}

   // Other members

}

The problem is that friend is a C++ keyword, so the following code will not compile:

friend* f = new friend;

The C++ compiler will interpret the type name as the C++ friend keyword and will issue errors indicating that the use is incorrect. To get around this problem, C++ has the __identifier keyword, shown here:

__identifier(friend)* f = new __identifier(friend);

Now the C++ compiler treats these instances of the word friend as being the name of a type. The code will compile. You can use __identifier anywhere a symbol is used, so you can use it on type names and members of types. You can also use it on type declarations within your own code.

 

Casts and Conversions

 

Much of the code you will write will be through typed pointers. However, some of the time you will get a generic pointer or a pointer to a base class. If this is the case, you will need to convert the pointer to a more specific pointer so that you can access the functionality that you require. .NET is strongly typed: object references are to a specific object of a specific type. You will not be able to call an object through a reference other than one in its class hierarchy.

 

Cast Operators

 

The most generic cast is a C-style cast. The C++ compiler will warn you whenever you use a C-style cast on object references. The reason for the warning is that no compile-time check is performed. If you attempt to cast to a pointer of another object type, at run time .NET will throw an exception. C-style casts are so simple and tempting to use, but .NET is the ideal reason to banish them in favor of C++ casts. Because C-style casts are so dangerous, the compiler will warn you (warning message C4303) when you use them on managed pointers. You should take this opportunity to use an appropriate C++ cast. There are two types of casts that you’ll perform. The first case is when you absolutely, utterly, and definitely know the type of the object and that the cast will succeed, in which case no run-time check is required. The other case is when you are not so sure, in which case you are happy for .NET to perform a type check for you.

The C++ dynamic_cast<> operator is used to convert pointers of related types. The runtime will check the type of the object to see whether the pointer that you request is to the object type or to a base class. If the cast fails, dynamic_cast<> will return 0. If you use dynamic_cast<>, you should ensure that you check the return value. Managed C++ has a new operator named __try_cast<>. This operator behaves like dynamic_cast<>, except if the cast fails, an exception of type InvalidCastException is thrown. In terms of implementation, the only difference between dynamic_cast<> and __try_cast<> is that the former uses the IL isinst, whereas the latter uses castclass.

If you are sure that you know the runtime type of an object, you can use static_cast<>. This operator will convert between pointer types with no check at run time. (In the intermediate language, the object reference being cast is merely copied into the reference it is being cast to.) If the object being cast is a __value type, you can use static_cast<> only to convert it to a System::Void* pointer. Finally, C++ provides reinterpret_cast<> to convert between unrelated pointers. In general, this operator should not be used for object references. The C++ compiler will issue warning C4669 if you use this cast on a __gc pointer, which you should use as an indication that you should change the cast operator. This operator is useful for casts on pinned pointers, where your intention is usually to get an unmanaged pointer access to managed memory. The const modifier is a C++ism, so although you can use it on your variables and method parameters, it has no effect on the .NET code generated. You use const to get the compiler to perform checks on how you use pointers.

void UseDataObject(const Data* pdata)

{

   pdata->x = 88;  // Error: object cannot be modified

}

In this code, we have decided that the method cannot modify the object passed to it. Making the object constant (prefixing the pointer with const) tells the compiler to check to see whether we make any attempt to modify the object. This code will not compile, but it is perfectly fine in terms of .NET. (If the parameter was declared Data* const pdata, the pointer, rather than the object, is constant.) When C++ sees const, it applies Microsoft::VisualC::IsConstModifier to the parameter, although interestingly, it is applied to the parameter in the same way whether const is used to make the pointer constant or to make the object constant. C++ allows you to cast away the effect of const using the const_cast<> operator.

 

 

 

Part 1 | Part 2 | Part 3 | Part 4

 


 

< C++ .Net Early Stages 12 | Main | C++ .Net Early Stages 14 >