Microsoft KB Archive/292596

= How To Create a Smart Tag DLL in ATL For Use in Office XP =

Article ID: 292596

Article Last Modified on 3/29/2007

-

APPLIES TO


 * Microsoft Excel 2002 Standard Edition
 * Microsoft Word 2002 Standard Edition
 * Microsoft Visual C++ 6.0 Professional Edition

-



This article was previously published under Q292596





IN THIS TASK
SUMMARY
 * Steps to Create the Smart Tag DLL in ATL
 * Steps to Register the Smart Tag DLL
 * Steps to Test the Smart Tag DLL
 * Troubleshooting

REFERENCES



SUMMARY
Smart Tags are a technology introduced with Office XP that provide Office users more interactivity with the content of their Office documents. A Smart Tag is an element of text in an Office document that is recognized as having custom actions associated with it. An example of one of these special elements of text might be an e-mail name that is typed into a Word document or an Excel workbook. If the e-mail name is recognized as a Smart Tag, the user is presented with one or more actions to perform on that text. Possible actions that are associated with an e-mail name are to look up additional contact information or to send a new e-mail message to that contact.

You can extend the capabilities of Office XP by developing your own Smart Tag Recognizer/Action dynamic-link library (DLL) for use in Office documents. This article describes how to build a Smart Tag DLL by using Active Template Library (ATL), and discusses what registry settings are required for Office XP to identify and use your Smart Tag DLL.

NOTE: Excel 2002 and Word 2002 are the only Office XP applications that support Smart Tags. However, the information that is presented in this article can be applied to Smart Tag development for any application that adopts the Smart Tag technology. A Smart Tag DLL is a standard Component Object Model (COM) DLL that implements two special interfaces: ISmartTagRecognizer and ISmartTagAction. The ISmartTagRecognizer interface recognizes text that is typed into a document as a Smart Tag. The ISmartTagAction interface performs actions on a particular Smart Tag string at the user's request. It is not required that these interfaces be implemented in the same DLL. You can have a recognizer DLL, and then one or more action DLLs that extend a single Smart Tag type for different actions.

back to the top

Steps to Create the Smart Tag DLL in ATL
The following steps create a simple Smart Tag DLL that recognizes the Microsoft Network (MSN) Instant Messenger contacts and gives the user the ability to send e-mail or instant messages to a recognized contact. Instant Messenger is required to use this sample. If you do not have Instant Messenger, you can obtain a copy from the following MSN Web site:

http://messenger.msn.com/

 In Visual C++, create a new ATL COM AppWizard project. Name the project MessengerSmartTag. Click OK to start the ATL COM wizard. In the next dialog box, make sure that Dynamic Link Library is selected and click Finish. Click OK to create the project. To create the Recognizer class, on the Insert menu, click New ATL Object. Select Simple Object and click Next. For the short name, type Recognizer and click OK. To create the Action class, follow the instructions in step 3, but type Action for the short name. Open the ClassView and expand MessengerSmartTag classes. Right-click the CRecognizer class and select Implement Interface. Click OK when the warning dialog box appears. Click Browse and select Microsoft Smart Tags 1.0 Type Library. Select the ISmartTagRecognizer and the ISmartTagRecognizer2 interface and press OK.

NOTE: The default location for the Microsoft Smart Tags 1.0 Type Library is C:\Program Files\Common Files\Microsoft Shared\Mstag.tlb. In ClassView, right-click the CAction class and select Implement Interface. Click OK when the warning dialog box appears. Click Browse and select Microsoft Smart Tags 1.0 Type Library. Select the ISmartTagAction and the ISmartTagAction2 interface and click OK.</li>  Open the Recognizer.h file and replace the class contents with the following code: ///////////////////////////////////////////////////////////////////////////// // CRecognizer class ATL_NO_VTABLE CRecognizer : public CComObjectRootEx<CComSingleThreadModel>, public CComCoClass<CRecognizer, &CLSID_Recognizer>, public IDispatchImpl<IRecognizer, &IID_IRecognizer, &LIBID_MESSENGERSMARTTAGLib>, public IDispatchImpl<ISmartTagRecognizer, &IID_ISmartTagRecognizer, &LIBID_SmartTagLib>, public IDispatchImpl<ISmartTagRecognizer2, &IID_ISmartTagRecognizer2, &LIBID_SmartTagLib> { public:
 * 1) include &quot;Resource.h&quot;
 * 2) import &quot;C:\Program Files\Common Files\Microsoft Shared\Smart Tag\MSTAG.TLB&quot; raw_interfaces_only, raw_native_types, no_namespace, named_guids

CRecognizer; ~CRecognizer;

DECLARE_REGISTRY_RESOURCEID(IDR_RECOGNIZER)

DECLARE_PROTECT_FINAL_CONSTRUCT

BEGIN_COM_MAP(CRecognizer) COM_INTERFACE_ENTRY(IRecognizer) //Removed -- COM_INTERFACE_ENTRY(IDispatch) COM_INTERFACE_ENTRY2(IDispatch, IRecognizer) COM_INTERFACE_ENTRY(ISmartTagRecognizer) COM_INTERFACE_ENTRY(ISmartTagRecognizer2) END_COM_MAP

// IRecognizer public: // ISmartTagRecognizer STDMETHOD(get_ProgId)(BSTR * ProgId); STDMETHOD(get_Name)(INT LocaleID, BSTR * Name); STDMETHOD(get_Desc)(INT LocaleID, BSTR * Desc); STDMETHOD(get_SmartTagCount)(INT * Count); STDMETHOD(get_SmartTagName)(INT SmartTagID, BSTR * Name); STDMETHOD(get_SmartTagDownloadURL)(INT SmartTagID, BSTR * DownloadURL); STDMETHOD(Recognize)(BSTR Text, IF_TYPE DataType, INT LocaleID,                 ISmartTagRecognizerSite * RecognizerSite); private: long lCount; SAFEARRAY *psa; // ISmartTagRecognizer2 STDMETHOD(Recognize2)(BSTR Text, IF_TYPE DataType, INT LocaleID, ISmartTagRecognizerSite2 * RecognizerSite2, BSTR ApplicationName, ISmartTagTokenList * TokenList) {       return E_NOTIMPL; }   STDMETHOD(get_PropertyPage)(INT SmartTagID, INT LocaleID, VARIANT_BOOL * HasPropPage) {       if (HasPropPage == NULL) return E_POINTER; return E_NOTIMPL; }   STDMETHOD(DisplayPropertyPage)(INT SmartTagID, INT LocaleID) {       return E_NOTIMPL; }   STDMETHOD(SmartTagInitialize)(BSTR ApplicationName) {       return E_NOTIMPL; } };                   </li>  Open the Recognizer.cpp file and add the following code to end of the file: CRecognizer::CRecognizer {   Messenger::IMsgrObject2Ptr oMsgrObj = NULL; Messenger::IMsgrUsersPtr oUsers = NULL; Messenger::IMsgrUserPtr oUser = NULL; SAFEARRAYBOUND rgsaBound[1]; long rgIndices[1]; HRESULT hr; // Create an instance of Instant Messenger. oMsgrObj.CreateInstance(&quot;Messenger.MsgrObject&quot;); // Get the list of contacts oUsers = oMsgrObj->GetList(Messenger::MLIST_CONTACT); // Store the number of contacts you have. lCount = oUsers->GetCount; rgsaBound[0].lLbound = 0; rgsaBound[0].cElements = lCount; // Create a SAFEARRAY to hold the list of contacts. psa = SafeArrayCreate(VT_VARIANT, 1, rgsaBound); // Loop through all contacts. for (long l=0; l<lCount-1; l++) {       rgIndices[0] = l;           // Set the specific user. oUser = oUsers->Item(l); // Convert the Friendly Name to lower case // and store it in a VARIANT. _variant_t v = _wcslwr(oUser->GetFriendlyName); // Put the VARIANT into the SAFEARRAY. hr = SafeArrayPutElement(psa, rgIndices, &v); } }

CRecognizer::~CRecognizer {   // Destroy the SAFEARRAY. SafeArrayDestroy(psa); }

HRESULT CRecognizer::get_ProgId(BSTR * ProgId) {   // Set the ProgID of the Recognizer interface. *ProgId = SysAllocString(L&quot;MessengerSmartTag.Recognizer&quot;); return S_OK; }

HRESULT CRecognizer::get_Name(INT LocaleID, BSTR * Name) {   // Set a short title about the recognizer. *Name = SysAllocString(L&quot;Microsoft Messenger Contacts Visual C++ Recognizer&quot;); return S_OK; }

HRESULT CRecognizer::get_Desc(INT LocaleID, BSTR * Desc) {   // Set a long description of the recognizer. *Desc = SysAllocString(L&quot;Microsoft Messenger recognizes your Instant Messenger Contacts&quot;); return S_OK; }

HRESULT CRecognizer::get_SmartTagCount(INT * Count) {   // Set the number of Smart Tags that are supported. *Count = 1; return S_OK; }

HRESULT CRecognizer::get_SmartTagName(INT SmartTagID, BSTR * Name) {   // This method is called the same number of times as you // return in SmartTagCount. This method sets a unique name // for the Smart Tag. *Name = SysAllocString(L&quot;microsoft/messenger#contacts&quot;); return S_OK; }

HRESULT CRecognizer::get_SmartTagDownloadURL(INT SmartTagID, BSTR * DownloadURL) {   // Set the URL that gets embedded in documents. *DownloadURL = NULL; return S_OK; }

HRESULT CRecognizer::Recognize(BSTR Text, IF_TYPE DataType, INT LocaleID,      ISmartTagRecognizerSite * RecognizerSite) {   // The Recognize method is called and passed a text value. // You should recognize strings in the text and set up the actions. WCHAR *pch, *strText = _wcslwr(Text); ISmartTagProperties *pSmartTagProp = NULL; long rgIndices[1]; HRESULT hr;

// Look through all contacts for (long l = 0; l<lCount; l++) {       rgIndices[0] = l;

// Get the contact name. _variant_t v;       hr = SafeArrayGetElement(psa,rgIndices,&v); // Convert the VARIANT to a BSTR. _bstr_t bstrContact = v;       // Loop through the string looking for contacts. for (pch = strText; (pch = wcsstr(pch, bstrContact))!=NULL; pch++) {               // Create a new property bag. hr = RecognizerSite->GetNewPropertyBag(&pSmartTagProp); if (SUCCEEDED(hr)) { // Commit the Smart Tag to the property bag. hr = RecognizerSite->CommitSmartTag(                     _bstr_t(&quot;microsoft/messenger#contacts&quot;),                      pch - strText+1, wcslen(bstrContact),                      pSmartTagProp); if (pSmartTagProp != NULL) pSmartTagProp->Release; }       }    }    return S_OK; }                   </li>  Open the Action.h file and replace the contents of the class with the following code: ///////////////////////////////////////////////////////////////////////////// // CAction class ATL_NO_VTABLE CAction : public CComObjectRootEx<CComSingleThreadModel>, public CComCoClass<CAction, &CLSID_Action>, public IDispatchImpl<IAction, &IID_IAction, &LIBID_MESSENGERSMARTTAGLib>, public IDispatchImpl<ISmartTagAction, &IID_ISmartTagAction, &LIBID_SmartTagLib>, public IDispatchImpl<ISmartTagAction2, &IID_ISmartTagAction2, &LIBID_SmartTagLib> { public: CAction{}
 * 1) include &quot;Resource.h&quot;
 * 2) import &quot;C:\Program Files\Common Files\Microsoft Shared\Smart Tag\MSTAG.TLB&quot; raw_interfaces_only, raw_native_types, no_namespace, named_guids

DECLARE_REGISTRY_RESOURCEID(IDR_ACTION)

DECLARE_PROTECT_FINAL_CONSTRUCT

BEGIN_COM_MAP(CAction) COM_INTERFACE_ENTRY(IAction) //Removed -- COM_INTERFACE_ENTRY(IDispatch) COM_INTERFACE_ENTRY2(IDispatch, IAction) COM_INTERFACE_ENTRY(ISmartTagAction) COM_INTERFACE_ENTRY(ISmartTagAction2) END_COM_MAP

// IAction public: // ISmartTagAction STDMETHOD(get_ProgId)(BSTR * ProgId); STDMETHOD(get_Name)(INT LocaleID, BSTR * Name); STDMETHOD(get_Desc)(INT LocaleID, BSTR * Desc); STDMETHOD(get_SmartTagCount)(INT * Count); STDMETHOD(get_SmartTagName)(INT SmartTagID, BSTR * Name); STDMETHOD(get_SmartTagCaption)(INT SmartTagID, INT LocaleID,                 BSTR * Caption); STDMETHOD(get_VerbCount)(BSTR SmartTagName, INT * Count); STDMETHOD(get_VerbID)(BSTR SmartTagName, INT VerbIndex, INT * VerbID); STDMETHOD(get_VerbCaptionFromID)(INT VerbID, BSTR ApplicationName,                 INT LocaleID, BSTR * Caption); STDMETHOD(get_VerbNameFromID)(INT VerbID, BSTR * Name); STDMETHOD(InvokeVerb)(INT VerbID, BSTR ApplicationName,                 IDispatch * Target, ISmartTagProperties * Properties,                  BSTR Text, BSTR Xml); // ISmartTagAction2 STDMETHOD(get_VerbCaptionFromID2)(INT VerbID, BSTR ApplicationName, INT LocaleID, ISmartTagProperties * Properties, BSTR Text, BSTR Xml, IDispatch * Target, BSTR * Caption) {       if (Caption == NULL) return E_POINTER;

return E_NOTIMPL; }   STDMETHOD(InvokeVerb2)(INT VerbID, BSTR ApplicationName, IDispatch * Target, ISmartTagProperties * Properties, BSTR Text, BSTR Xml, INT LocaleID) {       return E_NOTIMPL; }   STDMETHOD(get_IsCaptionDynamic)(INT VerbID, BSTR ApplicationName, INT LocaleID, VARIANT_BOOL * Dynamic) {       if (Dynamic == NULL) return E_POINTER;

return E_NOTIMPL; }   STDMETHOD(get_ShowSmartTagIndicator)(INT VerbID, BSTR ApplicationName, INT LocaleID, VARIANT_BOOL * Visible) {       if (Visible == NULL) return E_POINTER;

return E_NOTIMPL; }   STDMETHOD(SmartTagInitialize)(BSTR ApplicationName) {       return E_NOTIMPL; } };                   </li>  Open the Action.cpp file and add the following code to the end of the file: HRESULT CAction::get_ProgId(BSTR * ProgId) {  // Set the ProgID of the Action interface. *ProgId = SysAllocString(L&quot;MessengerSmartTag.Action&quot;); return S_OK; }

HRESULT CAction::get_Name(INT LocaleID, BSTR * Name) {  // Set a short name describing the Action. *Name = SysAllocString(L&quot;Messenger Smart Tag&quot;); return S_OK; }

HRESULT CAction::get_Desc(INT LocaleID, BSTR * Desc) {  // Set a long description describing the action. *Desc = SysAllocString(L&quot;Provides actions for the Messenger Smart Tag&quot;); return S_OK; }

HRESULT CAction::get_SmartTagCount(INT * Count) {   // Set the number of smart tags this action supports. *Count = 1; return S_OK; }

HRESULT CAction::get_SmartTagName(INT SmartTagID, BSTR * Name) {   // This method is called the same number of times as you // return in SmartTagCount. This method sets a unique name // for the smart tag. *Name = SysAllocString(L&quot;microsoft/messenger#contacts&quot;); return S_OK; }

HRESULT CAction::get_SmartTagCaption(INT SmartTagID, INT LocaleID, BSTR * Caption) {   // This caption is displayed on the menu for the smart tag. *Caption = SysAllocString(L&quot;Messenger Smart Tag&quot;); return S_OK; }

HRESULT CAction::get_VerbCount(BSTR SmartTagName, INT * Count) {   // Return the number of verbs we support. if (wcsstr(SmartTagName,L&quot;microsoft/messenger#contacts&quot;) != 0) { *Count = 2; }   return S_OK; }

HRESULT CAction::get_VerbID(BSTR SmartTagName, INT VerbIndex, INT * VerbID) {   // Return a unique ID for each verb we support. *VerbID = VerbIndex; return S_OK; }

HRESULT CAction::get_VerbCaptionFromID(INT VerbID, BSTR ApplicationName,                    INT LocaleID, BSTR * Caption) {   // Set a caption for each verb. This caption is displayed // on the Smart Tag menu. switch (VerbID) { case 1: *Caption = SysAllocString(L&quot;Send this contact an Instant Message&quot;); break; case 2: *Caption = SysAllocString(L&quot;Send email to this contact&quot;); break; default: *Caption = NULL; break; }   return S_OK; }

HRESULT CAction::get_VerbNameFromID(INT VerbID, BSTR * Name) {   // Set a string name for each verb. switch (VerbID) { case 1: *Name = SysAllocString(L&quot;SendInstantMessage&quot;); break; case 2: *Name = SysAllocString(L&quot;SendEmail&quot;); break; }   return S_OK; } HRESULT CAction::InvokeVerb(INT VerbID, BSTR ApplicationName,     IDispatch * Target, ISmartTagProperties * Properties,      BSTR Text, BSTR Xml) {   // This method is called when a user invokes a verb // from the Smart Tag menu. Messenger::IMessengerApp2Ptr oMessenger = NULL; Messenger::IMsgrObject2Ptr oMsgrObj = NULL; Messenger::IMsgrUsersPtr oUsers = NULL; Messenger::IMsgrUserPtr oUser = NULL; _variant_t v;

// Create an instance of Instant Messenger. oMessenger.CreateInstance(&quot;Messenger.MessengerApp&quot;); oMsgrObj.CreateInstance(&quot;Messenger.MsgrObject&quot;); // Get a list of contacts. oUsers = oMsgrObj->GetList(Messenger::MLIST_CONTACT); // Loop through all contacts. for (long l=0; l<(oUsers->GetCount-1); l++) {       // Get a specific contact. oUser = oUsers->Item(l); // Check to see if the contact is the correct one. if (wcscmp(_wcslwr(oUser->GetFriendlyName),_wcslwr(Text)) == 0) {           switch (VerbID) { case 1: // The user wants to display the Instant Message // box to send the contact a message. v = oUser.GetInterfacePtr; oMessenger->LaunchIMUI(v); break; case 2: // Shell the &quot;mailto&quot; protocol to start the // user's mail program and create a new message. _bstr_t bstrTemp = &quot;mailto:&quot;; bstrTemp += oUser->GetEmailAddress; ShellExecute(0,&quot;open&quot;,bstrTemp,NULL,NULL,1); break; }       }    }    return S_OK; }                   </li>  Open the Stdafx.h file and add the following line after the line that reads #include <atlcom.h>: NOTE: Change the path of the Msmsgs.exe file to the installation point for Instant Messenger. The default location for Instant Messenger is C:\Program Files\Messenger. </li> Press F7 to build the DLL.</li></ol>
 * 1) import &quot;C:\Program Files\Messenger\msmsgs.exe&quot;

back to the top

Steps to Register the Smart Tag DLL
Before you can use any Smart Tag DLL, you must register it on the system. Normal COM registration is done for you when you compile the project or call Regsvr32.exe with the DLL name. You must create additional registry entries that are not part of normal COM registration so that Office applications can identify the DLL as a Smart Tag DLL. To do this, follow these steps:
 * 1) From a command line, start Regedit.exe.
 * 2) At HKEY_CURRENT_USER\Software\Microsoft\Office\Common\Smart Tag\Actions, add a new subkey named MessengerSmartTag.Action.
 * 3) At HKEY_CURRENT_USER\Software\Microsoft\Office\Common\Smart Tag\Recognizers, add a new subkey named MessengerSmartTag.Recognize.
 * 4) Close the Registry Editor.

back to the top

Steps to Test the Smart Tag DLL
Smart Tags obey the same security model as macros. If the security settings of the application are set to High, the Smart Tag DLL does not load unless the DLL is digitally signed (as is also the case with VBA macros). For more information on digital signing, see the &quot;References&quot; section.

To test the custom Smart Tag Recognizer/Action DLL in Word, follow these steps:


 * 1) Start Instant Messenger and log on.

NOTE: The sample Smart Tag requires that you log on to Instant Messenger; if you do not log on to Instant Messenger, the custom DLL loads but does not recognize any contacts.
 * 1) Start Word 2002. On the Tools menu, point to Macro and click Security. Set the macro security to Medium and click OK. If the macro security setting was previously set to High, restart Word.
 * 2) Type the friendly name of a contact in a new document (for example, John Smith) and press ENTER. A faint line appears beneath the friendly name to indicate that it is recognized as a Smart Tag. Move the mouse over the friendly name, and the Smart Tag Action button appears.
 * 3) Click Smart Tag Action and select one of the custom action items from the drop-down menu. You can send an e-mail or instant message to the contact from your new document.

You can use similar steps to test the Smart Tag DLL in Excel 2002.

back to the top

Troubleshooting
If you have problems getting your custom Smart Tags to work, first make sure that the custom Smart Tag DLL is being loaded. In Word or Excel, on the Tools menu, click Auto Correct Options, click the Smart Tag tab, and ensure that Label Text with Smart Tags is selected and that your Smart Tag DLL is listed and selected. If your Smart Tag is not listed, it may not be properly registered.

If the execution of the custom recognizer or action class is the source of the problem, you can debug a Smart Tag DLL as you would any Visual C++ DLL. Set a breakpoint in the constructor for the Recognizer class. When you press F5 to debug the application, a dialog box appears and asks for an executable file for the debug session. Select either Winword.exe or Excel.exe. When Excel or Word starts and loads the Smart Tag, your code breaks at the breakpoint and you can step through the code for debugging.

back to the top