< C++ .Net Early Stages 6 | Main | C++ .Net Early Stages 8 >


 

 

Early Stages of the C++ .Net 7

(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. Managed Interfaces

 

Managed Interfaces

 

The new C++ compiler supports a new keyword named __interface. This keyword is supported for both managed and unmanaged code, so for a managed interface, you have to use __gc. Interfaces add metadata that describe behavior. They do not contain implementation. Indeed, all interfaces are implicitly abstract (you can use __abstract on an interface, but it is not necessary), and all members are implicitly pure virtual (again, you can use =0 on methods, but it is not necessary). Interfaces can contain methods, properties, and events, and all members are implicitly public. They cannot contain any implementation and cannot contain any storage, so they cannot contain fields. However, interfaces can contain __value enums because these do not represent storage. They are named values. Furthermore, interfaces cannot contain constructors, destructors, or operators, and because they are used to generate vtables, they cannot contain static members. Finally, __gc interfaces cannot derive from classes, but they can derive from other __gc interfaces. .NET supports multiple interface inheritance.

// interfaces.cpp

__gc __interface IPrint

{

   void Print();

   __property unsigned get_Pages();

   __property void set_Pages(unsigned);

   __event OnPrinted* printed;

};

This interface has a property named Pages, a method named Print, and an event named OnPrinted. The intention is that the interface will be implemented by a class that can print documents, and when the print job has completed, it informs interested parties by raising the OnPrinted event.

 

 

 

 

 

 

__delegate void OnPrinted(String*);

 

__gc class PrintedDoc : public IPrint

{

   unsigned pages;

   String* doc;

   public:

     PrintedDoc(String* s, unsigned p) : doc(s), pages(p){}

     void Print()

     {

      for (int i = 1; i <= pages; i++)

         Console::WriteLine(S"printing page {0}", __box(i));

      printed(doc);

     }

 

   __property unsigned get_Pages() { return pages; }

   __property void set_Pages(unsigned num) { pages = num; }

   __event virtual OnPrinted* printed;

};

Because the event is a member of the interface, it is declared as virtual. The PrintedDoc class needs to declare the event as virtual so that the generated methods are also virtual. Here is a class that uses an instance of PrintedDoc:

__gc class Book

{

   ArrayList* chapters;

public:

   Book()

   {

      chapters = new ArrayList;

      PrintedDoc* doc;

      doc = new PrintedDoc(S"Chapter1.doc", 90);

      doc->printed += new OnPrinted(0, Notify);

      chapters->Add(doc);

      doc = new PrintedDoc(S"Chapter2.doc", 67);

      doc->printed += new OnPrinted(0, Notify);

      chapters->Add(doc);

      doc = new PrintedDoc(S"Chapter3.doc", 87);

      doc->printed += new OnPrinted(0, Notify);

      chapters->Add(doc);

   }

   static void Notify(String* doc)

   {

      Console::WriteLine(S"{0} printed", doc);

   }

   void PrintAll()

   {

      IEnumerator* e = chapters->GetEnumerator();

      while(e->MoveNext())

      {

         IPrint* doc = dynamic_cast<IPrint*>(e->Current);

         if (doc != 0) doc->Print();

      }

   }

};

The PrintAll method iterates through the collection of documents and prints each one. The collection contains Object* pointers, so we cast to IPrint* because the only behavior that we want to get from the entry is its printable behavior. The collection could contain items other than PrintedDoc, but as long as those items implement IPrint, they will still be printed in PrintAll. This feature of interface programming, defining behavior is often overlooked. Types can derive from more than one interface, which means that a type can have more than one behavior, so you can choose which behavior you want from an object, a document could be both printable (rendered on paper) and persistent (saved to persistent storage, such as to disk).

__gc class Document : public IPrint, public IPersistent

{ /* other members */ };

An interface can derive from one or more interfaces. For example:

__gc __interface IOne

{

   void One();

};

__gc __interface ITwo

{

   void Two();

};

__gc __interface IThree : IOne, ITwo

{

   void Three();

};

A class that derives from IThree must implement the three methods One, Two, and Three, and it is treated as if it derives from IOne, ITwo, and IThree. A pointer to this class can be implicitly converted to any of the interfaces, and an IThree* pointer can be implicitly converted to an IOne* or ITwo*. If you have an IOne* pointer on a class that implements IThree, there is no implicit conversion from IOne* to ITwo*, but because the class implements both interfaces, you can explicitly cast between these interfaces with static_cast<>. One issue you might come across with multiple interfaces is if your class implements two interfaces that have a method with the same signature.

// interfaces2.cpp

__gc __interface IOarsman

{

   void Row();

};

__gc __interface IArgumentative

{

   void Row();

};

__gc class Rower : public IOarsman, public IArgumentative

{

public:

   void Row(){ Console::WriteLine(S"pull oar or shout?"); }

};

Both interfaces have a method named Row. The class implements both interfaces and hence implements Row. Because interfaces represent different behaviors, you do not necessarily intend to have a single implementation of Row. As it stands, we can create an instance of Rower and call Row through a pointer to Rower, to IOarsman, or to IArgumentative. The C++ compiler allows you to specify that a method implementation is for a specific interface.

__gc class Rower : public IOarsman, public IArgumentative

{

public:

   void IOarsman::Row(){ Console::WriteLine(S"pull oar"); }

   void IArgumentative::Row(){ Console::WriteLine(S"shout"); }

};

Now a user of the class can specify which version of Row should be called. To specify the version the user must obtain the appropriate interface pointer and call the following method:

IOarsman* objt = new Rower;

objt->Row();

Providing an explicit interface implementation such as this on a class has a side effect: you can call these methods only through interface pointers; you cannot call these methods through a pointer to the class. The compiler is rather coy about this:

Rower* r = new Rower; r->IOarsman::Row();

will result in an error telling me that the method call will fail at run time, rather than telling me that the code is just plain wrong. This side effect is useful, and classes in the .NET Framework use explicit interface implementation even when the class implements only one interface, to prevent the methods being called through a class pointer. For example, classes can be passed by value if they are serializable. We have already mentioned one way to indicate this: simply apply the [Serializable] attribute to the class, and all fields not marked as [NotSerialized] will be serialized. If you want to customize the way that serialization works, you can implement ISerializable on your class.

// serialize.cpp

__gc class MyFile : public IDisposable

{

   FileStream* f;

   public:

     MyFile(String* name)

     {

      f = new FileStream(name, FileMode::OpenOrCreate, FileAccess::ReadWrite);

     }

     FileStream* GetStream()

     { return f; }

     void Close()

     { if (f != 0) f->Close(); }

     void Dispose()

     { Close(); }

};

This class encapsulates a FileStream object, and the constructor takes a string with the name of the file and opens the file for read/write access. Client code can call GetStream to get access to this stream to read or write data through the GetStream method. We have indicated that the class implements IDisposable, which indicates to users of the class that it holds a resource that should be released as soon as possible by calling the Close method. We might decide that we want to serialize this object and store it somewhere (perhaps in a database). The intention being that when we deserialize the object, it will be initialized in such a way that we can call GetStream to get access to a stream on the original file and use the object as if it had never been serialized. The first approach is to add [Serializable] to the class and attempt to serialize the object with the following code:

// Create the file.

MyFile* f = new MyFile(S"data.txt");

// Write to the file using a StreamWriter.

StreamWriter* sw = new StreamWriter(f->GetStream());

sw->Write(S"this is data");

sw->Close();

 

// Now serialize the object.

SoapFormatter* sf = new SoapFormatter();

Stream* txt = File::Create(S"MyFile.soap");

sf->Serialize(txt, f);

txt->Close();

f->Close();

This code will fail. The reason is that the SoapFormatter will attempt to serialize the FileStream object, which is not serializable. Let’s take another approach and instead of serializing the fields in the class, let’s serialize the name of the file. To do this, the class needs to turn off standard serialization and implement custom serialization. This task is done by implementing ISerializable. The ISerializable interface is interesting: it has just one method named GetObjectData, which is called by the formatter to ask the object to serialize itself. This method is passed a SerializationInfo object that acts like a collection of name-value pairs. This object can be used to store enough information to identify the file that the FileStream object is based on. The interesting point is that if you implement this interface, you also have to implement a constructor that takes the same parameters as GetObjectData. This constructor is called by the formatter when an object is deserialized. Interfaces cannot declare constructors, so the only way that you know about this is to read the documentation. Here is the class with custom serialization:

[Serializable]

__gc class MyFile : public ISerializable, public IDisposable

{

   FileStream* f;

public:

   MyFile(String* name)

   {

      f = new FileStream(name, FileMode::OpenOrCreate, FileAccess::ReadWrite);

   }

   FileStream* GetStream()

   { return f; }

   void Close()

   { if (f != 0) f->Close(); }

   void Dispose()

   { Close(); }

protected:

   MyFile(SerializationInfo* info, StreamingContext context)

   {

    String* machine = Environment::MachineName;

    if (!info->GetString(S"__MachineName")->Equals(machine))

       throw new Exception(S"must be on the same machine!");

    if (info->GetString(S"__FileName") == S"<null>")

       throw new Exception(S"file has no name!");

    f = new FileStream(info->GetString(S"__FileName"), FileMode::Open, FileAccess::ReadWrite);

   }

   void ISerializable::GetObjectData(SerializationInfo* info, StreamingContext context)

   {

      info->AddValue(S"__MachineName", Environment::MachineName);

      info->AddValue(S"__FileName", f==0 ? S"<null>" : f->Name);

   }

};

We have made the new constructor protected so that users are not tempted to call it. We have also made the GetObjectData protected, but for added safety, we have identified that the method should be called only on an ISerializable interface pointer so that the user of this class is not tempted to call this method unless he specifically wants to serialize the object. There is one final property that we ought to mention about interfaces: default implementation. A class can derive from another class and one or more interfaces. If the base class has a member that has the same signature as a member of one of the interfaces it implements, that member can be used to provide the interface member, even if the base class does not implement the interface, as shown in this example:

__gc class Base

{

public:

   virtual void f()

   { Console::WriteLine(S"default impl"); }

};

 

__gc __interface ITest

{

   void f();

};

 

__gc class Test : public Base, public ITest

{ };

Base does not implement ITest, but it does provide an implementation of a method that has the same signature as an interface method (including the implicit virtual). The Test class derives from Base and gets the implementation of ITest::f from this base class.

 

 

 

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

 


 

< C++ .Net Early Stages 6 | Main | C++ .Net Early Stages 8 >