< C++ .Net Early Stages 5 | Main | C++ .Net Early Stages 7 >


 

 

Early Stages of the C++ .Net 6

(Managed Extensions for C++)

 

 

Note: 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). Here you will learn new keywords used in the C++ .Net though in VC++ 2005 these keywords already deprecated. However we just want to see how the whole thing changed to the managed C++ .Net from native, unmanaged C++ used in COM, MFC etc.

  1. Delegates and Events

  2. Attributes

 

Delegates and Events

 

Delegates are simply type-safe function pointers. When defined, a delegate can be initialized with the address of a static or an instance method, but the method must have the same signature as the delegate or a run-time exception will be thrown. This restriction protects you from some of the nastier bugs in Win32 that occur when the wrong method is imported from a DLL using ::GetProc­Address and cast to an inappropriate function pointer. Delegates are declared in C++ using the __delegate keyword on the signature of the method that can be called through the delegate. The declaration can be within a class or outside of a class.

// delegates.cpp

public __delegate int CallMethod(String*);

 

public __gc class ActiveClass

{

  public:

     __delegate void ActionStarted(String*);

};

The compiler generates a class from the declaration, so if the delegate is declared within a class, the delegate class will be nested within that class. For the preceding code, the compiler will generate a class named CallMethod and another class named ActiveClass::ActionStarted. The IL for CallMethod looks like this:

.class public auto ansi sealed CallMethod extends [mscorlib]System.MulticastDelegate

{

   .method public specialname rtspecialname instance void .ctor(object __unnamed000, native int __unnamed001) runtime managed forwardref {}

   .method public newslot virtual instance int32 Invoke(string __unnamed000) runtime managed forwardref {}

   .method public newslot virtual instance class [mscorlib]System.IAsyncResult BeginInvoke(string __unnamed000, class [mscorlib]System.AsyncCallback __unnamed001, object __unnamed002) runtime managed forwardref {}

   .method public newslot virtual instance int32 EndInvoke(class [mscorlib]System.IAsyncResult __unnamed000) runtime managed forwardref {}

}

The class derives from MulticastDelegate and that the methods are empty, but they are marked as forwardref and runtime. The modifiers forwardref and runtime mean that the methods are not declared in this class because they are implemented by the runtime. The base class MulticastDelegate holds a linked list of delegates. Delegates are combined into this list with a static method named Combine that takes two delegates as parameters. This method creates a new delegate whose linked list is the combination of the lists from the two delegates. In a similar way, there is a static method named Remove that will create a new delegate whose linked list is the difference between the lists of the two delegates passed as its parameters. A delegate is created by passing a function pointer to the constructor of the compiler-generated delegate class, so given this class:

// delegates.cpp

__gc class Caller

{

public:

   int CallMe (String* s)

   { Console::WriteLine(s); return s->Length; }

   static int CallMeToo(String* s)

   { Console::WriteLine(S"static: {0}", s); return s->Length; }

};

We can create a delegate that calls either the instance method or the static method.

Caller* c = new Caller;

CallMethod* m1 = new CallMethod(c, &Caller::CallMe);

int i = m1(S"Hello");

CallMethod* m2 = new CallMethod(0, &Caller::CallMeToo);

int j = m2(S"Hello");

The variable m1 is created using an instance of Caller, which is passed as the first parameter to the delegate class constructor. The second parameter indicates the method to call: the instance method CallMe. The delegate is invoked by treating it as if it is a function pointer, so we call m1, passing it a string parame­ter, and under the covers, the C++ compiler calls CallMethod::Invoke, which will go through the linked list the delegate class holds and call Invoke on each one. In this case, there is only one delegate in the list, and thus Caller::CallMe is called. The variable m2 is created from a static method, so the first parameter is zero because there is no object to call. The delegate is invoked in the same way. This code might look a little redundant, but be aware that because CallMethod is a class, an object reference could be passed as a method parameter, even to another process and of course, the delegates can be combined, as shown in the following code:

CallMethod* m3;

m3 = dynamic_cast<CallMethod*>(Delegate::Combine(m1, m2));

int k = m3(S"Hello, again");

Delegate is the base class of MulticastDelegate. When the combined delegate is invoked, the m1 delegate is called first, and then the m2 delegate is called. So what value is returned in k? Well, it is the value returned from m2. The rule is that when a multicast delegate is invoked, the result from the last delegate added to the delegate is returned. In this code, we have called Delegate::Combine directly. Because this method returns a Delegate*, we have to cast the pointer to my typed delegate. The C++ compiler allows you to use the += and -= operators on delegates, which it will translate as calls to Combine and Remove. The advantage of these operators is that the cast is not required in your code.

CallMethod* m3;

m3 += m1;

m3 += m2;

int k = m3(S"Hello, again");

If you want to get the return value from all the delegates, you can call the inherited member GetInvocationList, which will return an array of delegates. You can then call each member in the array in any order that you want.

Delegate* d[] = m3->GetInvocationList();

IEnumerator* e = d->GetEnumerator();

while (e->MoveNext())

{

   CallMethod* m = dynamic_cast<CallMethod*>(e->Current);

   int i = m(S"another call");

}

Another interesting thing that you can do with delegates is use them as fields to another class, as shown here:

// events.cpp

public __gc class Worker

{

public:

   CallMethod* m;

   void AddMethod(CallMethod* m1)

   {

      m += m1;

   }

   void DoSomething()

   {

      // Do some work here.

      m(S"something was done");

   }

};

The methods in the Worker class can be called as shown in the following code:

Worker* w = new Worker;

w->AddMethod(m1);

w->AddMethod(m2);

w->DoSomething();

This code represents a notification mechanism. The Worker class could perform some work in DoSomething and then inform the delegates m1 and m2 when that work has completed. Events are a formalization of this notification mechanism. They are declared in C++ with the __event keyword.

public __gc class Worker

{

public:

   __event CallMethod* m;

   void DoSomething()

   {

      // Do some work here.

      m(S"something was done");

   }

};

When you use the __event keyword, the compiler adds a private member for the delegate, three methods to the class, and the .event metadata. The methods have the name of the event prefixed with add_, remove_, and raise_. These methods are used to combine a delegate with the delegate field, to remove a delegate from that field, and to invoke the delegate. For this event, the compiler will add this metadata:

.event specialname CallMethod m

{

  .addon instance void Worker::add_m(class CallMethod)

  .removeon instance void Worker::remove_m(class CallMethod)

  .fire instance int32 Worker::raise_m(string)

}

In general, you should make the event public, in which case the compiler will make the delegate field private so that access to the field will be only through the methods added by the compiler. The add_ and remove_ methods will be made public, so external code can add delegates, but the raise_ method will be protected. This scheme fits in with the idea that events are used for notifications because it means that the notification can be generated only by the class containing the event (and derived classes). The C++ compiler allows you to use the += and -= operators on the event to add and remove events. The compiler translates calls to these operators to calls to the add_ and remove_ methods. Note that although the += operator is made on what appears to be a delegate field (admittedly one marked with __event), the code generated does not call Delegate::Combine on that field. Instead, it calls the add_ method on the class that contains the field.

Worker* w = new Worker;

w->m += m1;

w->m += m2;

w->DoSomething();

 

 

 

 

If you want to perform some special processing in the add_, remove_, and raise_ methods, you can provide implementations and inform the compiler. To do this task, you should not declare __event on a delegate field; instead, you should decorate the methods with the __event keyword. So that the compiler knows which methods are used to add and remove the delegates and which method is used to raise the event, you should use the convention of prefixing the event name with add_, remove_, and raise_.

public __gc class Worker

{

   CallMethod* m1;

   public:

       __event void add_m(CallMethod* d)    { m1 += d; }

       __event void remove_m(CallMethod* d) { m1 -= d; }

       __event int raise_m(String* s)       { return m1(s); }

   void DoSomething()

   {

       // Do some work here.

       m(S"something was done");

   }

};

We have added a private delegate field to hold the delegates that are added to the event. It is important to give this a name different from the event (that is, the name after the raise_ prefix). The problem with the default implementation of events provided by the compiler is that for each event, the class will have a delegate field. An instance will have storage for each delegate even if no clients have provided event handlers. The Control class stores all delegates in an instance of EventHandlerList, which allocates only sufficient memory for the delegates added to the object that can generate events. Events can be declared as static, and the compiler-generated methods and the delegate field will also be static, in which case the event is treated as a notification mechanism for the type that is, when the event is raised, all delegates added through all instances of the type are informed. Furthermore, events can also be virtual. If you write the individual event methods, you must ensure that you are consistent with the static and virtual keywords.

 

Attributes

 

Metadata is vital to .NET code. All types are described in metadata within the assembly where they are defined. Code that uses those types has metadata that describes exactly which code they will call. The runtime uses this information when executing your code, and if the exact method (in the exact assembly) you ask to be executed cannot be found, the runtime will throw an exception. Compilers generate metadata when they compile your code. They also read the metadata in the assemblies that you use (which you specify in C++ with #using). In some cases, you might decide that you want the compiler to apply a particular .NET metadata to your type. To do this task, you use an attribute, such as the [Serializable] attribute introduced earlier. If you add this attribute to a type, the compiler will add the serializable metadata to the type, which you can view in ILDASM.

[Serializable]

__gc struct Square

{

   int x;

   int y;

   int w;

   int h;

};

For the purpose of this discussion, we will make this type a __gc type, which means that objects are passed through object references. A type this size would normally be a __value type, in which case the data is always passed by value. The [Serializable] attribute is a pseudo custom attribute because strictly speaking it is not a custom attribute, a custom attribute is used to extend metadata, whereas [Serializable] applies existing metadata that can be added to the class.

The syntax that you see here is the standard syntax for custom attributes. The astute reader will notice that pseudo custom attributes are not the only way to add metadata to a type. The __abstract keyword adds the abstract metadata, __sealed adds the sealed metadata, and __value adds the value metadata to a type. Furthermore, __event and __property are used to identify code that will be used to create the .event and .property metadata. Pseudo custom attributes allow other languages to add metadata to their types. Custom attributes are implemented by __gc classes derived from System::Attribute. These classes can have constructors, fields, and properties, and you can pass data to a constructor to initialize the attribute. If an attribute class has a constructor with parameters, the parameters are passed through positional, unnamed parameters.

[CLSCompliant(true)] __gc class Test{};

This custom attribute indicates that the class is compliant with the Common Language Specification (CLS), meaning that its public members use types that can be used by any .NET language. The value of true is passed to the constructor of the class CLSCompliantAttribute. The convention is that the name of a custom attribute class has the suffix Attribute, but the C++ compiler allows you to call it either with or without the suffix. There are a few cases in which you must use the suffix. The most obvious is [GuidAttribute] in System::Runtime::InteropServices, which is used to apply a COM GUID to a type. You can also use GUIDs in your code, and for this you can use System::Guid. To avoid the compiler mistaking one for the other, you should use the complete attribute name. Named, optional attribute parameters are implemented through properties or public fields, for example:

[ObjectPooling(true, CreationTimeout=10000)]

__gc class MyComp : public ServicedComponent{};

The [ObjectPooling] attribute in System::EnterpriseServices is used to indicate the properties of object pooling that should be used for this class. The first parameter is not optional, and it indicates that object pooling is enabled. CreationTimeout is a property and is used to indicate the maximum amount of time that a client should wait for an object until an exception is thrown. There is no constructor that takes the timeout as a value, so if you want to specify this value, you have to do it through the property. If you omit this parameter, a default value is used. The syntax looks a little odd. It looks like you are calling a constructor and naming one of the parameters. In fact, the information that you give in a custom attribute is stored in metadata as a list of instructions. So in effect, this attribute says: “create for me an instance of ObjectPoolingAttribute by calling the constructor that takes a Boolean and pass true for this parameter, and then give the CreationTimeout property a value of 10000”. If you want to apply multiple attributes to an item, you can either use a pair of square brackets for each attribute or use one pair of square brackets and give the attributes in a comma-separated list. Attributes can be placed on any item that can generate metadata, before any other modifier that can be applied to the item. However, there are some cases when the compiler will not know where you intend the attribute to be applied. Here’s one example:

[Test] int MyMethod();

When the compiler sees this code, it will not know whether the attribute should be applied to the return value or to the method. (The compiler does not allow the attribute to be applied between the return type and the method name). In this case, the attribute will be applied to the method. If you want to apply the attribute to the return value, you’ll need to specify a target specifier.

[returnvalue: Test] int MyMethod();

The following Table lists the target specifiers that you can use. Some attributes appear redundant, for example:

[delegate: Test] __delegate void delOne();

[method: Test] __delegate void delTwo();

In this example, the [Test] attribute is applied to both the delOne and delTwo delegates. However, in your C++ code it is more readable to use the delegate target because the __delegate directive actually declares a delegate class (which the compiler will generate) and not a method.

 

 

 

 

Target Specifier

Description

assembly

For attributes applied to anonymous blocks that will generate assembly metadata.

class

For attributes applied to a C++ class.

constructor

The attribute is applied to a constructor.

delegate

The attribute is applied to the delegate (as opposed to the return value); this is the same as using the method target.

enum

The attribute is applied to the enum.

event

The attribute is applied to the .event metadata. If you want the attribute to be applied to the add_, raise_, or remove_ method, use the method target.

field

The attribute is applied to the field.

interface

The attribute is applied to the managed interface.

method

The attribute is applied to a global or member method; the add_, raise_, or remove_ methods of an event or the get_ or set_ methods of a property.

module

For attributes applied to anonymous blocks that will generate module metadata.

parameter

The attribute is applied to a method parameter.

property

The attribute is applied to the property (as opposed to the get_ or set_ methods, in which case you should use the method target); this is the same as using the returnvalue target.

returnvalue

The attribute is applied to the return value of a method or to a property.

struct

The attribute is applied to a struct.

 

Target specifiers used to specify the item an attribute should be applied to

 

Another situation in which the target specifier is vital is when you want to apply an attribute to an assembly or to a module. In .NET, the unit of deployment and versioning is the assembly. An assembly can be made up of one or more code and resource files. Code and embedded resources can be in files called modules. To specify that an attribute should be applied to the assembly rather than to a module, you should use the assembly or module target specifier, as shown in this example:

[assembly: ApplicationName("My stupid COM+ Application")];

This code gives the name of the COM+ application that the assembly is used for. When the assembly is registered with RegAsm (or a ServicedComponent type defined in the assembly is used for the first time), this string will be used as the name of the COM+ application. It is interesting to look at the metadata generated for a custom attribute. Let’s go back to the ObjectPooling example given earlier. The metadata for the class looks like this:

.class public auto ansi MyComp extends [System.EnterpriseServices]  System.EnterpriseServices.ServicedComponent

{

  .custom instance void [System.EnterpriseServices]

    System.EnterpriseServices.ObjectPoolingAttribute::.ctor(bool)

       = ( 01 00 01 01 00 54 08 0F  // .....T..

           43 72 65 61 74 69 6F 6E  // Creation

           54 69 6D 65 6F 75 74 10  // Timeout.

           27 00 00 )               // '..

}

The custom attribute is applied through the .custom directive. This gives the name of the constructor that is used to create the attribute followed by binary data that gives the information about the data that will be passed to the constructor. This data starts with 0x0001 (all attribute data appear to start with this value) followed by one byte (0x01), which is the serialized value of true. Next it gives the number of properties or fields that should be set (0x0001) and after this the properties and fields are listed in the order that they were declared. The binary data has an identifier to indicate whether the item is a property (0x54) or a field (0x53) and then the name of the property. This name is made up of a single byte string prefixed with the number of characters in the string. The property name is then followed by the value of the property (0x00002710).

The name of the property is stored as single-byte characters, but if you pass a string for the value of a parameter of an attribute (either positional parameters or named parameters), the data is stored as a serialized managed string that is, as Unicode characters. It makes no difference whether you provide the string value as ANSI (""), as Unicode (L"") or through managed string syntax (S"") in your code because the compiler will always store it as a serialized managed string. As mentioned before, the attribute is stored in metadata as a list of instructions used to create the attribute, but the question is: when is this attribute object created? A custom attribute is not necessarily created when an object instance is created. Custom attributes are created only when code attempts to access the attributes on an item. This action is performed by calling the static method Attribute::GetCustomAttribute to get a specified attribute on an assembly, module, class member, or method parameter; or by calling the instance method MemberInfo::GetCustomAttributes to get a list of custom attributes on any item. System::Type derives from MemberInfo, so you are likely to get the custom attributes for an object through the Type object.

When one of these methods is called, the runtime will create the attribute object using the instructions in the .custom directive. If you call GetType()->GetCustomAttributes twice on one object, two attribute objects will be created. Note also that GetCustomAttributes returns custom attributes; pseudo custom attributes are not returned by this method because they represent standard metadata. Pseudo custom attributes such as [Serializable] can be obtained through reflection (in this case, Type::IsSerializable). MemberInfo::GetCustomAttributes returns an array of Object pointers. There are several ways that you can determine the type of each member. First, you could perform a dynamic_cast<> for the attribute type that you are interested in, and if the cast succeeds, the attribute has been applied to the item. Note that the type that you will cast to is the full name of the attribute class; you can use the abbreviated name (without the Attribute suffix) in square brackets only when you are applying an attribute. Second, you can test the type object against the type object obtained from the class; the type object returned from GetType and __typeof is a static object for each type. Finally, you could compare the name of the type object returned from GetType()->ToString, but remember that if the attribute is in a namespace, the scope resolution operator will be the dot, not ::, thus:

Test* t = new Test;

Object* attrs[] = t->GetType()->GetCustomAttributes(false);

IEnumerator* e = attrs->GetEnumerator();

while (e->MoveNext())

{

   if (e->Current->GetType()->ToString()->Equals(S"System.ObsoleteAttribute")

   {

      Console::WriteLine(S"this class is obsolete");

   }

}

Here’s another assembly attribute:

[assembly: AssemblyVersion("1.0.*")];

 

 

 

 

This attribute is interesting because the information is a request to the compiler and the information in the attribute is not placed in the assembly as a custom attribute. Instead, the compiler interprets the information and uses it to create information that it puts in the assembly’s .ver metadata. In this case, the compiler creates a version of the form 1.0.xx.yy, where xx is the build number that the compiler will create from the number of days since the first day of the year 2000, and yy is the revision number that the compiler will create from the number of seconds since midnight modulo 2.

Creating your own attribute classes is straightforward: you derive from System::Attribute and apply the [AttributeUsage] attribute to indicate where the attribute can be used. In addition, [AttributeUsage] also allows you to indicate whether the attribute can be used more than once on the same item and whether the attribute is inherited when it is applied to a __gc type and that type is the base for another type. Any mandatory parameters should be constructor parameters, whereas optional parameters can be passed through fields or properties. The attribute classes in the .NET Framework class library often accommodate optional parameters through overloaded constructors, but we think it is clearer to use constructors only for mandatory parameters. Whatever you decide on this issue, you should ensure that all constructors initialize fields and properties to a suitable default value. You are restricted to the types that you can use for constructor parameters, fields, and properties on attribute classes. The acceptable types are listed below the following code. Because integers are allowed, you can also use enumerated values. You are also allowed to use arrays of the types given in this list, in which case when you apply the attribute, you should use an initializer list.

[AttributeUsage(AttributeTargets::All)]

__gc class UsersAttribute : public Attribute

{

    public:

       String* names[];

};

 

[Users(names = { S"Paul", S"John", S"George" } )]

__gc class ThisClass

{

    public:

};

Here are the types that can be used for constructor parameters, fields, and properties of attribute classes:

  1. bool

  2. char

  3. unsigned char

  4. wchar_t

  5. short

  6. unsigned short

  7. int

  8. unsigned int

  9. __int64

  10. float

  11. double

  12. Object *

  13. String *

  14. char *

  15. wchar_t *

 

 

Part 1 | Part 2 | Part 3 | Part 4 | Part 5 | Part 6 | Part 7 | Part 8

 

 


< C++ .Net Early Stages 5 | Main | C++ .Net Early Stages 7 >