Delen via


Useful Code: Swap .h/.cpp

Over the past few weeks, for a number of reasons I have been working on a number of macros to extend the functionality of Visual C++. The first reason is that I have been giving preparing demos for various talks, such as Gamefest (which I mentioned earlier) as well as PDC. In these talks, we stress the many ways users can customize the IDE to suit their needs and extending it with macros is a key part of that. The second, more important reason, is that a number of customers have asked for features that we simply cannot add to the product at this point in the release cycle. When the developers on my team heard I was doing this, they even went so far as to say I was doing "their job". Needless to say I was quite flattered :) What's more, they started mentioning that they had a bunch of their own, which they'd been keeping to themselves this whole time!

 

Although this post is meant to discuss a specific macro (first among a few I'll be posting), I want to talk a bit about Visual Studio's extensibility model. If you've already written add-ins or macros then this may be old news, and I promise to keep it brief.

 

Writing macros for Visual Studio is quite simple (could you guess I was going to say that?). You can access the macros editor, which is simply a sub-session of VS, from the Tools menu, under the macros entry, or with the Alt+F11 shortcut. If it's the first time you do this, you should see a project called MyMacros with an empty module called Module1, which is a great starting point to write a macro. At this point, you will have inevitably noticed that you have entered the wonderful world of VB development where it seems as though everything is taken care of for you… The two core APIs for C++ oriented macro development are DTE, which provides access to the IDE functionality, and the VCCodeModel, which is the gateway to working with the code inside the IDE. As I unveil more macros, I'll expound more on these interfaces.

 

Today's macro is a feature request I've often received from customers: "Give me a shortcut to swap between a .h and respective .cpp file". Ask and (sometimes) ye shall receive so here is the code in all its simplistic glory. The comments should speak for themselves…

 

    Sub SwapHeaderImpl()

 

        ' get the currently active document from the IDE

        Dim doc As EnvDTE.Document = DTE.ActiveDocument

        ' get the name of the document (lower-case)

        Dim docname As String = doc.Name.ToLower

        ' get the project that contains this document

        Dim project As Project = doc.ProjectItem.ContainingProject

 

        ' verify that we are working with a C++ document

        If doc.Language = EnvDTE.Constants.dsCPP Then

 

            ' switch file name string between *.h <-> *.cpp

            If docname.EndsWith(".h") Then

                docname = docname.Replace(".h", ".cpp")

            ElseIf docname.EndsWith(".cpp") Then

                docname = docname.Replace(".cpp", ".h")

            End If

 

            ' find file in current project and open it (can you spot the flaw in this section?)

            Dim item As ProjectItem

            For Each item In project.ProjectItems

 

                ' compare and open

                If docname = item.Name.ToLower() Then

                    DTE.ItemOperations.OpenFile(item.FileNames(0), Constants.vsViewKindCode)

                    Exit Sub

                End If

 

            Next

 

            ' if file was not in project, search include paths

            Dim vcproj As VCProject = project.Object

            Dim config As VCConfiguration = vcproj.Configurations(1)

            Dim compiler As VCCLCompilerTool = config.Tools("VCCLCompilerTool")

            Dim path As String

            For Each path In compiler.FullIncludePath.Split(";")

                If My.Computer.FileSystem.FileExists(path + "\" + docname) Then

                    DTE.ItemOperations.OpenFile(path + "\" + docname, Constants.vsViewKindCode)

                End If

            Next

 

        End If

    End Sub

 

You probably noticed that I point a flaw in the code above. Indeed, the code above won't be useful as it does not traverse the entire list of files contained in a project. The problem lies in the fact that some items within a project are both an item and a container of more items (i.e. folders/filters). These items can be accessed both as a ProjectItem object and as a ProjectItems object. Furthermore, items can be deeply nested, so in order to reach every file in a project, we need a recursive function such as the following.

 

    Sub OpenInProjectItems(ByVal name As String, ByVal projItems As EnvDTE.ProjectItems)

        Dim projItem As EnvDTE.ProjectItem

        ' Find all ProjectItem objects in the given collection

        For Each projItem In projItems

            If name = projItem.Name.ToLower() Then

                DTE.ItemOperations.OpenFile(projItem.FileNames(0), Constants.vsViewKindCode)

                Exit Sub

            End If

            ' recurse to get deeply nested items

            OpenInProjectItems(name, projItem.ProjectItems)

        Next

    End Sub

We can now rewrite the original macro using this helper function.

    Sub SwapHeaderImpl()

 

        ' get the currently active document from the IDE

        Dim doc As EnvDTE.Document = DTE.ActiveDocument

        ' get the name of the document (lower-case)

        Dim docname As String = doc.Name.ToLower

        ' get the project that contains this document

        Dim project As Project = doc.ProjectItem.ContainingProject

 

        ' verify that we are working with a C++ document

        If doc.Language = EnvDTE.Constants.dsCPP Then

 

            ' switch file name string between *.h <-> *.cpp

            If docname.EndsWith(".h")

                docname = docname.Replace(".h", ".cpp")

            ElseIf docname.EndsWith(".cpp") Then

                docname = docname.Replace(".cpp", ".h")

            End If

 

            ' find file in current project and open it

            OpenInProjectItems(docname, project.ProjectItems)

 

            ' if file was not in project, search include paths

            Dim vcproj As VCProject = project.Object

            Dim config As VCConfiguration = vcproj.Configurations(1)

            Dim compiler As VCCLCompilerTool = config.Tools("VCCLCompilerTool")

            Dim path As String

            For Each path In compiler.FullIncludePath.Split(";")

                If My.Computer.FileSystem.FileExists(path + "\" + docname) Then

                    DTE.ItemOperations.OpenFile(path + "\" + docname, Constants.vsViewKindCode)

                End If

            Next

 

        End If

    End Sub

I should also discuss the lower part of the code where the macro attemps to reach files outside of the project. Often, C++ developers may be implementing headers that are not added to the project but that are in the project's include path. In order to support this common scenario, the macro digs into the VC automation engine, as evidenced by the use of VCProject, VCConfiguration and VCCLCompilerTool. Navigating these APIs is a little more difficult but there is a lot of functionality buried in them. In this case, I load the first configuration (ideally I should retrieve the active configuration) for the project and then load the object containing all the properties of the compiler build event. Within this object I find the FullIncludePath property, which I can split to obtain all possible locations accessible from this project.

Voila!

Comments

  • Anonymous
    July 25, 2005
    Swapping .h/.cpp is one of those things that once you get used to it you can't live without it. I use VC++ 6 and WndTabs (www.wndtabs.com) and have found it to be a very valuable feature.
  • Anonymous
    November 27, 2005
    Well Thanks for the great info.
    I don't know much about VB or macros but I know this macro will be very helpful.
    However, when I try to add this to Visual Studio 2005 I get the following errors. Could you tell me what I'm doing wrong?
    The code works fine excluding the lower part where it seeks include directories.

    Error 1 Type 'VCProject' is not defined.
    Error 2 Type 'VCConfiguration' is not defined.
    Error 3 Type 'VCCLCompilerTool' is not defined.


  • Anonymous
    January 13, 2006
    Mr B.Lee

    I had the same problem that you´re facing with and I found the solution at http://msdn2.microsoft.com/en-us/library/2ya384e0.aspx

    You just have to follow the steps in this site.

    Hope it helps you
  • Anonymous
    March 09, 2006
    V. Good.  However, I sped it up on my machine from 3-4 sec for a .h to .cpp swap to less than 1 sec.  I changed OpenInProjectItems to be a function returning a boolean to indicate if the file had been found and opened.  If so, terminate all further searching.  Code as follows:

       Function OpenInProjectItems(ByVal name As String, ByVal projItems As EnvDTE.ProjectItems) As Boolean
           Dim projItem As EnvDTE.ProjectItem
           ' Find all ProjectItem objects in the given collection
           OpenInProjectItems = False
           For Each projItem In projItems
               If Name = projItem.Name.ToLower() Then
                   DTE.ItemOperations.OpenFile(projItem.FileNames(0), Constants.vsViewKindCode)
                   OpenInProjectItems = True
                   Exit Function
               End If
               ' recurse to get deeply nested items
               If True = OpenInProjectItems(name, projItem.ProjectItems) Then
                   OpenInProjectItems = True
                   Exit Function
               End If
           Next
       End Function

    and in the SwapHeaderImpl call change it to be:

               If False = OpenInProjectItems(docname, project.ProjectItems) Then
                   ' if file was not in project, search include paths
                   Dim vcproj As VCProject = project.Object
                   Dim config As VCConfiguration = vcproj.Configurations(1)
                   Dim compiler As VCCLCompilerTool = config.Tools("VCCLCompilerTool")
                   Dim path As String
                   For Each path In compiler.FullIncludePath.Split(";")
                       If My.Computer.FileSystem.FileExists(path + "" + docname) Then
                           DTE.ItemOperations.OpenFile(path + "" + docname, Constants.vsViewKindCode)
                       End If
                   Next
               End If

    OK, do you think?

    David :-)
  • Anonymous
    March 15, 2006
    trying the file in the same directory or scanning the alreay open documents will also speed up the swap in most cases:

    Sub SwapHeaderImpl()

     ' get the currently active document from the IDE
     Dim doc As EnvDTE.Document = DTE.ActiveDocument
     ' get the name of the document (lower-case)
     Dim docname As String = doc.Name.ToLower
     Dim docfullname As String = doc.FullName.ToLower
     ' get the project that contains this document
     Dim project As Project = doc.ProjectItem.ContainingProject

     ' verify that we are working with a C++ document
     If doc.Language = EnvDTE.Constants.dsCPP Then

       ' switch file name string between *.h <-> *.cpp
       If docname.EndsWith(".h") Then
           docname = docname.Replace(".h", ".cpp")
           docfullname = docfullname.Replace(".h", ".cpp")
       ElseIf docname.EndsWith(".cpp") Then
           docname = docname.Replace(".cpp", ".h")
           docfullname = docfullname.Replace(".cpp", ".h")
       End If

       ' try the file in the same directory
       If My.Computer.FileSystem.FileExists(docfullname) Then
           DTE.ItemOperations.OpenFile(docfullname, Constants.vsViewKindCode)
           Exit Sub
       End If

       ' try already open files
       For Each doc In DTE.Documents
           If docname = doc.Name.ToLower Then
               DTE.ItemOperations.OpenFile(doc.FullName, Constants.vsViewKindCode)
               Exit Sub
           End If
       Next

       ' find file in current project and open it
       If False = OpenInProjectItems(docname, project.ProjectItems) Then
         ' if file was not in project, search include paths
         Dim vcproj As VCProject = project.Object
         Dim config As VCConfiguration = vcproj.Configurations(1)
         Dim compiler As VCCLCompilerTool = config.Tools("VCCLCompilerTool")
         Dim path As String
         For Each path In compiler.FullIncludePath.Split(";")
             If My.Computer.FileSystem.FileExists(path + "" + docname) Then
                 DTE.ItemOperations.OpenFile(path + "" + docname, Constants.vsViewKindCode)
             End If
         Next
       End If

     End If

    End Sub
  • Anonymous
    May 10, 2006
    what is this i can't understand
  • Anonymous
    September 26, 2006
    PingBack from http://betterdev.wordpress.com/2006/09/27/swap-hcpp-in-visual-studio-2005/
  • Anonymous
    November 15, 2006
    We can&nbsp;now rewrite the original macro using this helper function.I do not agree. Go to http://www.prohotels.info/imitation_Iceland/ocellus_South%20Iceland/evaluation_Reykjavik_1.html
  • Anonymous
    November 16, 2006
    We can&nbsp;now rewrite the original macro using this helper function.I do not agree. Go to http://www.barcelohotels.info/religious_Italy/inability_Lazio/piccalilli_Rome_1.html
  • Anonymous
    November 16, 2006
    We can&nbsp;now rewrite the original macro using this helper function.I do not agree. Go to http://www.lowpricedmotel.info/index/4
  • Anonymous
    December 05, 2006
    We can&nbsp;now rewrite the original macro using this helper function.I do not agree. Go to http://www.reductionmotel.info/bleb_United%20Kingdom/intelligibility_England/applause_Bournemouth_1.html
  • Anonymous
    January 24, 2007
    We can&nbsp;now rewrite the original macro using this helper function.I do not agree. Go to http://www.buyemployments.info/alien_Italy/accompaniment_Toscana/isoseismal_Florence_1.html
  • Anonymous
    March 14, 2007
    We can&nbsp;now rewrite the original macro using this helper function.I do not agree. Go to http://www.getjobz.info/ejaculation_Italy/sere_Toscana/intelligibility_Florence_1.html
  • Anonymous
    June 05, 2007
    The comment has been removed
  • Anonymous
    June 06, 2007
    [url=http://artmam.net/Dir-Chicken_Recipe.htm]chicken salad recipe[/url]Good food memories from childhood offer more than pleasant flavor recall.Vicky Rangel of Mountain View, California, remembers tinga, a classic slow-braised ...
  • Anonymous
    August 14, 2007
    We can&nbsp;now rewrite the original macro using this helper function.I do not agree. Go to http://apartments.waw.pl/
  • Anonymous
    January 24, 2008
    The comment has been removed
  • Anonymous
    February 01, 2008
    Of the record first american cash advance advance cash fast loan online payday
  • Anonymous
    June 15, 2009
    PingBack from http://mydebtconsolidator.info/story.php?id=6188
  • Anonymous
    June 15, 2009
    PingBack from http://debtsolutionsnow.info/story.php?id=12840
  • Anonymous
    June 16, 2009
    PingBack from http://fixmycrediteasily.info/story.php?id=3488