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 (21)

  1. PatriotB says:

    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.

  2. B. Lee says:

    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.

  3. Jose Ricardo says:

    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

  4. David Coster says:

    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 🙂

  5. Tom Moers says:

    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

  6. asif says:

    what is this i can’t understand

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

  8. 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

  9. 4 says:

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

    I do not agree. Go to http://www.lowpricedmotel.info/index/4

  10. 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

  11. 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

  12. wfertgtr says:

    [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 …

  13. We can&nbsp;now rewrite the original macro using this helper function.

    I do not agree. Go to http://apartments.waw.pl/

  14. Based on cash advance service advance advance america cash center

  15. Of the record first american cash advance advance cash fast loan online payday