Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

.NET addins for Visual Studio 6.0

0.00/5 (No votes)
20 Dec 2005 2  
Extend the VS6 IDE with .NET addins.

Contents

Introduction

The title may sound a bit outdated (because Visual Studio 2005 is just released), but at my work we have got some big projects written with Visual Studio 6.0. So there is still a need for me to use it. On the other hand, I'm fascinated by the .NET Framework and its abilities. After writing a few addins in plain C++ dealing with the ugly COM stuff, I really wanted to switch over to .NET. So, I wrote a wrapper addin that wraps the calls from the Visual Studio IDE to a COM registered .NET library. Later on I reworked on this wrapper addin to use mixed mode code. So there is no need for registering the .NET stuff anymore. The .NET library collects all the DLL files in the same directory and looks for a custom attribute to identify a .NET addin and presents them to the IDE. Also the management of the settings for all the addins is done by this library. The main goal of the design was to simplify the extension, by just copying a new addin DLL into the Addin directory, automatically recognized by the IDE on next start up.

The addin system

This picture shows how the addin system works. The netwrapper.dll implements the IDSAddIn interface like a normal VS6 addin. It simply redirects the IDSAddIn calls to the VS6AddIn.dll which is fully written in C#. This module loads on startup all the DLL files in the same directory, checking for the VS6AddInPlugInAttribute and adds each addin to the Visual Studio 6 IDE via the AddCommand of the IApplication interface. (When it's called for the first time, it also calls the AddCommandBarButton member to create the toolbar). The implementation of the ICommand interface in the netwrapper.dll is a bit tricky. The methods of the installed plugins are unknown at compile time. I rewrote the GetIDsOfNames member of the IDispatch interface, by simply numbering the names in ascending order. I also rewrote the Invoke member of IDispatch, it calls the InvokeCommand of the PlugInManager class with the command name as parameter. The PlugInManager class uses the command name to identify the correct addin to be called, and calls its InvokeCommand member.

How to install the addin system

Copy the netwrapper.dll to Visual Studio's Addin directory e.g. "c:\Program Files\ Microsoft Visual Studio\COMMON\MSDev98\AddIns" Create a subdirectory called netmultiaddin inside the addins directory and copy all other DLL files to this subdirectory. Start-up Visual Studio 6.0 and a new toolbar should appear containing all the installed .NET addins. If you add a new addin later on, it doesn't automatically appear if you restart the IDE, you have to add it manually to the toolbar from the customization dialog (or disable the Netwrapper addin and re-enable it after restarting the IDE).

The files

The plugin-framework

  • VS6AddInPlugInDefs.dll
    • Contains the definition of necessary interfaces and the custom attribute used to mark an assembly as .NET addin (IConfigPlugIn, IVS6AddInPlugIn, SetModifiedEventHandler, VS6AddInPlugInAttribute).
  • Netwrapper.dll
    • Holds the COM interfaces needed by the IDE to act as an addin. It redirects calls to this addin to the .NET world.
  • Vs6addin.dll
    • Holds the PlugInManager class and the Options addin.
  • Vs6addinUtils.dll
    • Holds the utility classes to be used by some addins.

Enclosed sample addins

AutoComment.dll Comment out the current selection by a line or block comment.

CStringEvaluator.dll Inspect the contents of a CString variable by selecting a CString variable while in debug break mode. If the CString contains an SQL SELECT statement the result set can be viewed in a data grid.

GetFont.dll Create a LOGFONT declaration, containing the font data selected by common dialog for font selection.

 

IDManager.dll Contains two addins: The Resource ID sorter, which renumbers IDs in the resource.h file, and the Resource ID array builder, which creates an array declaration, containing the resource ID and its name.

MenuBinder.dll Finds and opens the menu handler, a dialog class if appropriate and its dialog resource, of a selected menu entry.

RegexSearcher.dll Provides a full .NET regular expression for a search over directories, project or open files.

 

SimpleTools.dll Contains two plugins: ShowDesign opens the dialog resource of an open dialog implementation or header file. ShowHeader opens the header file of a C++ implementation file and vice versa.

SnippetManager.dll Provides a manager for dragging, dropping and managing code snippets to and from.

TCHandler.dll Wraps the selection in the currently opened file with a predefined try/catch handler.

WinDiffer.dll Calls windiff or a similar program on two text selections.

How to create your own addin

  1. Start Visual Studio 2003
  2. Create a new class library project.
  3. Add a reference to VS6AddInPlugInDefs.dll.
  4. Add a reference to Visual C++ shared objects (6.0) (DSSharedObjects COM component).
  5. (Optional) add a reference to Visual C++ debugger (6.0), Visual C++ project system (6.0) and Visual C++ text editor (6.0).
  6. (Optional) add a reference to VS6AddInUtils.dll.
  7. Create a new class which implements the VS6AddIn.IVS6AddInPlugIn interface.
  8. If your addin needs to be configured by the user, add one or more user controls which implement the VS6AddIn.IConfigPlugIn interface.
  9. Add the VS6AddIn.VS6AddInPlugInAttribute to your assembly file e.g. [assembly: VS6AddIn.VS6AddInPlugInAttribute( typeof( <YourNameSpace>.<YourClassName>),"<YourCommandName>")], this can be done multiple times, if your DLL contains more than one addin.
  10. Add functionality to the InvokeCommand member of the addin.
  11. Build the project

How to implement the IVS6AddInPlugIn interface

Properties

  • string Commanddefs {get;}

    Return "YourCommandName\nButtontext\nStatusbartext\nTooltiptext" for your plugin.

  • string CommandName {get;}

    Return "YourCommandName" here, the same as in the assembly file.

  • int ButtonNr {get;}

    Return the number of the button in the wrappers toolbar bitmap (0 to 15 are the standard bitmaps for your use).

  • int ButtonType {get;}

    Return the type of the button (of kind DSSharedObjects.DsButtonType).

  • Bitmap LargeBitmap {get;}

    Return a 32 by 32 pixel, 16 colour bitmap for the large toolbar (for future use), RGB(192,192,192) is transparent.

  • Bitmap SmallBitmap {get;}

    Return a 16 by 16 pixel, 16 colour bitmap for the small toolbar (for future use) RGB(192,192,192) is transparent.

Methods

  • bool InvokeCommand(DSSharedObjects.Application app);

    Starts the command, app is the application object of the Visual Studio instance, returns true on success otherwise false.

  • System.Windows.Forms.Control[] GetConfigObjects();

    Returns the GUI objects for the configuration of your addin, an array of objects derived from System.Windows.Forms.Control which implement the IConfigPlugIn interface.

  • void ShutDown(bool bLastTime);

    If your addin needs some cleanup, do it here. bLastTime is true if the user is removing (uninstalling) the addin. bLastTime is false if the Visual Studio is exiting.

How to implement the IConfigPlugIn interface

Properties

  • string Caption {get};

    Supply a title for the options page.

Methods

  • void LoadDefault();

    Load default data into the subcontrols.

  • void LoadData();

    Load data (maybe from registry) into the subcontrols.

  • void SaveData();

    Save data (maybe to the registry) from the subcontrols.

  • void About();

    Show the Author information.

Events

  • SetModifyEventHandler SetModifyEvent {get;set;}

    Call this to report user changes to the Options window.

Caveats

Don't forget to call System.Runtime.InteropServices.Marshal.ReleaseComObject() for each used COM object from the IDE, otherwise the IDE will lock up on exit, waiting for those COM objects to be released. I've discovered some strange behaviour when the .NET wrapper for the COM object increments the reference counter. So, if you pass a wrapped COM pointer to a function call, always call the ReleaseComObject before exiting the function. But if you pass the pointer to a constructor of a class and store it in a member variable, don't call ReleaseComObject at the end of the constructor. There maybe some well defined logic, I don't know about that. You can easily check if your addin misses some ReleaseComObject calls. After closing the IDE, bring up the task manager and search for the msdev.exe process, if it's still there with no activity, it waits for the COM pointers to be released, but the framework releases it if the AppDomain is shut down, so this is a deadlock situation.

The following code snippet shows how I handle the COM pointers:

Using DSTextEditor;

public bool InvokeCommand(DSSharedObjects.Application App)
{
    ITextDocument txt = null;
    ITextSelection sel = null;

    try
    {
        txt = App.ActiveDocument as ITextDocument;
        
        if (txt!=null)
        {
            sel = txt.Selection as ITextSelection;
            if (sel!=null && sel.Text!="")
            {
                // Do some interessting stuff here
                return true;
            }
        }
    }
    catch(Exception ex)
    {
        return false;
    }
    finally
    {
        if (txt!=null)
            Marshal.ReleaseComObject(txt);
        if (sel!=null)
            Marshal.ReleaseComObject(sel);
        if (App!=null)                    
            Marshal.ReleaseComObject(App);
    }
}

Rebuild the addin system

In my first attempt I exposed the VS6AddIn.PlugInManager through a wrapper class to COM. In the netwrapper.dll I instantiated this COM object and called its members through COM. This approach needs a registration of the VS6AddIn.DLL with the regasm.exe tool using the /codebase switch because it cannot reside in the same directory as the netwrapper.dll (this confuses the IDE on startup, on trying to register these addin DLLs). But if you use the /codebase switch Microsoft strongly recommends you to sign your assembly. That's the reason why all my DLLs are signed with a strong name. Use the Debug or Release build configuration for this type of addin. Use regasm.exe /codebase vs6addin.dll /tlb:vs6addin.tlb to register the Pluginmanager and regasm.exe /u /codebase vs6addin.dll /tlb:vs6addin.tlb to unregister it.

Later on I decided to remove the ugly COM stuff from the netwrapper.dll using mixed mode code. So, I added a new build configuration named CLR (using the /clr switch for the compiler). _MANAGED is only defined if this configuration is used. This approach is very easy to install, just copy the netwrapper.dll to the IDE's addin directory and copy all the .NET addin DLLs and the dependent DLLs to a subdirectory called netmultiaddin. The CLR configuration is the same as the Release configuration for all projects, except the Netwrapper project.

Before the first build of the whole solution you have to supply your own strong name, generated by the sn.exe tool from the .NET Framework SDK. Generate a keypair file rename it to vs6addin.snk and replace all the vs6addin.snk files in the project with your own file. If you use the mixed mode version of the netwrapper.dll you can also remove all the key files, but don't forget to remove the reference to it in the General Properties of the Buildconfiguration ("Wrapper Assembly Key File") and in the assembly files of each addin ([assembly: AssemblyKeyFile()]).

Basically the projects should build on each Visual Studio .NET (2002, 2003 and 2005), but I have developed it with Visual Studio 2003, so no changes should be necessary to rebuild it with this version of Visual Studio.

Unsolved problems

While this is a computer program, there is still some unfinished stuff to do. After reading Nick Hodapps article (see Links section), I've tried to implement the netwrapper.dll as a MFC-Extension DLL, but that doesn't work with the CLR switch. So, if someone knows how this can be solved, let me know.

Another misbehavior is the handling of the toolbar bitmaps. The MSDEV receives the complete toolbar bitmap through a resource identifier and an instance handle of the DLL containing the bitmap. So this time, the netwrapper.dll contains the fixed toolbar, and the addins bitmaps are ignored, because I don't know how to provide a dynamic bitmap to the IDE. There is already a function in the PlugInManager class (of vs6addin.dll) that compiles a complete toolbar bitmap out of the different addin bitmaps. But the question is, how to present this to the IDE. Also, I don't know how to convert the created bitmap to 4 bits per pixel, but I think this can be solved easily. Another problem occurs on changing the title of the newly created toolbar on first start-up. I have used the trick mentioned by Nick Hodapp (see links section), but this doesn't work for Unicodebuilds. But these unsolved problems don't reduce the functionality, it's just "cosmetic".

Links

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here