< C++ .Net Early Stages 11 | Main | C++ .Net Early Stages 13 >


 

 

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

(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. Inheritance

  2. Exporting and Importing Types

  3. Visibility

  4. Member Access

 

Inheritance

 

.NET supports only single implementation inheritance, which means that any __gc classes that you define can have only one __gc base class (although it can implement more than one __gc interface). All class derivation in .NET is public; there is no concept of private or protected inheritance as there is in unmanaged C++. However, you still have to explicitly specify that you are deriving from a base class or implementing an interface through public inheritance.

// bases.cpp

__gc __interface IInterface

{ };

__gc class BaseWithItf

   : public IInterface  // Interfaces only supported through public inheritance

{ };

__gc class DerivedWithItf

   : public BaseWithItf // Base classes only supported through public inheritance

{ };

Class hierarchies are useful because they allow you to put common code in base classes. These base classes can be concrete or they can be abstract through the __abstract keyword or through C++ pure virtual syntax, which still results in an abstract .NET class. If the base class has no constructors, the compiler will generate a default constructor (one without parameters) that calls System::Object. The compiler will add code in the derived class constructors to call the default base class constructor. (The default base class constructor will be called first, before the call to System::Object). You can choose to call a non default constructor in the same way that you do in unmanaged C++.

// bases.cpp

__gc class Base

{

   int m_i;

   public:

       Base()      : m_i(0){}

       Base(int i) : m_i(i) {}

};

 

__gc class Derived : public Base

{

    public:

       Derived(int i) : Base(i){}

};

If a derived class overrides a base class method, the derived class can call the base class version of the method as long as the derived class has a suitable access. To call the base class version of the method, you call the method qualified with the base class name. Visual C++ introduces a new keyword for calling a method on the immediate base class: __super, shown here:

__gc class Base

{

    public:

       void f() {}

};

 

__gc class Derived : public Base

{

    public:

       f()

       {

          __super::f();

          // Same as calling Base::f();

       }

};

This keyword is more useful for unmanaged Active Template Library (ATL) attribute-injected code, and the keyword will allow you to call only the immediate base class, but it is a syntax that you might see in managed code. The C++ rules of converting derived class pointers to base class pointers apply to __gc classes: the conversion is implicit. However, if you have a base class pointer, you must cast it to get a derived class pointer. Of course, managed arrays are .NET objects. As mentioned earlier, a pointer to an array is typed according to the items in the array. A pointer to an array of a derived class can be cast to an array pointer of the base type. However, assigning a member of the array uses the runtime type. For example:

void CreateControls(Control* ctrls[])

{

   ctrls[0] = new Button;

   ctrls[1] = new TextBox;

   ctrls[2] = new Control;

}

This code uses the System::Windows::Forms classes. The Button and TextBox class both derive from Control, so the code in CreateControls looks fine because a Button* or a TextBox* can be implicitly converted to a Control*. The compiler is happy. However, a Button*[] can also be converted to a Control*[], so calling code could do this:

// calling code

Button* buttons[] = new Button*[3];

CreateControls(buttons);

The compiler is still happy: this code creates an array of Button* and passes this array to CreateControls where there is an implicit conversion to a Control*[]. The runtime type of each element of the array is Button*, so in CreateControls, a run-time error will occur when attempting to put a TextBox* and even a Control* into the array. .NET allows you to derive from a type in another assembly, and that assembly can be written in any .NET language. Indeed, other languages can derive from the non __sealed classes you write in C++. Of course, for other languages to be able to do this derivation, the type has to be accessible outside of the assembly.

 

Exporting and Importing Types

 

You can create executable assemblies and library assemblies also known as EXEs and DLLs. A DLL is useful only if the types are visible to code outside of the assembly. There are three categories of types in .NET:

 

  1. public types, which are visible outside of an assembly,

  2. private types, which are accessible only within the assembly, and

  3. Nested types (types nested within other types), in which the visibility is controlled by its enclosing type.

 

Visibility

 

In C++, you specify that a type is a public or private type by applying either the public keyword or the private keyword, as shown here:

public __gc class A{};

private __gc class B{};

__gc class C{};

The default visibility is private, so in the previous code, only the type A is visible outside of the assembly. When we say visible, we do not mean that you will never be able to “see” a private type. The visibility of a type is a .NET attribute accessible through reflection; for a public non-nested type, Type::IsPublic is true, whereas for a private non-nested type, Type::IsNotPublic is true. Thus, if you use ILDASM to look at an assembly, you’ll be able to see any private types in the assembly. The C++ linker uses .NET visibility when determining which code to put in the final assembly. All public types will be linked, but only private types used by a public type (directly or indirectly) will be linked. So, if you view your assembly with ILDASM and see that a private type is not in the assembly, it is because it is not used in your code.

Private types will not be accessible outside of the assembly when called using metadata. You use #using to import metadata from an assembly, but only public types will be imported. Note that we said only public types from the assembly can be imported using the #using statement. Modules (usually housed in .netmodule files) are part of an assembly, so if you specify a module with #using, all types in the module are available to the code in the file in which the #using statement resides. Similarly, if you specify an .obj file with #using, you’ll get access to all types in the file, which will be linked into the final assembly. Because private types are available through reflection, if you are willing to write the reflection code, you will have access to it. For example, here’s the code for a library DLL:

// compile with cl /clr /LD lib.cpp

#using <mscorlib.dll>

 

private __gc class B

{

public:

   void f()

   { System::Console::WriteLine(S"called B"); }

};

 

public __gc class A

{

   // Reference an instance of B so that the optimizer does

   // not remove the type from the DLL.

   A(){B* b = new B;}

};

On the surface, this code implies that only type A is accessible outside of the assembly. However, the following code accesses the type through reflection:

// Compile with cl /clr libuser.cpp.

#using <mscorlib.dll>

using namespace System;

using namespace System::Reflection;

 

void main()

{

   AppDomain* ad = AppDomain::CurrentDomain;

   Object* obj;

   obj = ad->CreateInstanceAndUnwrap(S"lib", S"B");

   Type* t = obj->GetType();

   MethodInfo* m;

   BindingFlags bf = (BindingFlags)(BindingFlags::Instance │ BindingFlags::Public);

   m = t->GetMethod(S"f", bf);

   m->Invoke(obj, 0);

}

The CreateInstanceAndUnwrap method on the application domain will load the specified type in the specified assembly. The name used is the display name of the assembly. Because in this example we have not given the library assembly a version or a culture and we have not signed the assembly, the display name is simply the name of the DLL without the extension. CreateInstanceAndUnwrap accesses the type whether or not it is public, and once an instance has been created, it is relatively easy to access the members of the class. Note that the BindingFlags used in GetMethod refers to the member, not the type.

What you cannot do is call the type through a typed pointer. You cannot use a B* pointer because the metadata for this type is not available. (Only metadata from public types can be imported from a library assembly through #using). We are not suggesting that you regularly call code in this manner. The point we am trying to make is that even though the type is private, it is still accessible if you are willing to write the code.

 

Member Access

 

Visibility of a type is only part of the information needed to determine if you can access members of the type. .NET defines six (The ECMA spec actually gives seven levels, but the level that we have left out of the following Table, privatescope, is not put on class members by the C++ compiler)  levels of access on members of a class. All levels are available to C++ code. Member access is specified using the C++ public, protected, and private keywords. Clearly there are not enough keywords for the number of access levels, so C++ uses a combination of these three keywords. Table 1 shows these combinations.

 

Access Level

Metadata Attribute

Assembly Accessibility

External Accessibility

public public

public

public

public

public protected

famorassem

public

protected

public private

assembly

public

private

protected protected

family

protected

protected

protected private

famandassem

protected

private

private private

private

private

private

 

Table 1: Member Access Specifiers in C++

 

If the access level has a repeated keyword, you can omit the second word. Thus, public can be used instead of public public. As you can see, there are only six of the possible nine combinations. In fact, the order that you give the keywords in the access level is unimportant: the most restrictive accessibility always determines the visibility to code outside the assembly. Consider this code:

public __gc class Base

{

    protected protected:

          void f(){}

    protected private:

          void g(){}

};

This class can be used as a base class for a class within this assembly or within another assembly. This ability is controlled by the public keyword on the type. If we had used the private keyword on the type, only classes within this assembly would be able to derive from this class. The member f is marked as protected protected (or simply, protected), which means that code within the same assembly as this class can access this member as long as that code is part of a class that derives from this class. The protected protected keywords also mean that if a class in another assembly derives from this class, that class can also access this member. This type of access is why the metadata for this access level is named family: any code that is part of the family can access this member. The member g is marked as protected private, which means that only code in a class derived from this class that is in the same assembly as this class can access this member. This type of access is why this access level is named famandassem (in the same family and in the same assembly).

C++ has keywords that bend the C++ rules of member accessibility. These keywords, such as the friend keyword, are not allowed in managed C++. Friends are not allowed, and you cannot change the accessibility with the using directive. However, you can change accessibility by deriving from a class; a member of a derived class can have a wider accessibility than the member it overrides. Finally, access levels are applicable only to code used through normal binding. If you decide to call code through reflection, you can access nonpublic members.

 

 

 

Part 1 | Part 2 | Part 3 | Part 4

 


 

< C++ .Net Early Stages 11 | Main | C++ .Net Early Stages 13 >