Writing an Add-in from Scratch
Listing 6-1 shows the source code for our first add-in, named Basic. You can think of Basic as a wizard-generated add-in with all its clothes removedthe naked add-in that's left is the smallest one possible that still does something useful. And, as you can see from the listing, the smallest possible add-in is small indeed. That's because add-ins have one requirement only: a public class that derives from and implements the Extensibility.IDTExtensibility2 interface. Basic.cs satisfies this requirement by defining a single, public class, named Basic, that derives from IDTExtensibility2 and implements the interface's five methodsOnConnection, OnStartupComplete, OnAddInsUpdate, OnBeginShutdown, and OnDisconnection. There's no Main method because Basic, like all add-ins, is destined to become a DLL. Instead, the OnConnection method serves as the add-in's entry point, and the Basic add-in implements that method by displaying its own name in a message box.
Listing 6-1 The Basic add-in source code
Basic.cs
using System;
using System.Windows.Forms;
using Extensibility;
public class Basic : IDTExtensibility2
{
public void OnConnection(object application,
ext_ConnectMode connectMode,
object addInInst,
ref Array custom)
{
MessageBox.Show("Basic Add-in");
}
public void OnStartupComplete(ref Array custom)
{
}
public void OnAddInsUpdate(ref Array custom)
{
}
public void OnBeginShutdown(ref Array custom)
{
}
public void OnDisconnection(ext_DisconnectMode removeMode,
ref Array custom)
{
}
}
Compiling the Basic Add-in
If you add the source code in Listing 6-1 to a text file named Basic.cs, you can compile the Basic add-in from the command line by using the following command:
csc /t:library /r:"c:\program files\microsoft visual studio .net
2003common7\ide\publicassemblies\extensibility.dll" basic.cs
The /t:library flag directs the C# compiler to create a DLL (Basic.dll) from the source file, and the /r:"c:\program files\microsoft visual studio .net 2003\common7\ide\publicassemblies\extensibility.dll" flag points the compiler to the assembly that contains the Extensibility namespace (Extensibility.dll). The Extensibility namespace defines three types, which all add-ins use: the IDTExtensibility2 interface and the ext_ConnectMode and ext_DisconnectMode enumerations, which define values passed to the OnConnection and OnDisconnection methods, respectively.
Tip
Typing long references at the command line invites both carpal tunnel syndrome and boredom. As an alternative, you can add a reference to the list of default references in the global CSC.rsp file, located at <WinDir>\Microsoft.NET\Framework\<Version>\CSC.rsp. For example, if you add /r:"c:\program files\microsoft visual studio .net 2003\common7\ide\publicassemblies\extensibility.dll" to the global CSC.rsp file, you can compile the Basic add-in with the following command:
csc /t:library basic.cs
Registering the Basic Add-in with COM
At this point, you have an add-in in a file named Basic.dll. To be more precise, you have a managed add-in that is defined in an assembly stored in a file named Basic.dll. Now comes the grand irony of Visual Studio .NET add-ins: Without some help, Visual Studio .NET can't host the managed add-ins that it buildsnot even the ones created by its own Add-in Wizard. The reason is that Visual Studio itself is mostly an unmanaged application. Visual Studio .NET inherited much of its functionality from Visual Studio 6, including its ability to host add-ins. Because Visual Studio 6 dealt with COM add-ins only, Visual Studio .NET won't host any other kind, either.
Fortunately, COM interoperability comes to the rescue. The .NET Framework designers knew from the beginning that the world wasn't about to throw away its enormous investment in COM and start all over with managed code, so they made sure that COM classes and .NET classes would be able to interact seamlessly. All it takes for a .NET class to make itself available to COM is for the .NET class to register itself as a COM class. In particular, managed add-ins require the following registry entries:
HKEY_CLASSES_ROOT\<ProgID>
A key whose name is the ProgID of the class that implements IDTExtensibility2
HKEY_CLASSES_ROOT\<ProgID>\CLSID
A key whose default value is the CLSID of the class that implements IDTExtensibility2
HKEY_CLASSES_ROOT\CLSID\<CLSID>
A key whose name is the CLSID of the class that implements IDTExtensibility2
HKEY_CLASSES_ROOT\CLSID\InprocServer32
A key whose named values identify the class that implements IDTExtensibility2 and the assembly in which that class is defined
Figure 6-1 shows the registry entries for the Basic add-in.
Note
Visual Studio .NET isn't the only beneficiary of COM interoperability. As long as your managed add-in is registered correctly, any environment that can host COM add-ins, such as Visual Basic 6, can host your managed add-in (provided, of course, that the .NET Framework is installed on the same machine).
Figure 6-1. The COM interoperability registry entries for the Basic add-in

Thankfully, there's no need to add these registry entries by hand. They're created automatically when you register your add-in using the .NET Framework Assembly Registry utility (RegAsm). The following command registers the Basic class as a COM class:
regasm /codebase basic.dll
Note
Ignore RegAsm when it complains that the /codebase flag should be used only with strongly named assemblies. The /codebase flag generates the CLSID\InprocServer32\CodeBase named valuewithout this value, the CLR won't be able to locate your add-in's assembly.
RegAsm generates a default ProgID and CLSID for each class it encounters in an assembly. (See the upcoming sidebar "GUIDs and FUIDs" for more information about the default CLSID.) The default ProgID is the same as the class's fully qualified name. For example, the code
namespace Outer
{
namespace Inner
{
public class MyClass ()
{
}
}
}
produces the default ProgID, Outer.Inner.MyClass. Because the Basic class doesn't belong to a namespace, its ProgID is simply Basic.
GUIDs and FUIDsTraditionally, a CLSID is a globally unique identifier (GUID)a 16-byte number that's guaranteed to be unique in time and space. However, close inspection of the default CLSID that RegAsm generates reveals a number that's a GUID in appearance only. In fact, RegAsm always generates the same CLSID when given the same fully qualified class name, regardless of where or when the number is generated. (We'll call this number a FUIDfor-the-most-part unique identifierwhich ranks just below a GUID.) This means that if two different programmers, at two different times, on two different machines, on two different continents, register a class named Basic, their classes will have the same FUID: 992B0D1F-A395-34F5-BFC9-E0A3E9385293. Well, you might not be able to stop others from co-opting your carefully chosen class name, but you don't have to accept the off-the-rack FUID that RegAsm hands out to your class. That's where System.Runtime.InteropServices.GuidAttribute comes in handy. When applied to a class, GuidAttribute overrides the default FUID and lets you assign a GUID of your own choosing. (And there are lots of GUIDs to choose fromjust run the Create GUID utility [GuidGen] to create an honest GUID that your class can call its own.) |
Registering the Basic Add-in with Visual Studio .NET
Basic.dll is a fully functional add-in, but Visual Studio .NET won't know of its existence yet. Add-ins signal their availability to the Visual Studio .NET integrated development environment (IDE) through one of the following registry entries
HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\VisualStudio\7.1\AddIns\<ProgID>
HKEY_CURRENT_USER\Software\Microsoft\VisualStudio\7.1\AddIns\<ProgID>
And they make themselves known to the Macros IDE through one of the corresponding VSA registry entries:
HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\VSA\7.1\AddIns\<ProgID>
HKEY_CURRENT_USER\Software\Microsoft\VSA\7.1\AddIns\<ProgID>
Note
From here on out, we'll refer only to add-ins in the Visual Studio .NET IDE. However, the information in the rest of the chapter applies equally well to add-ins in the Macros IDE.
The Add-in Manager populates its add-ins list from these two AddIns keys. An HKEY_LOCAL_MACHINE entry makes the add-in available to all users on a machine; because modifying the HKEY_LOCAL_MACHINE hive requires administrator privileges, an add-in registered in this way is also known as an administrator add-in. An HKEY_CURRENT_USER entry makes the add-in available to the user who creates the entry and is mirrored in the user settings under HKEY_USERS; not surprisingly, an add-in registered in this way is known as a user add-in. In either case, the name of the key is the ProgID of the class that implements IDTExtensibility2.
To register the Basic add-in for use by all users on a machine, run the Registry Editor utility (RegEdit) and create the following key (assuming that you have administrator privileges):
HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\VisualStudio\7.1AddIns\Basic
After you create this key, run Visual Studio .NET and choose Tools | Add-in Manager to display the dialog box shown in Figure 6-2. Notice that the Add-in Manager lists the add-in's name as Basic. By default, the Add-in Manager displays the ProgID it finds under the AddIns registry key. (In the section titled "Add-in Registry Named Values" later in this chapter, we'll cover the named values for this ProgID registry key that control the display name and other properties of the add-in.)
Figure 6-2. The Add-in Manager dialog box showing the Basic add-in

If you select the check box to the left of Basic and click OK, Visual Studio .NET loads the add-in. Assuming all goes well, Visual Studio .NET will call Basic's OnConnection method and you'll see the message box shown in Figure 6-3.
Figure 6-3. The message box displayed by the Basic add-in

And that's how you create an add-in from scratch. In the next section, we'll examine exactly what happens to an add-in in the Visual Studio .NET environment when the add-in loads, when the add-in unloads, and all the time in between.