Training
Certifications
Books
Special Offers
Community




 
Inside Microsoft® Visual Studio® .NET 2003
Author Brian Johnson, Craig Skibo, Marc Young
Pages 576
Disk N/A
Level Int/Adv
Published 02/12/2003
ISBN 9780735618749
ISBN-10 0-7356-1874-7
Price(USD) $49.99
To see this book's discounted price, select a reseller below.
 

More Information

About the Book
Table of Contents
Sample Chapter
Index
Companion Content
Related Series
Related Books
About the Author

Support: Book & CD

Rate this book
Barnes Noble Amazon Quantum Books

 


Chapter 12: The Code Model



12   The Code Model

The Visual Studio .NET code model promises to be nothing less than the programmer's Universal Translator, the macro writer's Babel fish, the hacker's Esperanto. The idea is simple—define a single API that captures the essence of the most common programming constructs, and have each of the languages in Visual Studio .NET implement that API in its native tongue. The result is a single set of objects—the code model—that a programmer can use to read or write code in any of the languages in Visual Studio .NET.

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.

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
{
}
}
}
}

Listing 12-1 An example C# source file

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.

Click to view graphic
Click to view graphic

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 together—you'll need a more refined view before you can navigate the hierarchy.

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.

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

Listing 12-2 The GetFileCodeModel and GetCodeModel functions

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 vsCMElementNamespace, 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.

Click to view graphic
Click to view graphic

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.

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

Listing 12-3 Navigating the hierarchy recursively

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.

Click to view graphic
Click to view graphic

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.

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

Listing 12-4 Using CodeType to recurse through the code hierarchy

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.

Generating Code

The other main use of the code model is to generate source code programmatically. This aspect of the code model reveals most clearly the promise of a universal programming language: the same AddClass method that generates a Visual C# class when run against a .cs file will generate a Visual C++ class when run against a .cpp file, and will generate a Visual J# class when run against a .jsl file. In this section, we'll show how to generate the source file in Listing 12-1 by using the code model. Note that the following example provides only the briefest of introductions to this subject—for details about the objects and methods used in this section, please refer to the Appendix.

Building a Source File

All of the code model methods that generate code begin with Add, as in AddNamespace, AddClass, AddVariable, and so on. By calling an Add method on a CodeElement, you create a new code construct within the CodeElement. Note that CodeElement objects can't adopt other existing CodeElement objects—they can have children only by bearing their own; a consequence of this is that you have to create your code hierarchy from the top down. The top-most element in Listing 12-1 is CMNamespace, so you begin by creating a new namespace, as shown in the following code:

Sub CreateListing_12_1()
    Dim fcm As FileCodeModel = GetFileCodeModel()
 
    If Not fcm Is Nothing Then
        Dim cdeNamespace As CodeNamespace
 
        ' Try to create a new namespace
        Try
            cdeNamespace = fcm.AddNamespace("CMNamespace")
        Catch e As Exception
        End Try
 
        ' If successful, create the rest of the code elements
        If Not cdeNamespace Is Nothing Then
 
            
 
        End If
    End If
End Sub

The FileCodeModel.AddNamespace method generates the source code for a new top-level namespace and returns a reference to its corresponding CodeNamespace object. The call to AddNamespace takes place within a Try/Catch block because Add methods throw an exception if they're unable to create the requested code element. Assuming all goes well, you'll have a reference with which to create the namespace's child elements.

The first child element to create is CMDelegate. CMDelegate defines an integer parameter, and you can create both the delegate and the parameter in the same Try/Catch block:

Try
    Dim cdeDelegate As CodeDelegate
 
    ' Try to create a new delegate
    cdeDelegate = cdeNamespace.AddDelegate("CMDelegate", _
        vsCMTypeRef.vsCMTypeRefVoid)
 
    ' Try to add a new parameter to the delegate
    cdeDelegate.AddParameter("delParam", vsCMTypeRef.vsCMTypeRefInt)
Catch e As Exception
End Try

AddDelegate and AddParameter both take a parameter that specifies the code element's type; the vsCMTypeRef.vsCMTypeRefVoid value represents a void type, and the vsCMTypeRef.vsCMTypeRefInt value represents an integer type. The previous code doesn't declare a variable to store AddParameter's return value because nothing further needs to be done with the delegate's parameter.

Next you create the structure and its field:

Try
    Dim cdeStruct As CodeStruct
 
    ' Try to create a new structure
    cdeStruct = cdeNamespace.AddStruct("CMStruct", -1)
 
    ' Try to add a new field to the structure
    cdeStruct.AddVariable("field", vsCMTypeRef.vsCMTypeRefInt)
Catch e As Exception
End Try

By now the rhythm should be familiar: define a variable for the new code element, assign the return value of the Add method to the variable, and call methods on the variable to alter the new code element. The -1 parameter to AddStruct tells the method to insert the source code for the new structure after every other sibling code element; all the Add methods accept this optional parameter. Here's the code for creating the enumeration and the interface:

Try
    Dim cdeEnum As CodeEnum
 
    ' Try to create a new enumeration
    cdeEnum = cdeNamespace.AddEnum("CMEnum", -1)
 
    ' Try to add a new member to the enumeration
    cdeEnum.AddMember("Member")
Catch e As Exception
End Try
 
Try
    Dim cdeInterface As CodeInterface
 
    ' Try to create a new interface
    cdeInterface = cdeNamespace.AddInterface("CMInterface", -1)
 
    ' Try to add a new method to the interface
    cdeInterface.AddFunction("CMInterfaceMethod", _
        vsCMFunction.vsCMFunctionFunction, vsCMTypeRef.vsCMTypeRefInt)
Catch e As Exception
End Try

The second parameter to AddFunction lets you specify what kind of function to create, such as a constructor, a destructor, a pure virtual function, and so on; the value of vsCMFunction.vsCMFunctionFunction for CMInterfaceMethod creates a vanilla function. Finally, here's the code that creates the class, its attribute, and all its members:

Try
    Dim cdeClass As CodeClass
 
    ' Try to create a new class
    cdeClass = cdeNamespace.AddClass("CMClass", -1)
 
    Try
        ' Try to add a new attribute to the class
        cdeClass.AddAttribute("CMAttribute", "CMVal")
    Catch e As Exception
    End Try
 
    Try
        ' Try to add a new member variable to the class
        cdeClass.AddVariable("memberVar", vsCMTypeRef.vsCMTypeRefObject, -1)
    Catch e As Exception
    End Try
 
    Try
        Dim cdeFunction As CodeFunction
 
        ' Try to add a new member function to the class
        cdeFunction = cdeClass.AddFunction("CMCallbackFunction", _
            vsCMFunction.vsCMFunctionFunction, _
            vsCMTypeRef.vsCMTypeRefVoid, -1)
 
        ' Try to add a new parameter to the member function
        cdeFunction.AddParameter("param", vsCMTypeRef.vsCMTypeRefInt)
    Catch e As Exception
    End Try
 
    Try
        ' Try to add a new property to the class
        cdeClass.AddProperty("CMProperty", "CMProperty", _
            vsCMTypeRef.vsCMTypeRefInt, -1)
    Catch e As Exception
    End Try
Catch e As Exception
End Try

Now that you're done writing the code that writes the code, it's time for some bad news: none of the language implementations will generate a complete simulacrum of Listing 12-1 from this code, either because the language doesn't support a particular code construct or because the language hasn't yet implemented a particular Add method. You can verify this for yourself by running the CreateListing_12_1 macro on different language source files—if you do, you'll find that Visual C# generates everything but the attribute; Visual C++ generates everything but the delegate and the property; Visual J# generates everything but the delegate, structure, enumeration, attribute, and property; and Visual Basic doesn't generate anything. Again, that's the bad news—the good news is that the code model is a young branch of the automation object model, so you can expect major improvements in its next version.

Looking Ahead

The code model brings us to the end of Part II of this book. Part III takes you into territory mostly uncharted by other programming books: in it you'll learn how to set up Setup, find help on Help, hotwire the V12 command-line engine hidden under the unassuming hood of the Visual Studio .NET IDE, and keep your source safe from everyone, including yourself.



Last Updated: January 28, 2003
Top of Page