Inside Microsoft Visual Studio .NET 2003

Inside Microsoft Visual Studio .NET 2003

4.5 2
by Brian Johnson, Marc Young, Craig Skibo
     
 

Microsoft Visual Studio .NET is a pivotal product in the history of programming tools, since it brings together multiple languages that can interoperate in the Microsoft .NET Framework. This title offers an in-depth examination of the architecture of Microsoft Visual Studio .NET to help professional developers get the most out of its capabilities. It drills down

See more details below

Overview

Microsoft Visual Studio .NET is a pivotal product in the history of programming tools, since it brings together multiple languages that can interoperate in the Microsoft .NET Framework. This title offers an in-depth examination of the architecture of Microsoft Visual Studio .NET to help professional developers get the most out of its capabilities. It drills down into the Visual Studio .NET integrated development environment (IDE), code editors, application design, and project management tools. It also covers features that allow developers to use Visual Studio .NET with their favorite programming tools. By the end of this book, developers will understand what Visual Studio .NET is and how and why it works as it does, learn how to maximize their productivity with it, and know how to customize its tools and environment to suit the projects at hand.

Product Details

ISBN-13:
9780735618749
Publisher:
Microsoft Press
Publication date:
02/12/2003
Edition description:
2003 Edition
Pages:
578
Product dimensions:
7.37(w) x 8.97(h) x 1.40(d)

Read an Excerpt

Chapter 12.

The Code Model

  • Discovering Code
    • A Quick Tour Through a Source File
    • Getting a CodeElement from a Point Object
  • Generating Code
    • Building a Source File
    • Looking Ahead


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.

Figure 12-1 The CodeElement objects that the code model generates for Listing 12-1 (Image unavailable)

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.


NOTE:
As a programmer, you know that modern programming languages have complexity to spare—so 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 languages—Visual C++, Visual C#, Visual J#, and Visual Basic—into 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 model—code 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.

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.

Figure 12-2 A detailed view of the example hierarchy (Image unavailable)

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.

Figure 12-3 A CodeType view of the example hierarchy (Image unavailable)

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


Lab: Using the Code Model Explorer Add-in:
The Code Model Explorer (CME) add-in provides a handy interface for exploring the code model objects that represent your Visual Studio .NET projects. In this lab, you�ll use the CME add-in to examine a typical add-in project. Follow these steps to get started:

  1. Create a new Visual C++ add-in project.
  2. Choose Tools | Code Model Explorer to display the CME add-in�s main form.
  3. Open the Output window.
  4. Select the definition of CConnect, as shown in Figure 12-4.
  5. Figure 12-4 The CME add-in output (Image unavailable)

When you select a CodeElement node in the CME add-in tree view, the add-in dumps information about the CodeElement to the Output window. For example, in the Output window shown in Figure 12-4, the "--- CodeClass Details ---" header indicates that CConnect is represented by a CodeClass object; the "Bases" entry shows CConnect�s three base classes—CComObjectRootEx, CComCoClass, and IDispatchImpl; and the "Children" entry lists the 14 CodeElement objects in CConnect�s Children collection.

When you select the Show Definition check box, the CME add-in will do its best to display the source code for the selected CodeElement node. If you select the Show Definition check box, expand the CConnect node, and select the OnConnection node, you�ll see the OnConnection source code highlighted in the Connect.cpp file (as shown in Figure 12-5).

Figure 12-5 The CME add-in Show Definition feature (Image unavailable)

Selecting the Show Children check box tells the CME add-in to display the CodeElement objects from the Children collection. For Visual C++ CodeElement objects, the Children collection is a superset of the Members collection and includes objects such as the bases for the parent CodeElement. Select Show Children and then collapse and reexpand the CConnect node, and you�ll see its Children nodes highlighted in red (as shown in Figure 12-6). Notice that the corresponding COM node from the Members collection doesn�t have any child nodes, which means that the red COM node has Children nodes of its own. Expand the red COM node and you�ll see that its children consist of two COM_INTERFACE_ENTRY nodes; select the first COM_INTERFACE_ENTRY node to see the output shown in Figure 12-7.

Figure 12-6 The Show Children feature (Image unavailable)

Figure 12-7 COM_INTERFACE_ENTRY details

Finally, select the CodeModel option, which displays the CodeElement objects available through the CodeModel.CodeElements property. In addition to the top-level objects available through the FileCodeModel object, you�ll find CodeElement objects representing everything from macros to IDL libraries.


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 Objects:
The 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 vsCMElementNamespace, 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 positives—the 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 case—at 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.


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:

(Code Unavailable)

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.

Read More

Customer Reviews

Average Review:

Write a Review

and post it to your social network

     

Most Helpful Customer Reviews

See all customer reviews >