A C++ Macro to Stub Interface Methods

In my previous column, I showed how to create a class library in C++ that can be called from a macro, and presented a very simple function in the class library that adds braces for you, suitable for use after typing an if or for statement.

In this column, I show you a somewhat meatier piece of code: one for use with a class that implements one or more interfaces. When you run the macro, it adds empty function bodies for all the functions in the interface. I developed this as another function in the class library from the last column, so I won't repeat the instructions for setting up your projects and your macros, associating the macro with a toolbar button, or closing Visual Studio each time you rebuild the class library code.

Elements, Classes, Interfaces, and Bases

When you write a class in Visual C++, the Design Time Environment thinks of it as several things, including a class and a CodeElement. It has base classes, each of which is also a CodeElement. Your class, its bases, and even its functions can all be represented as objects with properties such as Name and Type—and your macros can manipulate those objects. For example, an object that represents a class has a method called AddFunction(), which actually adds functions to one of your classes, right from the macro. That's the heart of this macro: a loop that calls AddFunction() repeatedly to add each function that's in the interface your class implements. This generates the code inside your class, and it's quite fun to see.

I found the code pretty hard to write, and you're likely to find it hard to read, for three reasons:

  • It contains parallel object hierarchies: one under the EnvDTE namespace with classes such as CodeElement, and one under the Microsoft::VisualStudio::VCCodeModel namespace with classes such as VCCodeElement. The functionality is not quite the same in the two hierarchies.
  • Most of the functions return quite general types, or the properties have quite general types. For example, the Namespaces property of a code model is not a CodeNamespace or a VCCodeNamespace, but rather a CodeElement. That means the code contains a lot of static_cast<> and dynamic_cast<>. (Option Strict Off in Visual Basic hides a lot, doesn't it?) I cast to a Microsoft::VisualStudio::VCCodeModel class only when the methods or properties I want aren't in the equivalent class from the EnvDTE namespace.
  • The arrays and collections returned from these functions are all one-based, and the Count property tells you how many items are in a collection.

Bear with me, though, because going through this exercise gives you more than just the ability to write C++ macros in C++. You can also learn how Visual Studio and its wizards think of your code, and how they work internally.

The general structure of the InsertMethods() function is, in pseudo-code:

Get a collection of the classes in the file you have open.
For each class:
   * Get a collection of the bases, which includes interfaces.
   For each base:
      * Determine if it is an interface, and if so:
         * Get all the functions in the interface.
         For each function:
            * Determine if it is not yet implemented in this file,
              and if not:
               * Add the function to the class;
               * Get all the parameters to the function;
               * Add the parameters, one at a time, to the function.

Where Am I?

When you invoke a macro and you have your selection point inside a class definition, it would be nice if the macro found your place in the overall class hierarchy of your project—and for that matter, in the project hierarchy of your solution. The following code makes that happen:

EnvDTE::ProjectItem *pi = DTE->ActiveWindow->ProjectItem;
VCFileCodeModel* vcCM = dynamic_cast<VCFileCodeModel*>
   (pi->FileCodeModel);
VCCodeNamespace* ns = dynamic_cast<VCCodeNamespace*>
   (vcCM->Namespaces->Item(__box(1)));
VCCodeElements* classes = static_cast<VCCodeElements*>(ns->Classes);

A file code model is a set of objects that represent your code, but only as contained in a single file (the file that has focus when you invoke the macro). If you click the toolbar button without having a file opened, or when you've been using Help, you may get strange results. I'm not trying to create production-quality code, so make sure you get your selection point into a C++ file before you run this macro.

The classes you've created are not floating loose in the code model; they're in a namespace. I'm assuming each file has exactly one namespace in it. If your files are often multi-namespace affairs, you'll need to tweak this macro. Also, I work with all the classes in the file rather than trying to find the one that contains the selection point. It's fairly harmless and makes the code simpler.

You can see the casts starting to pile up in this code. A good approach when writing these sorts of macros is to try the line without a cast first. If that won't compile, try a static cast. If you still get compiler errors, go with a dynamic cast. It also wouldn't hurt to add a few checks to this code to make sure that the dynamic casts return non-null pointers.

Finding the Interfaces for Each Class

The VCCodeClass has a handy property called Bases that represents all the bases of the class. Because C++ doesn't use different syntax for inheriting from a base class and implementing an interface, Bases gives you interfaces as well as traditional base classes. You can get the type of these and ask whether they are interfaces or not with the following code:

for (int l=1; l<classes->Count+1; l++)
{
   VCCodeClass* vcClass = dynamic_cast<VCCodeClass*>
           (classes->Item(__box(l)));
   VCCodeElements* vcCE = static_cast<VCCodeElements*>
           (vcClass->Bases);
   int elems = vcCE->Count;
   for (int i=1; i<elems+1; i++)
   {
      VCCodeElement* element = dynamic_cast<VCCodeElement*>
                 (vcCE->Item(__box(i)));
      VCCodeBase* baseclass = dynamic_cast<VCCodeBase*>(element);
      EnvDTE::CodeType* ct = baseclass->Class;
      if (ct->Kind == EnvDTE::vsCMElement::vsCMElementInterface)
      {
         VCCodeInterface* in = dynamic_cast<VCCodeInterface*>(ct);
//use in somehow
      }    // if this is an interface
      }    // for i: looping through the bases of this class
}          // for l: looping through the classes in this file

This code uses the Item() function to get each class in the classes collection. Then it grabs the Bases collection and loops through the CodeElements returned. The first step is to cast from a CodeElement to a VCCodeElement; from there, I cast to a VCCodeBase. The baseclass has a Class property that is a CodeType. The Kind property of the CodeType represents the kind of class that it is; this code confirms it's an interface. Knowing that, I can cast it to a VCCodeInterface pointer with confidence.

Are the Functions Already Implemented?

Once the interface has been found, the next step is to get all the functions in the interface and find out whether each is already implemented:

VCCodeElements* functions = static_cast<VCCodeElements*>
                                 (in->Functions);
VCCodeElements* targetFunctions = static_cast<VCCodeElements*>
                                       (vcClass->Functions);
for (int j=1;j<functions->Count+1;j++)
{
   EnvDTE::CodeFunction* ifunction =
               dynamic_cast<EnvDTE::CodeFunction*>
                       (functions->Item(__box(j)));
   if (!targetFunctions->Find(ifunction->Name))
   {
      //Add the function
   }
}    // for j: looping through functions in the interface

This code gets all the functions in the interface, as well as all the functions that have already been implemented in the class being modified, vcClass. It then loops through the functions in the interface, verifying whether each one is in the targetfunctions collection. If it is, the code's work is done—the function has already been added. If it's not, it's time to add this function to the class being modified.

Adding the Function

The AddFunction method adds a function without parameters. The parameters then have to be added to it one by one. The following code makes extensive use of CodeFunction objects:

EnvDTE::CodeFunction* cf = vcClass->AddFunction(ifunction->Name,
   ifunction->FunctionKind,
   ifunction->Type,
   0,    // position
   ifunction->Access,
   S"");
VCCodeElements* parms = static_cast<VCCodeElements*>
                            (ifunction->Parameters);
for (int k=1; k>parms->Count+1; k++)
{
   EnvDTE::CodeParameter* jParm =
           dynamic_cast<EnvDTE::CodeParameter* 
                 (parms->Item(__box(k)));
   cf->AddParameter(jParm->Name,
                 jParm->Type,
                   __box(-1));    // position
 }    // for k: looping through parameters of the function

Most of the parameters to AddFunction come directly from ifunction, the function in the interface that is to be added. The position parameter establishes the location in the class definition: 0 means that the function is inserted at the beginning of the class. The last position is a file name, and passing an empty string like this results in the function being added to the same file where the class was already defined.

Similarly, the calls to AddParameter take their parameters from the original parameters of the function from the interface that is to be added to the class. (If you can follow that sentence the first time, you're really starting to understand how this all works.) It's important to add each parameter at the end of the parameter list (signified by -1), because the Parameters property lists the parameters from left to right within the function.

Wrapping It Up

That's a wrap! All the loops and if blocks end, and the code is ready to go. My only complaint with it is that AddFunction() puts extra access qualifiers in your code, so that you end up with a class definition that starts like this:

public __gc class Foo2: public IList
   {
   public:

      void RemoveAt(int)
      {
      }
   public:

      void Remove(System::Object __gc *)
      {
      }

Still, the extra qualifiers don't hurt anyone, and you could always delete them.

I'll confess, I started to write this macro because I like the feature in Visual Basic that stubs my functions for me. I type Implements IFoo and press Enter, and my class fills up with function bodies. Well, now I can have that functionality in my C++ code, and I got a tour of the code model along the way. This kind of code is not for those who don't like to cast, but it has its rewards. I plan some more exploration, so stay tuned!

About the Author

Kate Gregory is a founding partner of Gregory Consulting Limited (www.gregcons.com). In January 2002, she was appointed MSDN Regional Director for Toronto, Canada. Her experience with C++ stretches back to before Visual C++ existed. She is a well-known speaker and lecturer at colleges and Microsoft events on subjects such as .NET, Visual Studio, XML, UML, C++, Java, and the Internet. Kate and her colleagues at Gregory Consulting specialize in combining software develoment with Web site development to create active sites. They build quality custom and off-the-shelf software components for Web pages and other applications. Kate is the author of numerous books for Que, including Special Edition Using Visual C++ .NET.




Comments

  • parameter is incorrect error

    Posted by MRutledge on 09/30/2005 04:24pm

    I get a parameter is incorrect error. This is what I have implemeneted:
    [code]
    			static String* StubInterfaces(EnvDTE::_DTE* DTE)
    			{
    				EnvDTE::ProjectItem *pi = DTE->ActiveWindow->ProjectItem;
    				VCFileCodeModel* vcCM = dynamic_cast
    					(pi->FileCodeModel);
    				VCCodeNamespace* ns = dynamic_cast
    					(vcCM->Namespaces->Item(__box(1)));
    				VCCodeElements* classes = static_cast(ns->Classes);
    
    				for (int l=1; lCount+1; l++)
    				{
    					VCCodeClass* vcClass = dynamic_cast
    						(classes->Item(__box(l)));
    					VCCodeElements* vcCE = static_cast
    						(vcClass->Bases);
    					int elems = vcCE->Count;
    					for (int i=1; i
    							(vcCE->Item(__box(i)));
    						VCCodeBase* baseclass = dynamic_cast(element);
    						EnvDTE::CodeType* ct = baseclass->Class;
    						if (ct->Kind == EnvDTE::vsCMElement::vsCMElementInterface)
    						{
    							VCCodeInterface* in = dynamic_cast(ct);
    							VCCodeElements* functions = static_cast
    								(in->Functions);
    							VCCodeElements* targetFunctions = static_cast
    								(vcClass->Functions);
    							for (int j=1;jCount+1;j++)
    							{
    								EnvDTE::CodeFunction* ifunction =
    									dynamic_cast
    									(functions->Item(__box(j)));
    								if (!targetFunctions->Find(ifunction->Name))
    								{
    									EnvDTE::CodeFunction* cf = vcClass->AddFunction(ifunction->Name,
    										ifunction->FunctionKind,
    										ifunction->Type,
    										0,    // position
    										ifunction->Access,
    										S"");
    									VCCodeElements* parms = static_cast
    										(ifunction->Parameters);
    									for (int k=1; k>parms->Count+1; k++)
    									{
    										EnvDTE::CodeParameter* jParm =
    											dynamic_cast
    											(parms->Item(__box(k)));
    										cf->AddParameter(jParm->Name,
    											jParm->Type,
    											__box(-1));    // position
    									}    // for k: looping through parameters of the function   }
    								}    // for j: looping through functions in the interface
    							}    // if this is an interface
    						}    // for i: looping through the bases of this class
    					}          // for l: looping through the classes in this file
    				}
    
    				return("");
    			}
    [/code]

    Reply
Leave a Comment
  • Your email address will not be published. All fields are required.

Top White Papers and Webcasts

  • Live Event Date: November 6, 2014 @ 2:00 p.m. ET / 11:00 a.m. PT Are you wanting to target two or more platforms such as iOS, Android, and/or Windows? You are not alone. 90% of enterprises today are targeting two or more platforms. Attend this eSeminar to discover how mobile app developers can rely on one IDE to create applications across platforms and approaches (web, native, and/or hybrid), saving time, money, and effort and introducing apps to market faster. You'll learn the trade-offs for gaining long …

  • Live Event Date: October 29, 2014 @ 11:00 a.m. ET / 8:00 a.m. PT Are you interested in building a cognitive application using the power of IBM Watson? Need a platform that provides speed and ease for rapidly deploying this application? Join Chris Madison, Watson Solution Architect, as he walks through the process of building a Watson powered application on IBM Bluemix. Chris will talk about the new Watson Services just released on IBM bluemix, but more importantly he will do a step by step cognitive …

Most Popular Programming Stories

More for Developers

Latest Developer Headlines

RSS Feeds