This column presents a macro 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.
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: a macro 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.
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*>
VCCodeElements* vcCE = static_cast<VCCodeElements*>
int elems = vcCE->Count;
for (int i=1; i<elems+1; i++)
VCCodeElement* element = dynamic_cast<VCCodeElement*>
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*>
VCCodeElements* targetFunctions = static_cast<VCCodeElements*>
for (int j=1;j<functions->Count+1;j++)
EnvDTE::CodeFunction* ifunction =
//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,
0, // position
VCCodeElements* parms = static_cast<VCCodeElements*>
for (int k=1; k>parms->Count+1; k++)
EnvDTE::CodeParameter* jParm =
__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
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.