Discovering Code
One of the basic uses of the code model is to find code that's already there. The code model gives you the tools to enumerate all the code constructs in a project as well as zero in on a code construct in a specific source file at the user's request.
A Quick Tour Through a Source File
Let's look at an example file to see how the code model represents it. Listing 12-1 shows a (somewhat) typical C# source file.
Listing 12-1 An example C# source file
namespace CMNamespace
{
delegate void CMDelegate(int delParam);
struct CMStruct
{
int field;
}
enum CMEnum
{
Member
}
interface CMInterface
{
int CMInterfaceMethod();
}
[ CMAttribute("CMVal") ]
class CMClass
{
object memberVar;
void CMCallbackFunction(int param)
{
}
int CMProperty
{
get
{
return 0;
}
set
{
}
}
}
}
The code in Listing 12-1 defines a namespace that holds a menagerie of code constructs, including a delegate, a structure, an enumeration, an interface, and a class. The interface and the class each define members of their own: the interface defines a method, and the class defines a member variable, a method, and a property.The code model gives you different ways of looking at these constructs, depending on your needs. We'll begin with the most basic representation: the CodeElement. The CodeElement object exposes a number of properties that allow you to determine the specific kind of code construct being represented. Figure 12-1 shows how the code model wraps each of the code constructs in Listing 12-1.
Figure 12-1. The CodeElement objects that the code model generates for Listing 12-1

Figure 12-1 illustrates the hierarchical relationship between the different CodeElement objects, starting with CMNamespace. The dotted lines are to remind you that the CodeElement objects have no direct connections linking them togetheryou'll need a more refined view before you can navigate the hierarchy.NoteAs a programmer, you know that modern programming languages have complexity to spareso much so that the 21 days to mastery promised by some programming books hardly seems enough time to teach yourself cout << "Hello, world";, much less all of Visual C++. Now try to imagine the complexity of the code model, which strives to distill the functionality of four major languagesVisual C++, Visual C#, Visual J#, and Visual Basicinto a single, comprehensive API. If you think one chapter might not be enough to cover all the code model, you're right, and this chapter doesn't even try. Instead, this chapter gives merely a short introduction to the two main uses of the code modelcode discovery and code generation; we'll defer complete coverage of the code model until the Appendix.
Navigating the Hierarchy
Before you can navigate the hierarchy shown in Figure 12-1, you need access to the top-level CodeElement objects. The FileCodeModel object represents a source file and its code constructs, and the FileCodeModel.CodeElements property holds the collection of top-level CodeElement objects that we want. In our example, the only top-level object is CMNamespace, so you'd expect to find only one CodeElement object in the FileCodeModel.CodeElements collection. (Alternatively, you could use the CodeModel.CodeElements collection, which holds the top-level CodeElement objects for an entire project, but then you'd have to search the items in the collection to find the one representing CMNamespace.) To get you started on your code model journey, Listing 12-2 provides functions that return the FileCodeModel object or the CodeModel object associated with the active window.
Listing 12-2 The GetFileCodeModel and GetCodeModel functions
Function GetFileCodeModel() As FileCodeModel
' Description: Returns the FileCodeModel object of the active window
Dim txtWin As TextWindow = GetTextWindow(DTE.ActiveWindow)
Dim fcm As FileCodeModel
If Not txtWin Is Nothing Then
Try
fcm = txtWin.Parent.ProjectItem.FileCodeModel
Catch e As Exception
End Try
End If
Return fcm
End Function
Function GetCodeModel() As CodeModel
' Description: Returns the CodeModel object of the active window
Dim txtWin As TextWindow = GetTextWindow(DTE.ActiveWindow)
Dim cm As CodeModel
If Not txtWin Is Nothing Then
Try
cm = txtWin.Parent.ProjectItem.ContainingProject.CodeModel
Catch e As Exception
End Try
End If
Return cm
End Function
We established already that you can't travel directly from one CodeElement object to another, so how do you find the rest of the CodeElement objects from the CMNamespace CodeElement object? The answer is that you query the CodeElement object for the interface that corresponds to the underlying code construct and then use that interface to find the related CodeElement objects. The code model defines interfaces for each of the major code constructs: CodeNamespace, CodeStruct, CodeInterface, CodeClass, CodeEnum, CodeVariable, CodeDelegate, CodeProperty, CodeAttribute, CodeFunction, and CodeParameter. Each of these interfaces offers properties and methods specific to its underlying code construct; for example, CodeFunction has a Parameters collection that contains a CodeParameter object for each formal parameter of the underlying function. The CodeElement.Kind property returns a value from the vsCMElement enumeration that indicates the specific type of the underlying code construct. For the CodeElement that wraps CMNamespace, the Kind property returns vs-CMElementNamespace, which means you can retrieve a CodeNamespace interface from that CodeElement object.Once you have the CodeNamespace interface, you can retrieve its children in the hierarchy by iterating through its Members collection. Most of the interfaces also contain a Members collection, which allows you to access their children. Navigating the code hierarchy, then, requires successive iterations of querying the CodeElement object for a specific interface and then finding the child CodeElement objects through the Members property of the interface. Figure 12-2 shows a more detailed view of our example hierarchy. The solid lines in Figure 12-2 represent the child code elements reachable through the Members collections.
Figure 12-2. A detailed view of the example hierarchy

Listing 12-3 shows one way of traversing the hierarchy. You pass in a CodeElements collection to the RecurseCodeElements routine, which iterates through the collection, writing the names of each item to the Output window and calling itself recursively whenever the current item sports a Members collection.
Listing 12-3 Navigating the hierarchy recursively
Sub TestRecurseCodeElements()
Dim output As New OutputWindowPaneEx(DTE)
output.Clear()
output.WriteLine("--- TestRecurseCodeElements ---")
output.WriteLine()
Dim fcm As FileCodeModel = GetFileCodeModel()
If Not fcm Is Nothing Then
RecurseCodeElements(fcm.CodeElements, 0)
End If
End Sub
Sub RecurseCodeElements(ByVal elements As CodeElements, _
ByVal level As Integer)
Dim output As New OutputWindowPaneEx(DTE)
Dim indent As New String(" ", 4 * level)
Dim members As CodeElements
Dim elem As CodeElement
' Iterate through each item in CodeElements collection
For Each elem In elements
' Display element name
output.WriteLine(indent & elem.Name)
members = GetMembers(elem)
If Not members Is Nothing Then
' Call macro recursively
RecurseCodeElements(members, level + 1)
End If
Next
End Sub
Function GetMembers(ByVal elem As CodeElement) As CodeElements
Dim members As CodeElements = Nothing
If Not elem Is Nothing Then
' Determine the element type and retrieve its Members collection
Select Case elem.Kind
Case vsCMElement.vsCMElementNamespace
Dim cdeNamespace As CodeNamespace = elem
members = cdeNamespace.Members
Case vsCMElement.vsCMElementClass
Dim cdeClass As CodeClass = elem
members = cdeClass.Members
Case vsCMElement.vsCMElementStruct
Dim cdeStruct As CodeStruct = elem
members = cdeStruct.Members
Case vsCMElement.vsCMElementDelegate
Dim cdeDelegate As CodeDelegate = elem
members = cdeDelegate.Members
Case vsCMElement.vsCMElementEnum
Dim cdeEnum As CodeEnum = elem
members = cdeEnum.Members
Case vsCMElement.vsCMElementInterface
Dim cdeInterface As CodeInterface = elem
members = cdeInterface.Members
End Select
End If
Return members
End Function
The GetMembers function in Listing 12-3 determines the type of the CodeElement passed to it, assigns the CodeElement to a variable of the correct type, and returns the type's Members collection. In Visual Basic, you could just as easily return Members from the CodeElement variable itself, assuming that the underlying object also implemented a Members property. However, strongly typed languages require that you explicitly cast the CodeElement variable to the correct type (or QueryInterface for the correct interface), so we tried to use code comparable to that used by such languages.The GetMembers function illustrates some of the complexities involved with managing the code model interfaces. To help manage this complexity, the code model defines a generic interface named CodeType, which you can retrieve from any object that also supports one of the following interfaces: CodeClass, CodeStruct, CodeInterface, CodeEnum, and CodeDelegate. (Incidentally, CodeType defines the Members property, which is why you can find this property on objects that support the previous interfaces.) If you have a CodeElement object, you can check for the availability of the CodeType interface directly by using the CodeElement.IsCodeType property.So, CodeType gives us yet another way to view our example hierarchy, as shown in Figure 12-3; CodeType also simplifies the logic needed to traverse the code hierarchy, as shown in Listing 12-4. The one "gotcha" in the CodeType approach is that CodeNamespace objects don't support the CodeType interface. In the RecurseCodeElementsByCodeType macro, solving this "gotcha" requires an extra If branch to check for CodeNamespace elements specifically.
Figure 12-3. A CodeType view of the example hierarchy

We're almost at the end. You can see from Figure 12-2 and Figure 12-3 that the Members collections let you reach all CodeElement objects except the attribute on the class and the parameters in the delegate and the class member function. The CodeClass interface defines an Attributes collection that holds the CodeAttribute objects that apply to the class, and the CodeDelegate and CodeFunction interfaces define a Parameters collection of CodeParameter objects; iterating through those collections allows you to complete the journey through the code hierarchy.
Listing 12-4 Using CodeType to recurse through the code hierarchy
Sub TestRecurseCodeElementsByCodeType()
Dim output As New OutputWindowPaneEx(DTE)
output.Clear()
output.WriteLine("--- TestRecurseCodeElementsByCodeType ---")
output.WriteLine()
Dim fcm As FileCodeModel = GetFileCodeModel()
If Not fcm Is Nothing Then
RecurseCodeElementsByCodeType(fcm.CodeElements, 0)
End If
End Sub
Sub RecurseCodeElementsByCodeType(ByVal elements As CodeElements, _
ByVal level As Integer)
Dim output As New OutputWindowPaneEx(DTE)
Dim indent As New String(" ", 4 * level)
Dim elem As CodeElement
' Iterate through each item in CodeElements collection
For Each elem In elements
' Display element name
output.WriteLine(indent & elem.Name)
' Check whether element is a namespace
If elem.Kind = vsCMElement.vsCMElementNamespace Then
Dim cdeNamespace As CodeNamespace = elem
' Call macro recursively
RecurseCodeElementsByCodeType(cdeNamespace.Members, level + 1)
' Check whether CodeType is available
ElseIf elem.IsCodeType Then
Dim cdeType As CodeType = elem
' Call macro recursively
RecurseCodeElementsByCodeType(cdeType.Members, level + 1)
End If
Next
End Sub
Getting a CodeElement from a Point Object
You've seen how to start at the top of a CodeElement hierarchy and visit every child. The code model also allows you to find a CodeElement from a point object in a source file. This ability enables you to create interactive features that respond to the programmer's input.The code model offers two ways of retrieving a CodeElement object from a point: the CodeElementFromPoint method of the FileCodeModel object and the CodeElement property of the TextPoint, EditPoint, and VirtualPoint objects. Here's the prototype for CodeElementFromPoint:
CodeElement CodeElementFromPoint(TextPoint Point, vsCMElement Scope);
The CodeElementFromPoint method takes a TextPoint object that specifies a location in a source file and a vsCMElement value that determines which of the enclosing code elements to return. For example, in Listing 12-1, if you had a TextPoint located on the param parameter of CMCallbackFunction, calling CodeElementFromPoint with a vsCMElement value of vsCMElementParameter would return the CodeElement representing param; calling CodeElementFromPoint with a vsCMElement value of vsCMElementClass would return the CodeElement representing CMClass.The CodeElement property takes a vsCMElement value that serves the same purpose as the Scope parameter of CodeElementFromPoint. You might wonder why the code model would bother with CodeElementFromPoint when point objects already have a way to get a CodeElement. The answer is that the CodeElement property is implemented in terms of CodeElementFromPoint and is just a concise way of calling xxxPoint.Parent.Parent.ProjectItem.FileCodeModel.CodeElementFromPoint.
Lab: Finding CodeElement Objects from Point ObjectsThe TestCodeElementFromPoint and TestCodeElementProperty macros let you get a feel for how the Scope parameter affects the CodeElement returned by the CodeElementFromPoint method and the CodeElement property. To use either of the macros, open the Output window, place the insertion point anywhere in a source code file, and run the macro. The macro calls CodeElementFromPoint or the CodeElement property with each possible vsCMElement value in the Scope parameter and sends information about the returned CodeElement to the Output window.If you run these macros on different code constructs in source files from different languages, you'll soon notice that Visual C++ does the best job of returning the element you request. However, best doesn't mean perfect. For example, suppose you have the Visual C++ equivalent of Listing 12-1. If you were to place the insertion point on the param parameter of CMCallbackFunction and run the TestCodeElementFromPoint macro, Visual C++ would correctly return CMCallbackFunction for vsCMElementFunction, CMClass for vsCMElementClass, and CMNamespace for vs-CMElementNamespace, but it would miss param entirely when asked for vsCMElementParameter! The same test for the other languages yields the following results: Visual C# ties Visual J# at 2 for 3 (vsCMElementParameter and vsCMElementFunction), and Visual Basic is 1 for 3 (vsCMElementParameter). What's worse are the false positivesthe incorrect CodeElement objects that are returned in place of the correct ones. You might order a vsCMElementParameter, but don't be surprised if the waiter brings you a vsCMElementFunction instead.So what are you to make of all this? Basically, you can't trust CodeElementFromPoint and CodeElement in the general caseat least not yet. At best, you might be able to get by in certain situations, when you know that only a particular language will be used. |