Macros for C++, in C++

The Visual Studio development environment is made by programmers for programmers. So, it’s not surprising that it’s extensible by programmers, too. There are actually two ways to extend Visual Studio: macros and add-ins. Macros are suggested for your own use, as quick keystroke-savers, while add-ins can be large and complex, tools you can distribute and sell; they provide significant functionality.

Another difference between add-ins and macros is that add-ins are usually written in C++ (although you can create them in any .NET language) and macros are written in Visual Basic only. I just had to find a way around that, so my solution is to write a class library that provides the macro functionality I want, and make simple calls into that library from a VB macro. As an added bonus, I can distribute that library as a compiled assembly, rather than only distributing the source code of the macro. I also can re-use the library in several macros, and in add-ins as well.

The DTE Object

Whether you’re writing a macro or an add-in, the key class and the starting point of your work is DTE, in the EnvDTE namespace. This represents the IDE itself, the design-time environment. From here, you can access the document being edited, properties the user has set, and more. An instance of it is provided to any macro you run—and your macro code can pass that instance to code it calls, such as the Managed C++ class library I created.

Your best guide around the DTE class and the EnvDTE namespace is Intellisense: the online help features Visual Basic examples almost exclusively. Most of the properties of this class make perfect sense once you see them: ActiveDocument, for example, represents the active document, the one being edited when the macro is invoked.

Creating a Macro Project

To create a macro project, open Visual Studio and choose View, Other Windows, Macro Explorer. Expand the MyMacros node and you’ll see Module1. I renamed mine to Editing because I intend to write a number of macros to make editing and entering code simpler and keep them in this module. Expand the module and you’ll see a macro called Macro1: I renamed mine to Braces. This macro is only one line long because it just calls a method in the class library. I’ll show it to you shortly.

Creating the Class Library

I opened a second instance of Visual Studio to create the class library. This is just an ordinary Managed C++ class library. Then, add a reference to envdte, which appears on the .NET tab of the Add Reference dialog. Here’s the class I implemented, with one method called Braces():

public __gc class Utilities
{
public:
   static String* Braces(EnvDTE::_DTE* DTE)
   {
      int tabsize = 4;
      EnvDTE::TextSelection* ts =
        static_cast<EnvDTE::TextSelection*>
           (DTE->ActiveDocument->Selection);
      ts->Insert(S"\n",
         EnvDTE::vsInsertFlags::vsInsertFlagsInsertAtEnd);
      ts->MoveToLineAndOffset(ts->TopPoint->Line+1,
                              ts->TopPoint->LineCharOffset, false);
      ts->Insert(S"{\n\n",
         EnvDTE::vsInsertFlags::vsInsertFlagsInsertAtEnd);
      ts->MoveToLineAndOffset(ts->TopPoint->Line+2,
                              ts->TopPoint->LineCharOffset, false);
      ts->Insert(S"}",
         EnvDTE::vsInsertFlags::vsInsertFlagsInsertAtEnd);
      ts->MoveToLineAndOffset(ts->TopPoint->Line-1,
                              ts->TopPoint->LineCharOffset+tabsize,
                              false);
      return("");
   }
};

Notice that this method takes a DTE reference. That’s what lets it do all the snazzy macro things you’d normally code in VB inside the macro itself. This example is small, just enough to get started. I get the ActiveDocument, ask it for the selection (which may be as small as an insertion point if no text is selected), and cast it from an Object* to the TextSelection* I know it is. At this point, I use the Insert and MoveToLineAndOffset methods repeatedly, to insert an open brace, a blank line, and a close brace, all indented appropriately. The value false is passed to MoveToLineAndOffset to ensure that the selection is not extended to the new location.

Making the Class Library Available to the Macro Project

Once the class library is built, the assembly is available to add as a reference to the Macro project. Double-clicking the macro edits it, and the Macro Editor looks enough like Visual Studio that you’ll just use it without thinking about it too much. You can see a References tab, and you can right-click and choose Add Reference. There’s just one problem: In Macro Explorer, the Add Reference Dialog is a lot trimmer than you’re used to seeing in Visual Studio.

Where are the COM and Project tabs? More distressingly, where is the Browse button? How are you going to find the new assembly to add the reference to it? And while you’re asking questions like that, just what makes assemblies appear on that .NET tab anyway? It’s tempting to think they are assemblies from the GAC, but you can’t add a reference to an assembly that’s only in the GAC, so that can’t be it. It turns out the assemblies in that list are all in a particular folder on your hard drive: Visual Studio Install Path\Common7\IDE\PublicAssemblies. You can copy anything you like there, and it will appear on that dialog. You won’t care when you are building ordinary projects, but it’s vital information for a macro project that uses other assemblies.

I copied the assembly from the class library project to the PublicAssemblies folder, and then flipped back to Visual Studio in which the Macro Explorer was open and added the reference. Here’s the VB code for the macro:

Imports EnvDTE
Imports System.Diagnostics

Public Module Editing
   Sub Braces()
      CppMacroClasses.Utilities.Braces(DTE)
   End Sub
End Module

I only had to write one line of this: the call to Braces. I can handle having that much VB in my C++ projects without complaining.

Getting the Macro Onto a Toolbar

There are lots of ways to execute a macro, but getting it onto a toolbar is handy. In Visual Studio, right-click any toolbar and then click Customize. On the Toolbar tab, click New, and give the toolbar a name (I called mine Kate). On the Commands tab, select Macros in the category list (it’s second from the bottom) and MyMacros.Editing.Braces (or whatever you called your macro) in the commands list, like this:

Drag the MyMacros.Editing.Braces entry onto the empty toolbar floating near the dialog; then drag the toolbar up with the other toolbars. Close the Customize dialog. Open a file of code, and type this line:

if (a == b)

Click the toolbar button and the braces should appear following your if statement. That’s a time-saver, right?

Associating a Keystroke with the Macro

Mousing all the way up to the toolbar to get your braces inserted for you isn’t as convenient as it could be. That same Customize dialog that got the button onto the toolbar also is used for setting keyboard shortcuts. On the Commands tab, click Keyboard. It doesn’t matter which command you select before clicking. On the Options dialog that appears, scroll through the alphabetical list to Macros.MyMacros.Editing.Braces, select it, click in the Press Shortcut Keys box, and type a shortcut key that seems good to you. Alt+B for braces made sense to me. Click OK. If it’s your first shortcut, let Visual Studio rename the Default Settings to something else: mine is called Kate Default Settings now.

The Development Cycle

There is one annoyance when you develop your macros this way. After you’ve run the macro, you are likely to decide to change it a bit. So, you change the C++ code and rebuild the assembly, and then copy it over to PublicAssemblies. Unfortunately, that copy will fail because the assembly is in use. You’ll need to close Visual Studio so that it will release the assembly. Then, you can copy the assembly again and start Visual Studio to test your changes. It’s a little awkward, so be prepared for it.

So there you have it: a class library in C++ that works with the DTE object, a tiny macro in VB that uses the class library, a neat trick to add the reference to the macro project, and you’ve got a macro for C++, written in C++. You can adapt this to create a variety of handy macros, each calling a different method in the class library. I’ve got more macro and add-in work ahead, so stay tuned.

About the Author

Kate Gregory is a founding partner of Gregory Consulting Limited (www.gregcons.com). In January 2002, she was appointed MSDN Regional Director for Toronto, Canada. Her experience with C++ stretches back to before Visual C++ existed. She is a well-known speaker and lecturer at colleges and Microsoft events on subjects such as .NET, Visual Studio, XML, UML, C++, Java, and the Internet. Kate and her colleagues at Gregory Consulting specialize in combining software develoment with Web site development to create active sites. They build quality custom and off-the-shelf software components for Web pages and other applications. Kate is the author of numerous books for Que, including Special Edition Using Visual C++ .NET.


More by Author

Get the Free Newsletter!

Subscribe to Developer Insider for top news, trends & analysis

Must Read