智能标记DIY 四川省新都县国家税务局 周鸣扬 01-12-7 14:04:26
Office XP的推出,耳目一新的界面及功能让我们为之一振。在Office XP环境下,随处可见智能标记(Smart Tag)的应用。智能标记是一项很有争议的技术,它是一组跨越各种应用软件的共享信息按钮,当用户移动鼠标使光标放在带有智能标记的字词之上时,智能标记 按钮就会自动弹出来,提供各种操作供选择(如图一所示)。智能标记技术在本质上和超链接是相同的。不过,智能标记在功能上已经远远超出超链接而具有更多的 精彩特色,如:多地址指向、激活应用程序、跨程序共享等等。本文今天不是要讲智能标记的优点及应用,而主要是说说如何自己设计出智能标记,让自己感兴趣的 话题也能够被Office XP所“智能标记”。
同微软习惯的作法一样,每当有新的技术诞生时,都会为开发者者提供一套新的SDK。同样,对于“智能标记”,微软提供了一个智能标记软件开发包 (Smart Tag Software Development Kit,以下简称开发包),用来支持开发工作(下载地址:http://download.microsoft.com/download /OfficeXPDev/Install/XP/W982KMeXP/EN-US/stsdk.exe),在进行编程前,除了要安装上述SDK之外,更 重要的是要对Smart Tag的工作原理有一个全面的理解。图二展示了Smart Tag的工作原理:
上图简单地说明了智能标记的工作原理:智能标记是一种公用的程序,Office XP(包括IE)都可以调用它。智能标记包括了两层含义:识别(Recognizers)、动作(Actions)。识别就是对预定义的字符进行标识(字 符出现波浪线,呈现“i”形图标);动作就是由用户调用的和特定类型数据关联的动作。识别什么?采取什么样的动作?这些具体的规定全部是由已经在注册表中 注册的DLL文件来制约的(如Office XP自带的对“人名(英文)”进行标识的智能标记,就是通过fperson.dll来实现的,在开发包中有一Action程序,可以查看出目前 Office XP中所有的智能标记及使用的DLL)。也就是说,如果想要自己设计出一个智能标记,最关键的技术就是要设计出一个DLL文件,在这个文件中对识别和动作 进行约定,然后再将DLL在注册表中注册就行了。在我的主页“国税之家”(http://nationaltax.home.chinaren.com) 的“个人世界中”可以找到一个关于Smart Tag工作原理的演示程序,有兴趣的朋友可以去看看。
能够在Office XP的各个应用之间被共享的智能标记从本质上讲是一ActiveX组件。在安装完Office XP之后,在X:/Program Files/Common Files/Microsoft Shared/Smart Tag目录下会生成一个MSTAG.TLB文件,这个文件就是由智能标记对应的类型库,通过这个类型库,我们就可以很容易地生成自己的智能标记的DLL文 件。利用Visual Studio 6.0 Tools中的“OLE View”程序,可以看出这个类型库具体所包含的属性及方法。说到开发ActiveX应用,就不能不提起用ATL(ActiveX Template Library: ActiveX模板库)和COM编程。做为VC中的ATL,在进行COM编程时,ATL是一种快速、简易的方法。使用ATL可创建ActiveX控件,而 且这种控件不需要所有MFC自动提供的内置功能,创建的组件小巧、快速。最关键是,在使用ATL时,通过对MSTAG.TLB所定义的COM组件进行继 承,可以很快的建立自己的应用程序框架,再通过接口,就能够在框架中添加新的功能。用ATL编程时,另外一个好处就是可以让生成的DLL文件自动注册,无 需人工添加代码来进行干预注册过程。MSTAG.TLB提供了四个接口供调用,分别是:ISmartTagRecognizer(以下简称识别)、 ISmartTagRecognizerSite、ISmartTagAction(以下简称动作)、ISmartTagProperties,识别和动 作是核心,其他的两个接口只是在识别和动作之间起参数传递作用。自定义智能标记最主要的就是将识别和动作接口中各种属性及方法定制好。识别和动作接口中各 种属性及方法大都相似,这里只以识别接口为例,说明在编程时有哪些属性需要填充,如下表所列:
ISmartTagRecognizer接口中各种属性及方法
属性 ( 方法 ) 名
类型
含义
ProgId
字符型
识别器程序标识符 (programmatic id)
Name
字符型
识别器的功能简单描述
Desc
字符型
识别器的功能详细描述
SmartTagCount
数值型
可识别的智能标记类型数
SmartTagDownloadURL
字符型
其它智能标记的下载地址
SmartTagName
字符型
支持的智能标记类型唯一标识符
Recognize
将字符串识别成可动作的智能标记的方法
下面就着手具体的编程操作,在本文中,以识别姓名“张三”、“李四”为例,详细说明具体的编程步骤。本程序具备智能标记编程的基本体系,对所识别的姓名产生两种操作:发送电子邮件和浏览个人主页。
一、在VC中使用ATL COM AppWizard向导新建一项目AboutMe,在向导的每一步中都选择默认值。
二、在项目中添加一ATL对象(Insert->New ATL Object),选择Object下面的“simple object”, 之后会出现一个标题为“ATL Object 属性”的对话框,在这个对话框中设置ATL对象的属性。在Short Name中键入“Recogniser”,其他的相应编辑框中同时使用默认值。这一步很关键,因为在以后的编程中会用到这些信息。按照这种方法,再在项目 中添加一ATL对象,只不过在Short Name中键入的是“Action”。这步实际是完成了智能标记的“识别”和“动作”类的定义。接下来就是将ISmartTagRecognizer和 IsmartTagAction两个接口添加到类中。
三、引入接口。在类查看器(Class View)中选择CRecognizer,单击鼠标右键,选择“Implement Interface”,这时会出现一个类型库选择框,选择“Microsoft Smart tags 1.0 type library”然后再选择“ISmartTagRecognizer”可。这样做是为了将智能标记的组件接口ISmartTagRecognizer引 入CRecognizer类中。用同样的方法,在CAction类中也按上面的方法加入ISmartTagAction接口(在最后一步选择 ISmartTagAction)。接口引入完成之后,在CRecognizer和CAction类定义中,多了些属性及方法申明,接着要做的工作就虽按 照这些申明来完成类的实现。
四、修改类定义。CRecognizer和CAction类中的方法(Method)在类定义中已经有了默认的代码,这些代码是没有用的,首先应该修改两个类的定义,修改后的两个类的定义如下:
1、CRecognizer类定义(Recognizer.h)
class ATL_NO_VTABLE CRecognizer :
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);
class ATL_NO_VTABLE CActions :
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);
#endif //__STRECOGNIZER_H_
五、类的实现部分。CRecognizer和CAction类的实现部分是本文所述之关键部分。在进行代码实现之前,首先得清楚地认识到,代码部份不应有 任何的BUG(如对内存的分配、使用、释放方面的BUG,这也是本文程序代码有点冗长的主要原因),否则,BUG将导致Office XP不能正常运行。具体实现过程在代码的注释部份说得很清楚,代码中有一点复杂的就是在“识别”和“动作”之间进行通信的问题,这时对 ISmartTagRecognizerSite、ISmartTagProperties两个接口地在传输中所起的作用就显得尤为重要。
// Recognizer.cpp : CRecognizer类的实现部分
//nPersons:全局变量,指明需要标记的人数
#define nPersons (sizeof(person)/sizeof(PERSON))
//HrAllocString实现部份: 将写入BSTR型的pbstr
HRESULT HrAllocString(BSTR *pbstr, WCHAR *sz)
*pbstr = SysAllocString(sz);
return S_OK; // Successful operation.
{L"张三", L"Mailto:张三@XXXX.com",L"http://www.张三.com"},
{L"李四", L"Mailto:李四@XXXX.com",L"http://www.李四.com"},
//为接口的各属性赋值,ProgId的值必须准确,必须是在ATL对象属性中的ProgId值
STDMETHODIMP CRecognizer::get_ProgId (BSTR * ProgId)
*ProgId=SysAllocString(L"Aboutme.Recognizer");
STDMETHODIMP CRecognizer::get_Name (INT LocaleID, BSTR * Name)
return HrAllocString(Name, L"中文姓名识别");
STDMETHODIMP CRecognizer::get_Desc (INT LocaleID, BSTR * Desc)
return HrAllocString(Desc, L"My Recognizer ");
STDMETHODIMP CRecognizer::get_SmartTagCount (INT * Count)
STDMETHODIMP CRecognizer::get_SmartTagName (INT SmartTagID, BSTR * Name)
return HrAllocString(Name, SMART_TAG_NAME);
STDMETHODIMP CRecognizer::get_SmartTagDownloadURL (INT SmartTagID, BSTR * DownloadURL)
//DownloadURL:智能标记菜单中“检查新操作”所访问的URL
STDMETHODIMP CRecognizer::Recognize (BSTR Text, IF_TYPE DataType, INT LocaleID, ISmartTagRecognizerSite * RecognizerSite)
//DataType=IF_TYPE_SINGLE_WD;
ISmartTagProperties *pSmartTag = NULL;
BSTR key,strMailBox,strHomepage,SmartTagName;
//产生一个新的ISmartTagProperties对象,将个人信息写入该对象,供CAction使用
RecognizerSite->GetNewPropertyBag(&pSmartTag);
SmartTagName = SysAllocString(SMART_TAG_NAME);
//从预定义的字符串中查看Text是否属于需要标记的字符
for(iTerm = 0; iTerm < nPersons; iTerm++)
for (pch = strText; (pch = wcsstr(pch, person[iTerm].strName)) != NULL; pch++)
strMailBox=SysAllocString(person[iTerm].strMailBox);
strHomepage=SysAllocString(person[iTerm].strHomepage);
key=SysAllocString(MAIL_KEY);
//将邮件地址和个人主页写入pSmartTag该对象
//pSmartTag中属性存储格式:键值(相关于索引)->内容
pSmartTag->Write(key,strMailBox);
key=SysAllocString(HOMEPAGE_KEY);
pSmartTag->Write(key,strHomepage);
SysFreeString(strMailBox);
SysFreeString(strHomepage);
//标记从pch - strText +1起到wcslen(person[iTerm].strName)结束
RecognizerSite->CommitSmartTag(SmartTagName, pch - strText +1, wcslen(person[iTerm].strName), pSmartTag);
SysFreeString(SmartTagName);
// Actions.cpp : CActions类实现部份
#define VERB_BROWSE_WEB 1
#define cVerb (sizeof(arrVerb)/sizeof(VERB))
{L"发送电子邮件",L"Send a E_mail",VERB_SENDEMAIL},
{L"在新窗口中打开主页", L"Go to homepage",VERB_BROWSE_WEB}
//初始化CActions的各种属性,ProgId是关键
STDMETHODIMP CActions::get_ProgId(BSTR * ProgId)
return HrAllocString(ProgId, L"Aboutme.Actions");
STDMETHODIMP CActions::get_Name(INT LocaleID, BSTR * Name)
return HrAllocString(Name, L"My caption");
STDMETHODIMP CActions::get_Desc(INT LocaleID, BSTR * Desc)
return HrAllocString(Desc, L"My action");
STDMETHODIMP CActions::get_SmartTagCount(INT * Count)
STDMETHODIMP CActions::get_SmartTagName(INT SmartTagID, BSTR * Name)
return HrAllocString(Name, SMART_TAG_NAME);
STDMETHODIMP CActions::get_SmartTagCaption(INT SmartTagID, INT LocaleID, BSTR * Caption)
//智能标记下拉菜第一行的标题=Caption+已标记的文本
return HrAllocString(Caption, L"个人信息");
STDMETHODIMP CActions::get_VerbCount(BSTR SmartTagName, INT * Count)
STDMETHODIMP CActions::get_VerbID(BSTR SmartTagName, INT VerbIndex, INT * VerbID)
STDMETHODIMP CActions::get_VerbCaptionFromID(INT VerbID, BSTR ApplicationName, INT LocaleID, BSTR * Caption)
if (VerbID < 0 || VerbID >= cVerb)
return E_INVALIDARG; // One or more arguments are invalid.
return HrAllocString(Caption, arrVerb[VerbID].strCaption);
STDMETHODIMP CActions::get_VerbNameFromID(INT VerbID, BSTR * Name)
if (VerbID < 0 || VerbID >= cVerb)
return HrAllocString(Name, arrVerb[VerbID].strName);
//BSTR_TO_CHAR 将BSTR转换成char*型
#define BSTR_TO_CHAR (char*)_bstr_t
STDMETHODIMP CActions::InvokeVerb(INT VerbID, BSTR ApplicationName, IDispatch * Target, ISmartTagProperties * Properties, BSTR Text, BSTR Xml)
BSTR key,mailbox,homepage;
key=SysAllocString(MAIL_KEY);
//将邮件地址和个人主页信息从Properties对象中分别读出
//Properties中属性存储格式:键值(相关于索引),内容
Properties->get_Read(key,&mailbox);
key=SysAllocString(HOMEPAGE_KEY);
Properties->get_Read(key,&homepage);
if (VerbID < 0 || VerbID >= cVerb)
switch (arrVerb[VerbID].id)
ShellExecute(0,0,BSTR_TO_CHAR(mailbox),0,0,0);
ShellExecute(0,0,BSTR_TO_CHAR(homepage),0,0,0);;
上面的两段代码都用到了common.h,其代码如下:
//CRecognizer和CAction共用的函数
HRESULT HrAllocString(BSTR *pbstr, WCHAR *sz);
#define HOMEPAGE_KEY L"homepage"
//Uniform Resource Identifier (URI:统一资源标识)+“#” +标识名
#define SMART_TAG_NAME L"URN:NAME#MyTagName"
六、添加对DLL文件的注册代码。作为智能标记,其信息在注册表中的位置是特定的,所以,必须手工指明信息在注册表中的注册位置及键值。具体办法是:
1、注册动作,打开项目中的Actions.rgs文件,在文件尾追加下列信息
2、注册识别器,打开项目中的Recognizers.rgs文件,在文件尾追加下列信息
七、编译。在编译之前,首先应将URLMOB.LIB库添加到项目中。编译成功之后,会出现一个对话框,让你输入启用DLL的应用程序,输入Office XP之中的任一程序,你的智能标记便成功了!(需要说明的是,智能标记的DLL文件在Office XP运行时已经加载,处于锁定状态,如果出现LINK错误,首先得关闭所有的Office XP程序)。本所述程序在Windows 2000、VC6.0、Office XP下编译通过。由于本文主要是侧重对编程进行叙述,所发对各种函数的调用格式未作详细说明,具体请查阅Smart Tag SDK帮助文档。
在编程成功运行后,你可能会发现Office XP应用程序的启动速度慢了,这很正常。因为Office XP需要加载相关的DLL文件。如果发现预定义的字符并未被“智能标记”,首先应该检查一下自定义的智能标记信息在注册表中是否正确注册,接下来在 Office XP程序(如WORD)”中看看是否已经启用了智能标记(菜单“工具”→ “自动更正选项”→“智能标记),察看智能标记名称是否在标记列表中出现。在以后运行Office XP(含IE6)时,在出现“张三”和“李四”的地方,都会看到你的智能标记出现,应该说,跨程序共享才是智能标记最诱人的地方!