Now that you've gotten a good feel for how to implement an interface that has already been defined, you are now going to create your own interface, implement it with a class, and finally use that implementation within a client application.
The first step is to create the necessary projects you will need. You will be creating two projects that both reside within the same solution. Click New, Project from the File menu, select Visual C++ Project from the list of project types, and select Managed C++ Class Library. Give your project the name DogLib and click OK to create the project. This project is where you will implement the interface and associated class, which will then be compiled into a .NET assembly DLL.
To create the client application that will utilize the library, select New, Project from the File menu. Once again, select Visual C++ Project from the list of project types and select the Managed C++ Application template. Give your project the name DogClient. Before you click OK to create the project, click the radio button Add to Solution, which adds the project to the same solution as the library you just created.
The last housekeeping step before you start coding is to change the output directory properties for each of the projects you just created. Using Class View, right-click each project and select Properties. The property page that you need to update is the first page displayed, so change the OutputDirectory property as shown in Figure 17.2, making sure you do this for each project. This will place the final binaries for each project in the same directory.
The class library you are going to create will implement three interfaces using a single class. These interfaces will consist of an IDog base interface and two interfaces that derive from this base: IPoodle and IBulldog. If you haven't already guessed it, this is the reason for the strange project names.
The first step is to change the class name from the default wizard-generated class name, Class1, to a more appropriate class name, CDog. Open the DogLib.h file and make that change.
Interfaces are declared using the new Visual C++ keyword __interface. You'll notice as you start designing and declaring the methods your interface contains that this is very similar to creating the declaration for a class. The first interface to create is the IDog interface. With Visual C++ 6.0 and ATL, interfaces were declared using an Interface Definition Language (IDL) file. However, with Visual Studio .NET, you can declare an interface in the same file as the implementation class.
The IDog interface will contain one method and two properties. However, as mentioned earlier, the properties actually translate into two separate functions each: one to set the property's value and one to retrieve the value. These are known informally as the getter and the setter. The method that the IDog interface supports returns a String value and has the name Bark. Because managed interfaces can use regular .NET Framework data types, you don't have to use any of the automation-compatible data types, as are used in Microsoft Interface Definition Language (MIDL) declarations in COM and ATL.
The first property the IDog interface contains is the NumberOfFleas property. The data type for this property is the .NET value type Int32, and since it is a read-only property, the client cannot give the dog more fleas. Therefore, this property only contains a getter function.
Because properties are actually functions, you must preface the property function with the __property keyword. This does a couple of things for you. The most important is that it creates what is known as a pseudo data member. This pseudo data member allows a client using the property to use it more like a value variable rather than dealing with the getter and setter functions. In other words, you define the interface property using a get or set function, such as get_NumberOfFleas, but a client using the interface can simply refer to it as NumberOfFleas. Based on the context in which the property is used, the proper mapping to the appropriate interface method is performed.
The __property keyword is also used by the IDE within the Object Browser window, within the Class View window, and with IntelliSense. In other words, when you type the name of an interface within the IDE and use the member access operator ->, IntelliSense will display the list of member functions, but instead of just showing the getter and setter functions (which it does do), it will also just display the name of the property in the list. Figure 17.3 shows the IntelliSense drop-down list for the IDog interface.
We've already talked about the first property, NumberOfFleas. The second property the IDog interface contains is the Name property, and unlike the NumberOfFleas property, it contains a getter and a setter function. The final interface code contained within the DogLib namespace for the IDog interface is shown here:
public __gc __interface IDog { String* Bark(); property void set_Name( String* sName ); property String* get_Name(); property Int32 get_NumberOfFleas(); };
The next two interfaces are specialized interfaces that inherit from the IDog interface. Interface inheritance is similar in syntax to class inheritance in C++. In other words, following the name of the interface, a comma-delimited base interface list is specified, separated from the derived interface name by a colon. Because a poodle probably has more than the average number of fleas, the IPoodle interface will override the IDog property NumberOfFleas. Furthermore, because a poodle sounds different from the average dog when it barks, it also overrides the Bark method. A poodle, however, can be named anything, just like any other dog. Therefore, the IPoodle interface doesn't need to override the Name property. The IBulldog interface contains the same method and property as the IPoodle interface. The IPoodle and IBulldog interfaces appear as shown here:
public __gc __interface IPoodle : public IDog { String* Bark(); __property Int32 get_NumberOfFleas(); }; public __gc __interface IBulldog : public IDog { String* Bark(); property Int32 get_NumberOfFleas(); }
It's time to implement your interface. This step is quite similar to the interface implementation you did for the StringStack collection. The CDog class will implement all three interfaces. Therefore, create a base interface list from which your class inherits by placing the interfaces within a comma-delimited list, separated from the class name by a colon. As mentioned earlier, if you were to compile the library now, you would receive several errors because none of the interface methods and properties are implemented.
When a client creates an instance of the class (rather than obtaining a pointer to a specific interface, which will be discussed later this hour) and calls the member function Bark, which interface does this Bark method belong to? Each of the three interfaces contains a Bark method, and without some way of delineating which Bark method the client needs, an ambiguity exists. This is solved by using fully qualified method names within the implementation. A fully qualified name consists of the interface name and the method name, separated by the C++ scope resolution operator (::). Therefore, for the Bark method and the NumberOfFleas getter function, you will have to create three implementations for each, corresponding to each of the three interfaces. However, because there is only one Name property, you can create just one implementation function that will be used by all three interfaces. Listing 17.4 shows the implementation of the CDog class.
1: // DogLib.h 2: #pragma once 3: 4: using namespace System; 5: 6: namespace DogLib 7: { 8: public __gc __interface IDog 9: { 10: String* Bark(); 11: property void set_Name( String* sName ); 12: property String* get_Name(); 13: property Int32 get_NumberOfFleas(); 14: }; 15: 16: public __gc __interface IPoodle : public IDog 17: { 18: String* Bark(); 19: property Int32 get_NumberOfFleas(); 20: }; 21: 22: public __gc __interface IBulldog : public IDog 23: { 24: String* Bark(); 25: property Int32 get_NumberOfFleas(); 26: }; 27: 28: public __gc class CDog 29: : public IDog 30: , public IPoodle 31: , public IBulldog 32: { 33: public: 34: // shared interface members 35: void set_Name( String* sName ) { m_sName = sName; } 36: String* get_Name() { return m_sName; } 37: 38: // IDog interface 39: String* IDog::Bark() { return "Woof Woof!"; } 40: Int32 IDog::get_NumberOfFleas(){ return 20; } 41: 42: // IPoodle interface 43: String* IPoodle::Bark(){ return "Yap Yap!"; } 44: Int32 IPoodle::get_NumberOfFleas() { return 100; } 45: 46: // IBulldog interface 47: String* IBulldog::Bark(){ return "Grrr Grrr!"; } 48: Int32 IBulldog::get_NumberOfFleas() { return 5; } 49: 50: private: 51: String* m_sName; 52: }; 53: }
Even though you are currently unable to test the functionality of your interfaces and implementation, it would be a good time to compile the solution and remove any errors that are within the code.
Top |