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