Building Smart Tags in Microsoft Visual Basic .NET

This content is no longer actively maintained. It is provided as is, for anyone who may still be using these technologies, with no warranties or claims of accuracy with regard to the most recent product version or service release.

 

J Sawyer
Microsoft Corporation

October 2001

Applies to:
     Microsoft® Office XP
     Microsoft Visual Basic® .NET Beta 2

Summary: This article will show you how to create a simple smart tag with Visual Basic .NET Beta 2. (20 printed pages)

Download Odc_stvbnet.exe.

Contents

Introduction
What Are Smart Tags?
Why Use Visual Basic .NET for Smart Tags?
The Visual Basic .NET Smart Tag
Getting Started
Building the Recognizer
Building the Action
Testing and Debugging the Smart Tag
Self-Registering Smart Tags
Conclusion

Introduction

With the release of Microsoft® Office XP, developers can now create components that recognize custom information, tag this information, and execute actions on them. This isn't anything you haven't seen before; these components are called smart tags and are one of the most innovative features in Office XP.

But smart tags aren't the only things new for developers coming from Microsoft this year. Also in the news is a development environment built on Microsoft's .NET initiative and the Common Language Runtime (CLR). Within Visual Studio .NET is an awesome list of feature updates to the Microsoft Visual Basic language, called Microsoft Visual Basic .NET.

In this article, we will walk through the process of building a smart tag from Visual Basic .NET Beta 2 (some details may change with the final release of Microsoft Visual Basic .NET). Along the way, we'll look at how COM interoperability (COM Interop) works in Visual Basic .NET and point out some of the differences between Visual Basic .NET and Visual Basic 6.0. This article is intended for Visual Basic developers who are somewhat familiar with implementing smart tags, but who are new to Visual Basic .NET.

What Are Smart Tags?

This article assumes that you have some familiarity with developing smart tags already, but I'll take a second to review smart tag development to make sure we're all on the same page.

Smart tags are simple dynamic-link libraries (DLLs) that implement one or two interfaces provided by Office XP: ISmartTagRecognizer and ISmartTagAction. When a smart tag-aware application such as Microsoft Word 2002 or Microsoft Excel 2002 first starts, it fires up all available smart tag recognizers and actions. As the user enters text, the application uses runs the smart tag recognizers on a background thread, periodically passing the recognizer chunks of text to be recognized when there is free processor time. When a user selects a smart-tagged section of text, Word calls into that block's smart tag action to display and act on the various actions available.

The interfaces ISmartTagRecognizer and ISmartTagAction are fairly straightforward once you get the hang of them; in fact, both have only one method. Once you've written a couple, it only takes a few minutes to write a new (albeit very simple) one. If you want more information on developing smart tags, there are several articles available at the Office Developer Center as well as the Microsoft Office XP Smart Tag SDK. The article Developing Smart Tag DLLs also offers a detailed explanation of the smart tag interfaces.

Why Use Visual Basic .NET for Smart Tags?

A good reason for using Visual Basic .NET for smart tags is that it gives you the power to use the rich new paradigms available in Microsoft .NET, particularly XML Web services. One of the first things that occurred to me in writing smart tags was how easy it would be to base a smart tag on an XML Web service. This would give the flexibility to dynamically alter the list of words a recognizer picks, modify the list of actions an action supports, and certainly provide a rich interface to external data through each action tag by allowing the action to call an XML Web service. Additionally, easy support for multithreading allows your actions to be more responsive to user input and not stop the Word application from responding. Take care when using multithreading, however; using multithreading in your Recognize method won't do any good, as recognition is already running on a background thread—not to mention that the Recognize method is synchronous!

The Visual Basic .NET Smart Tag

The Visual Basic .NET smart tag that we will build is a relatively simple smart tag. It will recognize only one piece of text (VB.NET) and display a dialog box. This allows us to worry less about the implementation of the recognizer and the action and focus on the steps needed to create a smart tag in Visual Basic .NET. To do this, we will need to explore COM interoperability with Visual Basic .NET and discuss some of the issues with that. We'll also look at steps needed to debug Visual Basic .NET smart tags from the Visual Studio .NET integrated development environment (IDE).

Once we've explored the basics needed to create smart tags, we'll then look at the some of the new features and capabilities of the .NET Framework and Visual Basic .NET that allow us to create truly self-registering smart tag components, taking us into attributes and reflection in the .NET Framework.

Getting Started

To begin creating our Visual Basic .NET smart tag, we'll need to create a new Visual Basic Class Library project—the equivalent to an ActiveX® DLL project in Visual Basic 6.0—and name the project VBNetSmartTag. Before continuing, we'll need to add a reference to the Microsoft Smart Tags 1.0 Type Library: click Add Reference on the Project menu, and click the COM tab. In the list, double-click Microsoft Smart Tags 1.0 Type Library, and then click OK.

Click here to see larger image

Figure 1. The Add Reference dialog box (click image to see larger picture)

After clicking OK, the following message appears: "Could not find a primary interop assembly for the COM component 'Microsoft Smart Tags 1.0 Type Library'. A primary interop assembly is not registered for this type library. Would you like to have a wrapper generated for you?" Click Yes to allow Visual Basic .NET to create the interop assembly. Once this is done, a new assembly is created for you with the runtime callable wrapper (RCW) that will be used to call these COM objects from the managed Microsoft .NET environment. For more information and details on the import process and alternatives, see Importing a Type Library as an Assembly in the .NET Framework Developer's Guide.

Building the Recognizer

Now that we have a reference to the smart tag type library, we'll begin by building the recognizer component, calling it VBNetRecognizer. To make the smart tag types to be referenced directly without qualification, we'll add the following imports to the top of the Class1.vb file:

Imports SmartTagLib
Imports System.Runtime.InteropServices

This step isn't required, but it simplifies variable and type declaration. Note that simply adding a reference to the type library doesn't accomplish this as it did in Visual Basic 6.0. Instead, the reference simply includes the assembly in the compilation; the namespace (Microsoft .NET's closest equivalent to a type library) must be specifically included at the very top of the source file and must precede any other declarations except Option statements.

Now that we've set up our imports, it's time to declare the class. Unlike Visual Basic 6.0, multiple classes can be included in a single source file, so we need to declare it explicitly. When Visual Basic .NET created our project, it declared a default Class1. We'll simply change Class1 to Recognizer and type Implements ISmartTagRecognizer on the next line to have it implement the recognizer interface. We also will want to add some attributes to the class for use by COM Interop services: ProgID, GuidAttribute, and COMVisible. The ProgID attribute allows you to specify the ProgID that will be used when the class is registered with COM while the GuidAttribute allows you to specify the GUID for the class ID (CLSID). To create a GUID, click Create GUID on the Tools menu, click 4. Registry Format (ie. {xxxxxxx-xxxx ... xxxx}), click Copy, and click Exit to copy the new GUID to the clipboard (you will need to trim the braces from the new GUID). While neither of these attributes is required for interop, they make these properties explicit and, as we will see later, available from Microsoft .NET reflection. The COMVisible attribute can be applied to the assembly in the AssemblyInfo.vb file (to make all public classes visible from COM) or to individual classes (to make specified classes visible or invisible to COM). In our project, we will apply this only to the classes we want registered with COM. The attributes that are associated with a class must also be on the same line of code as the class declaration (this holds true for subs and functions as well), so we will use the line continuation characters to make it more readable. Also, since COM will use our class, we'll need to make sure that we have a default constructor that takes no arguments. By the time we're done, our class looks like this:

Imports SmartTagLib
Imports System.Runtime.InteropServices

<ProgId("VBNetSmartTag.Recognizer"), _
    GuidAttribute("393E8FD0-2780-474b-9760-2EF455BAC790"), _
    ComVisible(True)> _
Public Class Recognizer
    
    Implements ISmartTagRecognizer

    ' Default constructor with no arguments
    ' (required for COM Interop).
    Public Sub New()

    End Sub

End Class

Now that we have the class set up, we need to add the interface members for the ISmartTagRecognizer interface. While we could type them in manually, we'll allow the Visual Basic .NET IDE to create the procedure definitions for us. The process for this is exactly the same as in Visual Basic 6.0; select the interface from the Class Name list on the left side of the code editor, select each procedure from the Method Name list on the right side of the code editor, and all of our procedures are defined for us with the proper syntax and types.

Once this process is complete, we then fill in the blanks on the standard informational properties for the smart tag. For the Desc, we'll use Sample VB.NET Smart Tag Recognizer. For the Name, we'll use VB.NET Recognizer. The ProgID property, as we defined earlier with our attribute, will be VBNetSmartTag.Recognizer and, since we will only recognize one smart tag, named urn:samples-msdn-microsoft-com#VBNetSmartTag, we will return the name from the SmartTagName property and a value of 1 from the SmartTagCount property. Last, but not least, since this is a simple sample, we'll just return an empty string from the SmartTagDownloadURL property. For returning values from the properties, we can use the function or property name (as in Visual Basic 6.0) or the new Return keyword. While some subtle differences exist between the two, for our purposes either will do (for more information on the differences, see the Function statement in the Visual Basic Language Reference). By the time we're done, the body of our recognizer looks like the following:

' Default constructor with no arguments
' (required for COM Interop).
Public Sub New()

End Sub

' A substantive description of what the recognizer does.
Public ReadOnly Property Desc(ByVal LocaleID As Integer) As String _
        Implements SmartTagLib.ISmartTagRecognizer.Desc
    Get
        Return "MSDN Visual Basic.NET Sample Smart Tag"
    End Get
End Property
    
' A short title reflecting what the recognizer does.
Public ReadOnly Property Name(ByVal LocaleID As Integer) As String _
        Implements SmartTagLib.ISmartTagRecognizer.Name
    Get
        Return "MSDN VB.NET Sample Smart Tag"
    End Get
End Property

'The COM ProgID of the smart tag.
Public ReadOnly Property ProgId() As String _
        Implements SmartTagLib.ISmartTagRecognizer.ProgId
    Get
        Return "VBNetSmartTag.Recognizer"
    End Get
End Property

'Recognizes terms.
Public Sub Recognize(ByVal [Text] As String, _
        ByVal DataType As SmartTagLib.IF_TYPE, _
        ByVal LocaleID As Integer, _
        ByVal RecognizerSite As SmartTagLib.ISmartTagRecognizerSite) _
        Implements SmartTagLib.ISmartTagRecognizer.Recognize

End Sub

'Specifies the number of smart tag types that this recognizer supports.  
Public ReadOnly Property SmartTagCount() As Integer _
        Implements SmartTagLib.ISmartTagRecognizer.SmartTagCount
    Get
        Return 1
    End Get
End Property

' The URL that gets embedded in documents to allow users to 
   download actions 
' if they do not already have them installed for that particular 
   smart tag. 
Public ReadOnly Property SmartTagDownloadURL
      (ByVal SmartTagID As Integer) As String _
        Implements SmartTagLib.ISmartTagRecognizer.SmartTagDownloadURL
    Get
        Return ""
    End Get
End Property

' The unique identifiers of smart tag types that the recognizer supports. 
Public ReadOnly Property SmartTagName(ByVal SmartTagID As Integer) As String _
        Implements SmartTagLib.ISmartTagRecognizer.SmartTagName
    Get
        Return "urn:samples-msdn-microsoft-com#VBNetSmartTag"
    End Get
End Property

One significant difference between Visual Basic 6.0 and Visual Basic.NET that is readily apparent is the declaration of implemented methods. In Visual Basic 6.0, this would be a private method and named InterfaceName_MethodName; the smart tag Desc property would be declared Private Property Get ISmartTagRecognizer_Desc(ByVal LocaleID As Long) as String. In Visual Basic .NET, however, the implemented method is specified using the Implements keyword with the method and specifying the interface and member that the function is implementing.

This implemented method allows us to do several things that had been difficult to accomplish. First, we can easily have methods on the class's default interface that are also methods on the implemented interface. In Visual Basic 6.0, you'd either reimplement the procedures or one procedure would be a wrapper for another. For example, when creating smart tags in Visual Basic 6.0, if you want the smart tag properties and methods to be available from the component's default interface (for whatever reason), you would have to reimplement those procedures and then call one set of interface methods from another. In Visual Basic .NET, however, the methods implemented for the smart tag interfaces are also implemented on the default interfaces for the component, simplifying polymorphism and reducing code maintenance issues. Also, the name of the method doesn't have to be the same name as the method on the interface we are implementing, giving us more flexibility. For example, if we wanted our smart tag properties to be accessible from the default interface of our class, but wanted them prefixed with SmartTag (for example, instead of Desc, we'd want it called SmartTagDesc), we could do that easily by renaming the procedure but maintaining the proper function signature. Finally, if we are implementing multiple interfaces with at least some calls in common, we can implement multiple interface methods in one method in the class, which can be particularly useful in developing smart tags. For example, if we wanted to implement our smart tag recognizer interface and our smart tag action interface on the same class, we could share the same method for the Name, Desc, ProgID, SmartTagName, and SmartTagCount properties. To do this, we list multiple members on the different classes, separated by a comma. Our Desc property would then look like the following:

Public ReadOnly Property Desc(ByVal LocaleID As Integer) As String _
        Implements SmartTagLib.ISmartTagRecognizer.Desc, _
        SmartTagLib.ISmartTagAction.Desc
    Get
        Return "MSDN Visual Basic.NET Sample Smart Tag"
    End Get
End Property

Now that we've taken care of the informational methods, it's time to implement the meat of our recognizer, the Recognize method. To help clear the clutter of code on the screen, we'll right-click in the code editor window, point to Outlining, and click Collapse to Definitions. This will hide the bodies of the methods and should only show the method definition line, greatly cleaning up the display. To expand the method bodies selectively, we can either double-click on the + sign in the left margin or double-click the ellipses () after the method definition. We'll expand the Recognize method so we can implement its functionality. Since this is just a simple recognizer, we will recognize the text VB.NET in the document. First, we'll change the name of the argument for the incoming text (defined as [Text]) to something a little easier to type: strRecText. Also, in order to see what was passed into the Recognize method, we'll add the arguments to the smart tag properties so we can read them later from the action component.

To do this, we first use the familiar InStr function to determine if the text we are looking for is in the text to recognizer. In doing this, we will declare the variable and assign it in the same statement—a feature new to Visual Basic .NET. If we find it, we will create a new property bag and then add the passed arguments to the property bag. Another difference that you may notice is that Visual Basic .NET requires parentheses around all arguments, whether it is a function or a subroutine. Once we're done with this, we'll commit the smart tag.

Public Sub Recognize(ByVal strRecText As String, _
        ByVal DataType As SmartTagLib.IF_TYPE, _
        ByVal LocaleID As Integer, _
        ByVal RecognizerSite As SmartTagLib.ISmartTagRecognizerSite) _
        Implements SmartTagLib.ISmartTagRecognizer.Recognize
    Dim intLocation As Integer = InStr(strRecText, "VB.NET", 
               CompareMethod.Text)
    If intLocation > 0 Then
        Dim stPropBag As ISmartTagProperties = RecognizerSite.GetNewPropertyBag()
        Dim strPropType As String
        ' Determine the data type (as a string). 
        Select Case DataType
            Case IF_TYPE.IF_TYPE_CELL
                strPropType = "IF_TYPE_CELL"
            Case IF_TYPE.IF_TYPE_CHAR
                strPropType = "IF_TYPE_CHAR"
            Case IF_TYPE.IF_TYPE_PARA
                strPropType = "IF_TYPE_PARA"
            Case IF_TYPE.IF_TYPE_REGEXP
                strPropType = "IF_TYPE_REGEXP"
            Case IF_TYPE.IF_TYPE_SINGLE_WD
                strPropType = "IF_TYPE_SINGLE_WD"
        End Select
        'Add the data type to the property bag.  
        stPropBag.Write("DataType", strPropType)
        'Add the text sent to the function. 
        stPropBag.Write("Text", strRecText)
        'Add the LocaleID.
        stPropBag.Write("LocaleID", LocaleID)
        'Commit the SmartTag. 
        RecognizerSite.CommitSmartTag("urn:samples-msdn-microsoft-
            com#VBNetSmartTag", _
            intLocation, 6, stPropBag)
    End If
End Sub

Building the Action

Like our recognizer, the action that we will implement will be simple, showing only the process of creating a smart tag in Visual Basic .NET. Our action will simply display a message box that shows the text recognized and the contents of the property bag that was passed from the recognizer. While we could create the class in the same file as the recognizer (Visual Basic .NET now supports multiple classes in a single file), we will opt to create a new file for the action and name it Action.vb*.* To add the new file to the project, right-click on the project in the Solution Explorer window, select Add, and click Add Class. As with the recognizer, we'll start by adding the Imports statements to the top of the file and Implements ISmartTagAction in the line after the class declaration. Once this is done, we'll let Visual Basic .NET fill in the interface methods for us as we did with the recognizer component. Since this is a simple action, we won't spend a lot of time on the informational methods and properties (everything except the InvokeVerb method) of the smart tag action interface; we'll just fill them quickly with some default return values. Remember, recognizers and actions both have only one method; the rest of the interfaces are simple properties. When this is done, our action looks like this:

Imports SmartTagLib
Imports System.Runtime.InteropServices

<ProgId("VBNetSmartTag.Action"), _
    GuidAttribute("A7F42545-36C3-49b7-81EB-7A47B0A779ED"), _
    ComVisible(True)> _
Public Class Action
    Implements ISmartTagAction

    Public ReadOnly Property Desc(ByVal LocaleID As Integer) As String _
            Implements SmartTagLib.ISmartTagAction.Desc
        Get
            Return "VB.NET Smart Tag Action Component"
        End Get
    End Property

    Public Sub InvokeVerb(ByVal VerbID As Integer, _
            ByVal ApplicationName As String, _
            ByVal Target As Object, _
            ByVal Properties As SmartTagLib.ISmartTagProperties, _
            ByVal [Text] As String, _
            ByVal Xml As String) _
            Implements SmartTagLib.ISmartTagAction.InvokeVerb

    End Sub

    Public ReadOnly Property Name(ByVal LocaleID As Integer) As String _
            Implements SmartTagLib.ISmartTagAction.Name
        Get
            Return "VB.NET Action"
        End Get
    End Property

    Public ReadOnly Property ProgId() As String _
            Implements SmartTagLib.ISmartTagAction.ProgId
        Get
            Return "VBNetSmartTag.Action"
        End Get
    End Property

    Public ReadOnly Property SmartTagCaption(ByVal SmartTagID As Integer, _
            ByVal LocaleID As Integer) As String _
            Implements SmartTagLib.ISmartTagAction.SmartTagCaption
        Get
            Return "VB.NET Smart Tag"
        End Get
    End Property

    Public ReadOnly Property SmartTagCount() As Integer _
            Implements SmartTagLib.ISmartTagAction.SmartTagCount
        Get
            Return 1
        End Get
    End Property

    Public ReadOnly Property SmartTagName(ByVal SmartTagID As Integer) 
               As String _
            Implements SmartTagLib.ISmartTagAction.SmartTagName
        Get
            Return "urn:samples-msdn-microsoft-com#VBNetSmartTag"
        End Get
    End Property

    Public ReadOnly Property VerbCaptionFromID(ByVal VerbID As Integer, _
            ByVal ApplicationName As String, _
            ByVal LocaleID As Integer) As String _
            Implements SmartTagLib.ISmartTagAction.VerbCaptionFromID
        Get
            Return "Show VB.NET Smart Tag"
        End Get
    End Property

    Public ReadOnly Property VerbCount(ByVal SmartTagName As String) As Integer _
            Implements SmartTagLib.ISmartTagAction.VerbCount
        Get
            Return 1
        End Get
    End Property

    Public ReadOnly Property VerbID(ByVal SmartTagName As String, _
            ByVal VerbIndex As Integer) As Integer _
            Implements SmartTagLib.ISmartTagAction.VerbID
        Get
            Return 1
        End Get
    End Property

    Public ReadOnly Property VerbNameFromID(ByVal VerbID As Integer) 
                  As String _
            Implements SmartTagLib.ISmartTagAction.VerbNameFromID
        Get
            Return "ShowTag"
        End Get
    End Property
End Class

There isn't anything here that we haven't seen already with the recognizer interface. The only thing that is left for us to do is to implement the InvokeVerb method. As with the recognizer, we'll change the input argument for [Text] to strText, simply because it's easier to type. As we've done before, we'll also declare the variable for the final message and assign it on the same line. We'll then append all of the properties that were added to the smart tag property bag in our recognizer.

    Public Sub InvokeVerb(ByVal VerbID As Integer, _
            ByVal ApplicationName As String, _
            ByVal Target As Object, _
            ByVal Properties As SmartTagLib.ISmartTagProperties, _
            ByVal strText As String, _
            ByVal Xml As String) _
            Implements SmartTagLib.ISmartTagAction.InvokeVerb
        Dim strMsg As String = strText
        Dim i As Short
        For i = 0 To Properties.Count - 1
            strMsg &= vbCrLf & Properties.KeyFromIndex(i) & _
                "=" & Properties.ValueFromIndex(i)
        Next i
        MsgBox(strMsg, , "VB.NET Smart Tag Action")
    End Sub

Testing and Debugging the Smart Tag

Now that we've implemented both interfaces, we're ready to start testing our Microsoft .NET smart tag. For now, we'll need to register our smart tags with the smart tag infrastructure manually using regedit.exe. Since we're specifying the GUID that will be used for COM Interop registration, we'll copy the appropriate GUID from our classes and enter the appropriate registry keys under HKEY_CURRENT_USER\Software\Microsoft\Office\Common\Smart Tag.

Note   Modifying the Microsoft Windows registry in any manner, whether through the Registry Editor or programmatically, always carries some degree of risk. Incorrect modification can cause serious problems that may require you to reinstall your operating system. It is a good practice to always back up a computer's registry first before modifying it. If you are running Microsoft Windows NT or Microsoft Windows 2000, you should also update your Emergency Repair Disk (ERD). For information about how to edit the registry, view the "Changing Keys and Values" Help topic in the Registry Editor (Regedit.exe) or the "Add and Delete Information in the Registry" and "Edit Registry Information" topics in the Registry Editor (Regedt32.exe).

Unlike Visual Basic 6.0 and earlier, however, we can't simply start the project and wait for the components to be created. Instead, we need to tell Visual Studio .NET which application to start for the debug session (Word) and then start the debug session. We'll also need to specify that the assembly gets registered for COM Interop when it gets compiled. To do this, we'll right-click on the project in the Solution Explorer window and click Properties. To set the debugging settings, expand the Configuration Properties folder and click Debugging. In the Start Action area, click Start External Program and type the path to Microsoft Word 2002 (while the new version of Office is named "XP", its individual components are named "2002" and referred to internally as "Office10").

Click here to see larger image

Figure 2. The Solution Property Pages dialog box: Debugging options (click image to see larger picture)

Next, we need to change the build settings to automatically register the assembly with COM Interop on compilation. To do this, click Build under the Configuration Properties folder and check Register for COM Interop. When you click Apply, Visual Studio .NET will display the following message: "The assembly must be strongly named if it is to be registered for COM interop. Do you want to give the assembly a strong name now?" Clicking OK will generate a key for the assembly and give it a strong name; this is required for COM-visible assemblies. For more information on strong names and key files, see Creating and Using Strong-Named Assemblies in the MSDN Library.

Click here to see larger image

Figure 3. The Solution Property Pages dialog box: Build options (click image to see larger picture)

Once this is complete, we can set our breakpoints and start the project.

Note   To comply with Office XP security, managed COM add-ins (COM add-ins targeting the common language runtime) must be digitally signed, and users' security settings should be set to their highest levels. Additionally, you will need to incorporate into your managed COM add-in project a small unmanaged proxy called a shim. For details, see Deployment of Managed COM Add-Ins in Office XP.

Remember, though, to completely shut down Word and Outlook before starting the debugging session. Test the smart tag recognizer by typing VB.NET in Word and clicking on the Smart Tag Actions button.

Click here to see larger image

Figure 4. Recognizing the smart tag and showing the Smart Tag Actions button (click image to see larger picture)

When you click on the smart tag action, you should get a message box similar to the following:

Aa163620.odc_stvbnet05(en-us,office.10).gif

Figure 5. The VB.NET Smart Tag Action dialog box

Self-Registering Smart Tags

Now that our smart tag is working, we've got just one more thing to do: use reflection to register the smart tag with the smart tag infrastructure at the time that the smart tag is registered with COM. This eliminates the need to ship a separate .reg file or to add code or logic in the distribution package to add the registry keys.

Remember, smart tags require a registry entry for Office to "see" them, above and beyond the normal registry key you'd expect any well-dressed DLL to have. The specific keys are:

  • HKEY_CURRENT_USER\Software\Microsoft\Office\Common\Smart Tag\Actions
  • HKEY_CURRENT_USER\Software\Microsoft\Office\Common\Smart Tag\Recognizers

Registering a smart tag involves simply creating a key under either or both of these keys and giving it the CLSID or progID of your smart tag (using the CLSID is preferable).

For Microsoft Visual C++® developers with full access to their implementation of the DLLRegisterServer and DLLUnregisterServer functions, this has never been a problem. For Visual Basic developers, however, this has been impossible until now. The .NET Framework allows the developer to specify static (in Visual Basic .NET, Shared) functions that are called by regasm.exe (Microsoft .NET's version of regsvr32.exe) when the assembly is registered and unregistered with COM Interop. This function must be on a public class, must have the ComRegisterFunction attribute or ComUnregisterFunction attribute and must take a single string argument, which will have the name of the registry key that is being added for the class's CLSID. To implement this, we'll add two functions to the Recognizer class (this is arbitrary; it can be added to any public class) and call them Register**and Unregister. Because the code for these functions will be similar, we'll implement it in a Visual Basic module and call a shared function from our Register and Unregister methods, with an argument indicating if it is a registration or an unregistration as well as the registry key that is passed as the argument by regasm.exe.

<ComRegisterFunction()> _
Shared Sub Register(ByVal RegKey As String)
    RegSmartTag(RegKey, True)
End Sub

<ComUnregisterFunction()> _
Shared Sub Unregister(ByVal RegKey As String)
    RegSmartTag(RegKey, False)
End Sub

There isn't much to this code. The interesting code is in the implementation of RegSmartTag. This will use the .NET Framework's reflection API to examine the classes of the assembly, determine which smart tag interfaces each class implements, read the GuidAttribute attribute that we associated with those classes, and then add or remove the proper registry entries, thus allowing us to have fully self-registering smart tags. Conceptually, reflection is similar to using the IDispatch interface's functions (accessible only through C/C++) or the TypeLib Information COM component in Visual Basic 6.0, but significantly more powerful since reflection not only provides more information on classes in an assembly than a type library does, but can also be extended and enhanced through the use of standard and custom attributes.

The first thing we'll do is to open the smart tag registry keys. Next, we need to get the currently executing assembly and get its public types. Since the public types could be anything (enumerations, classes, interfaces, and so on), we need to make sure that the type is a class. If it is, we then need to get the interfaces supported by the class and loop over all supported interfaces to find the smart tag interfaces. As we find each interface, we then need to add the information to the registry. Since we defined the GUID for the class using an attribute, we can use reflection to get the CLSID by calling Attribute.GetCustomAttribute and passing it the specific attribute type that we want to get. Since this returns a generic Attribute object (the base class for all attributes), we'll also need to explicitly convert it to a GuidAttribute class. Then we'll read the value of the attribute and then add or remove the registry key as necessary. So that the smart tags that we've registered in this code are easily recognizable, we will also add some additional values to the registry key for the smart tag with information regarding the assembly. Throughout the code, we'll also write to the console to show the status of the registration. While we won't see this when we compile, we will be able to see the output if we manually register or unregister the smart tag component using regasm.exe from the Visual Studio.NET Command Prompt.

Imports Microsoft.Win32
Imports System.Runtime.InteropServices
Imports System.Reflection
Imports System
Module FileRegistration

    Private fReg As Boolean = False   ' 
               <-- Shared variable so this gets called only once.  
    Public Sub RegSmartTag(ByVal RegKey As String, ByVal Register As Boolean)
        If Not fReg Then
            OutputMsg("*****  Beginning smart tag registration. *****")
            OutputMsg(vbTab & "Register=" & Register.ToString())
            OutputMsg("")

            ' Main smart tags registry key. 
            Dim rkeySmartTags As RegistryKey _
                = Registry.CurrentUser.OpenSubKey _
                ("Software\Microsoft\Office\Common\Smart Tag", True)
            ' Actions subkey.
            Dim rkeyActions As RegistryKey _
                = rkeySmartTags.OpenSubKey("Actions", True)
            ' Recognizers subkey. 
            Dim rkeyRecognizers As RegistryKey _
                = rkeySmartTags.OpenSubKey("Recognizers", True)

            ' Get the current assembly. 
            Dim reflAssembly As [Assembly] = 
               [Assembly].GetExecutingAssembly()
            ' Get all public types for the assembly. 
            Dim reflTypes As Type() = reflAssembly.GetExportedTypes()
            Dim reflType As Type
            For Each reflType In reflTypes    
                  ' <-- Looping over the exported types. 
                ' Get the interfaces to look for the smart tag interfaces.
                If reflType.IsClass Then  ' Make sure that it's a class.  
                    Dim reflInterfaces As Type() = 
                  reflType.GetInterfaces()
                    Dim reflInterface As Type
                    For Each reflInterface In reflInterfaces
                        Select Case reflInterface.Name
                            Case "ISmartTagAction"  ' Smart tag 
                     action interface. 
                                HandleReg(reflType, rkeyActions, Register)
                            Case "ISmartTagRecognizer" 
                  'Smart tag recognizer interface. 
                                HandleReg(reflType, rkeyRecognizers, 
                     Register)
                        End Select
                    Next reflInterface
                End If
            Next reflType

            'Done.  Now clean up.  
            reflTypes = Nothing
            reflAssembly = Nothing
            rkeyActions.Close()
            rkeyActions = Nothing
            rkeyRecognizers.Close()
            rkeyRecognizers = Nothing
            rkeySmartTags.Close()
            rkeySmartTags = Nothing

            fReg = True ' <-- Set our shared variable to True. 
            OutputMsg("")
            OutputMsg("*****  Completed smart tag registration. *****")
            OutputMsg("")
        End If
    End Sub
    
    Shared Sub HandleReg(ByVal sysType As Type, ByVal RegKey As RegistryKey, _
            ByVal Register As Boolean)
        ' Code to actually do the registration of the smart tag.  
        ' INPUTS:
        '   sysType: Type for the class that's getting registered.  
        '   RegKey: Registry key to register the class in.  
        '       Should be the actions or recognizers key.  
        '   Register: True to register, False to unregister.  

        ' Get the type of the GuidAttribute class.  
        Dim typGUIDAttr As Type = GetType(GuidAttribute)

        ' Check to see if the GuidAttribute attribute is defined on the class.  
        If Attribute.IsDefined(sysType, typGUIDAttr) Then
            Dim attrValue As GuidAttribute
            ' Get the GuidAttribute attribute through reflection.  
            attrValue = CType(Attribute.GetCustomAttribute(sysType, 
                     typGUIDAttr), GuidAttribute)
            ' Get the string representation of the GUID.  
            Dim strGuidVal As String = "{" & attrValue.Value & "}"
            If Register Then
                Try
                    Dim newKey As RegistryKey _
                        = RegKey.CreateSubKey(strGuidVal)
                    newKey.SetValue("Assembly", sysType.Assembly.FullName)
                    newKey.SetValue("Type", sysType.FullName)
                    OutputMsg(sysType.Name & " registered with 
                        smart tags successfully")
                Catch
                    OutputMsg("Failed to register " & 
                  sysType.Name & " with smart tags")
                    OutputMsg(Err.GetType.Name & ":" & Err.Description)
                End Try
            Else
                Try
                    RegKey.DeleteSubKey(strGuidVal, False)
                    OutputMsg(sysType.Name & " unregistered with smart 
                           tags successfully")
                Catch
                    OutputMsg("Failed to unregister " 
                  & sysType.Name & " with smart tags")
                    OutputMsg(Err.GetType.Name & ":" & Err.Description)
                End Try
            End If
        Else
            'If we don't find the GuidAttribute attribute, 
                     write to the system console.  
            OutputMsg("Could not register " & sysType.Name & " 
            as smart tag.")
            OutputMsg("GUID attribute not found on class.")
        End If
    End Sub
    Private Sub OutputMsg(ByVal Msg As String)
        ' Use DEBUG conditional compile constant to only output
        ' when compiling the DEBUG version.  
        #If DEBUG Then
            System.Console.WriteLine(Msg)
        #End If
    End Sub
End Module

Conclusion

Once you've understood the basics of COM Interop in Microsoft .NET, creating smart tags in Visual Basic .NET isn't extremely difficult. It is, however, somewhat different from Visual Basic 6.0. This allows developers to leverage the power of Visual Basic .NET and the .NET Framework to create new and exciting smart tags. These smart tags could take advantage of powerful feature such as multi-threading and integrated support for Microsoft .NET XML Web services. Also, the .NET Framework allows us to do create truly self-registering smart tags that don't require any additional steps beyond COM registration.