65.9K
CodeProject 正在变化。 阅读更多。
Home

ATL COM共享插件(使用C++ - 简单方法)

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.33/5 (4投票s)

2007 年 2 月 26 日

CPOL

27分钟阅读

viewsIcon

80472

downloadIcon

1570

ATL COM共享插件(使用C++ - 简单方法)

引言

ATL COM 共享加载项提供了一种向支持 COM 的应用程序(如 Microsoft Office 套件程序)添加功能的方法。ATL COM 共享加载项的示例包括 Dotric DotReader 程序和 Dotric ShareWord 程序(其他地方有描述)。这两个程序都通过添加到 Microsoft Word 的额外菜单项进行访问。

创建一个新的共享加载项非常简单。启动 Microsoft Visual Studio 2002(或更高版本),从主菜单中选择“文件”、“新建”和“项目”。在“新建项目”对话框中,从树视图中选择“其他项目类型”和“可扩展性”,然后选择“共享加载项”模板。键入项目名称和位置,然后按“确定”启动“共享加载项向导”。按“下一步”,然后选择“使用 Visual C++/ATL 创建加载项”,然后再次按“下一步”。使用显示的复选框选择要添加功能的应用程序(例如 Microsoft Word),然后再次按“下一步”。接下来的两个步骤主要是个人偏好问题,所以完成这两个步骤并按“完成”。

太简单了。但是,你现在拥有的是一个 ATL COM 项目,它通常涉及大量丑陋的 COM 调用到宿主应用程序。遗憾的是,一些非常整洁的 MFC 类库无法在这些共享加载项中使用,毕竟宿主应用程序很可能使用 MFC。似乎 Microsoft 的 ATL 人员和 MFC 人员不经常互相交流。因此,本文的目的是通过无耻地复制和粘贴 MFC 中的源代码来描述一种更简单的开发 ATL COM 共享加载项的方法。

MFC 方式

如果你仔细阅读了本文的简介,你现在应该有一个骨架 ATL COM 共享加载项项目。但是,要以简单的方式完成 ATL COM,至少需要一个 MFC 项目用于其源代码。我说至少一个,因为如果你计划做不止一个这样的项目,明智的做法是为每个宿主应用程序创建一个单独的 MFC 项目,并保存生成的源代码以备将来使用。如果此时你宁愿使用 MFC 并忘记 ATL COM,那么我推荐 Lori Turner(Microsoft Corporation,2000 年 3 月)撰写的文章《Automating Microsoft Office 97 and Microsoft Office 2000》。可以通过访问 http://support.microsoft.com/kb/253235/EN-US 并勾选 立即下载 OffAutmn.exe 软件包 来获取此文章。

无论如何,要生成一些 COM 源代码,启动几乎任何版本的 Microsoft Visual Studio,然后从主菜单中选择“文件”、“新建”和“项目”。在“新建项目”对话框中,从树视图中选择“Visual C++”和“MFC”,然后选择“MFC 应用程序”模板。键入项目名称和位置。使用包含宿主应用程序名称和一些指示该项目正在提供 ATL COM 共享加载项源的项目名称。按“确定”启动 MFC 应用程序向导。按“下一步”并选择“单文档”作为应用程序类型。现在是重要部分,如果你使用的是 Visual Studio 2005,请再按五次“下一步”以转到“高级功能”页面,并勾选“自动化”复选框。如果你使用的是其他版本的 Visual Studio,则需要找到“自动化”复选框并勾选它!按“完成”生成 MFC 项目。

为宿主应用程序 COM 接口生成 MFC COM 类包装器的过程在 Visual Studio 的不同版本之间略有不同。对于 Visual Studio 2005,从主菜单中选择“项目”和“添加类”。早期版本的 Visual Studio 可能将其称为“类向导”。在“添加类”对话框中,从树视图中选择“MFC”,然后选择“从类型库创建 MFC 类”模板,然后按“添加”。在“从类型库创建类向导”对话框中,使用相应的单选按钮选择“从文件添加类”,然后按“...”按钮来查找宿主应用程序类型库。

类型库可以是 dll、exe、tlb 或 olb 文件,这听起来帮助不大,但这主要是试错法。我可以告诉你的是,Microsoft Office 的文件通常在“C:\Program Files\Microsoft Office\OFFICE11”、“C:\Program Files\Microsoft Office\Office”或适用于你版本的 Microsoft Office 的任何文件夹中。Microsoft Office 应用程序的类型库文件名如下表所示。

Application 2000 XP 2003
访问 Msacc9.olb Msacc10.olb MSACC.OLB
Excel Excel9.olb Excel10.olb EXCEL.OLB
Outlook(展望) Msoutl9.olb Msout10.olb MSOUTL.OLB
PowerPoint Msppt9.olb Msppt10.olb MSPPT.OLB
单词 Msword9.olb Msword10.olb MSWORD.OLB

一旦使用 VS 向导找到了所需的类型库文件,选择“文件”对话框中的“打开”,然后“从类型库添加类向导”对话框将填充许多接口选项。我建议按“>>”按钮选择所有选项。按“完成”以生成大量的 MFC COM 类包装器。

如果您确实打算使用某个 Microsoft Office 应用程序作为主机,那么您很可能也需要通用的 Microsoft Office 类型库。为了获取此类型库,我建议您创建另一个新的 MFC 项目,并按照前面描述的步骤进行操作。Microsoft Office 类型库通常位于“C:\Program Files\Common Files\Microsoft Shared\OFFICE11”、“C:\Program Files\Microsoft Office\Office”或适用于您版本的 Microsoft Office 的任何文件夹中。可能的类型库文件名可能是 _Mso9.dll_、_Mso10.dll_ 或 _MSO.DLL_。

权宜之计

CDotDispatchDriver 下载在本文顶部提供)

按照上一节的步骤,你将拥有一个或多个包含大量 COM 包装器的 MFC 项目。查看这些 COM 包装器的源文件,可以看出每个都派生自类 COleDispatchDriver。查阅 Microsoft 的 MFC 参考图表,发现 COleDispatchDriver 被描述为“支持类”,并且不派生自任何其他 MFC 类。现在 COleDispatchDriverafxdisp.h 中定义,但如果你尝试在 ATL COM 共享加载项项目中包含此头文件,你将发现自己需要添加大量其他头文件,而无济于事。

我的方法是从包含文件 afxdisp.h 及其关联的源文件 oledisp2.cpp 中删除所有不必要的内容。这个过程是一个相当残酷的让杂种编译的过程,并在不到 2 小时内完成。但是,我还没有发现最终结果的错误,你可以在本文顶部下载它。首先要注意的是,类的名称已更改为 CDotDispatchDriver,以避免我自己或编译器与原始 MFC 类混淆。其次,在此过程中还创建了一个 DotOleErrors 类。第三,COleDispatcherDriver 频繁使用 MFC 类 CString。幸运的是,ATL 也有一个名为 CString 的类,其工作方式大致相同,并在 atlstr.h 中定义。

从这里到整洁的面向对象 ATL COM 共享加载项代码的步骤如下

  1. 将 `DotDispatchDriver` 源文件复制到您的共享加载项项目中。
  2. 确定用于此目的的一个或多个 MFC 项目中所需的 COM 包装器。
  3. 将步骤 2 中确定的 COM 包装器文件复制到您的共享加载项项目中。
  4. 使用“查找和替换”查找类名 COleDispatchDriver 的所有实例并将其替换为类名 CDotDispatchDriver
  5. 根据需要修改 COM 包装器文件中的 include 声明。

有了 CDotDispatchDriver,这样的代码

szFunction = OLESTR("ActiveDocument");
hr = pDispApp->GetIDsOfNames (IID_NULL, &szFunction, 1, LOCALE_USER_DEFAULT,
                             &dispid_ActiveDoc);
hr = phr = pDispApp->Invoke (dispid_ActiveDoc, IID_NULL, LOCALE_USER_DEFAULT,
                             DISPATCH_PROPERTYGET, &dpNoArgs, &vResult, 
                             NULL, NULL);
pDispActiveDoc = vResult.pdispVal;

将变成这样的代码

m_oDocument = m_oWordApp.get_ActiveDocument(); 

关于主机 COM 接口的帮助

到现在您可能已经注意到,关于各个 COM 接口的具体信息并不多。一个信息来源是 MFC COM 包装器本身。通过查看源代码可以获得相当多有用的信息。

OLE/COM 对象查看器是一个有用的工具。它位于 Visual Studio 文件夹中的“Common7\Tools\Bin”文件夹中。可执行文件名为“OleView.exe”。或者,通过选择“工具”和“外部工具...”可以将 OLE/COM 查看器添加到 Visual Studio 的菜单项中。启动 OLE/COM 对象查看器后,单击工具栏上带有三个红色三角形的按钮以“查看类型库”。在标准“打开”对话框中,找到宿主应用程序的类型库文件,然后单击“打开”。然后会打开一个新窗口,其中包含类型库文件的源代码。如果需要,此源代码可以保存为 IDL 文件。

Microsoft Office 程序套件通过其 Visual Basic 宏编辑器提供 COM 接口信息。要获取此信息,请启动您选择的 Microsoft Office 程序,然后从主菜单中选择“工具”、“宏”和“Visual Basic 编辑器”。一旦 Microsoft Visual Basic 编辑器运行起来,从其主菜单中选择“帮助”和“Microsoft Visual Basic 帮助”。此帮助的一个很好的起点是搜索“对象模型”。找到的对象模型图提供了一种方便的方式来深入了解更详细的信息。当然,此信息以 Visual Basic 为中心,但转换为 C++ 通常是显而易见的。

创建 C# 项目也很有用。为此,启动 Visual Studio,然后从主菜单中选择“文件”、“新建”和“项目”。在“新建项目”对话框中,从树视图中选择“C#”和“Windows”,然后选择“Windows 应用程序”作为模板。键入项目名称和位置,然后按“确定”。创建新的 C# 项目后,从 Visual Studio 主菜单中选择“项目”和“添加引用”。在“添加引用”对话框中,切换到“COM”面板。此面板通常包含一个全面的类型库文件列表,其中应包含您预期宿主应用程序的类型库文件。因此,选择您的宿主应用程序并按“确定”。切换到中央代码查看器区域中的“对象浏览器”视图。如果“对象浏览器”面板不是可用面板之一,则从主菜单中选择“视图”和“对象浏览器”以使其可用。查看“对象浏览器”树视图,您将找到一个 Interop 类,其中包含一个表示您所选宿主应用程序的 COM 接口的类。展开此树节点将显示宿主应用程序 COM 接口的全面描述。如果这足以让您放弃 C++ 而改用 C#,那么我推荐 Andrew W. Troelsen(Intertech Training,2004 年 6 月)撰写的文章《使用 C# 编程 Outlook 2003 简介》。此文章可在 http://msdn.microsoft.com/library/default.asp?url=/library/en-us/dv_vstechart/html/ol03csharp.asp 找到。

谷歌当然永远是信息的金矿。

一个示例

(_WordFindFacility_ 源文件可从文章顶部下载)

三个新项目

最初对示例的期望是它们具有指导性、新颖性,甚至可能是有用的。然而,实际上我发现所有最好的教科书和文章提供的示例都只不过是具有指导性。因此,这里提供的示例只是为 Microsoft Word 添加了一个单词查找功能。这完全没用,因为 Microsoft Word 当然已经有一个单词查找功能,但更糟糕的是,这个单词查找功能是模态的。这种模态行为发生是因为 COM 提供了同步通信。因此,当您的共享加载项具有程序控制权时,Microsoft Word 没有,反之亦然。本示例使用 Microsoft Visual Studio 2005 和 Microsoft Word 2003。

如本文引言中所述,首先要做的是创建一个新的共享加载项项目。所以启动 Visual Studio 2005 并开始一个新项目。在“新建项目”对话框中,“项目类型”是“其他项目类型”和“可扩展性”。模板是“共享加载项”,并使用项目名称“_WordFindFacility_”。在“共享加载项向导”的第 1 页,选择“使用 Visual C++/ATL 创建加载项”。在第 2 页,主机应用程序是 Microsoft Word(取消选择其余的)。在第 3 页,将“Word Find Facility”作为名称和描述。在第 4 页,勾选“我希望我的加载项在主机应用程序加载时加载”和“我的加载项应可供安装它的计算机的所有用户使用,而不仅仅是安装它的人”。在第 5 页按“完成”。

如果您还没有这样做,现在是创建两个 MFC 项目的时候了,如本文的MFC 方式一节所述。其中一个项目当然是为 Microsoft Word 接口生成 COM 包装器。另一个是用于通用 Microsoft Office 接口。在这两种情况下,项目都是一个 MFC 应用程序,其应用程序类型是“单文档”,并且“高级功能”页面上的“自动化”复选框已被勾选。在每个项目中,从主菜单中选择“项目”和“添加类”。要添加的类是“从类型库创建 MFC 类”。在“从类型库添加类向导”对话框中,使用单选按钮选择“从文件添加类”,然后按“...”按钮定位相关文件。对于 Microsoft Word,文件是“C:\Program Files\Microsoft Office\OFFICE11”文件夹中的 _MSWORD.OLB_。对于 Microsoft Office,文件是“C:\Program Files\Common Files\Microsoft Shared\OFFICE11”文件夹中的 MSO.dll。在这两种情况下,都按“>>”添加所有类。

应用补丁

创建 COM 接口包装器后,需要将包含 Microsoft Word COM 接口包装器的 MFC 项目中的以下文件复制到新的 WordFindFacility 项目中:

  • CApplication.h
  • CDocument0.h
  • CWords.h
  • CRange.h
此外,需要将包含 Microsoft Office COM 接口包装器的 MFC 项目中的以下文件复制到新的 WordFindFacility 项目中:
  • CCommandBarPopup.h
  • CCommandBarButton.h
  • CCommandBar0.h
  • CCommandBars.h
  • CCommandBarControl.h
  • CCommandBarControls.h
最后,需要将本文权宜之计一节中创建的 DotDispatchDriver.hDotDispatchDriver.cppDotOleErrors.hDotOleErrors.cpp 文件复制到新的 WordFindFacility 项目中。将文件实际复制到 WordFindFacility 文件夹后,还必须通过在 Visual Studio 的解决方案资源管理器中右键单击 WordFindFacility 树节点,将其添加到项目中。在弹出菜单中选择“添加”和“现有项”,然后使用“添加现有项”对话框选择文件。现在,为了完成从 MFC 到 ATL 的转换,必须查找所有出现变量名 COleDispatchDriver 的地方,并将其替换为变量名 CDotDispatchDriver。此外,每个包含对 CDotDispatchDriver 引用的头文件也必须在文件顶部附近包含以下行:
#pragma once
#include "DotDispatchDriver.h"

在这些头文件的顶部,以 `#import` 开头的行必须删除。如果你一直认真阅读文本,此时尝试第一次编译可能是明智之举。

连接到 Microsoft Word

有了编译成功的编译器,终于可以开始编写代码了。首先,需要某种机制来调用 Microsoft Word。参考 WordFindFacility 项目中向导生成的 Connect.cpp 源文件,有一个名为 OnConnection 的方法,它传递了一个 IDispatch 指针,可以很好地满足目的。因此,在 Connect.h 头文件中,在其他包含声明中添加以下行:

#include "CApplication.h"

然后将以下行添加到 CConnect 类声明中

private:
    CApplication m_oWordApp;

现在回到 Connect.cpp 源代码,将以下行添加到前面讨论的 OnConnection 方法中

try
{ 
    m_oWordApp.AttachDispatch(pApplication); 
}
catch( DotOleException* e )
{ 
    DotOleErrors::OleErrorMessages(e); 
}
catch( DotOleDispatchException* e )
{ 
    DotOleErrors::DispatchErrorMessages(e); 
}

此外,必须在其他包含声明中添加以下行

#include "DotOleErrors.h"

这里所做的是围绕 IDispatch 指针包含了一个包装器。然而,在使用这些东西时,必须记住,尽管表面上看起来如此,但这里正在进行进程间通信,因此建议频繁使用 try/catch 语句。

修改 Microsoft Word 主菜单

接下来的任务是修改 Microsoft Word 主菜单,以包含我们的单词查找功能。这可能是在现有菜单栏选项中添加一个菜单项,但对于本示例,也将添加一个新的菜单栏选项。因此,在 Connect.h 头文件中,在其他 include 声明中添加以下行:

#include "CCommandBarPopup.h"
#include "CCommandBarButton.h"
然后将以下行添加到 CConnect 类声明中
private:
   CCommandBarPopup m_oWordFindPopup;
   CCommandBarButton m_oWordFindButton;

这样添加的两个变量声明分别代表新的菜单栏选项和新的菜单项。在 Connect.h 文件中,以下行也可以添加到 CConnect 类中:

private:
   void AddMenuItems();
   void RemoveMenuItems(); 

这些当然是用于添加和删除单词查找功能的菜单项的方法声明。切换回 Connect.cpp 文件,新方法 `AddMenuItems` 被添加到文件末尾,从以下行开始:

void CConnect::AddMenuItems()
{
    try
    {
        CCommandBars oCommBars = m_oWordApp.get_CommandBars();
        CCommandBar0 oMenuBar = oCommBars.get_ActiveMenuBar();
        CCommandBarControls oMenuBarControls = oMenuBar.get_Controls();

        long count = oMenuBarControls.get_Count();
        for (long iii=1; iii<=count; iii++)
        {
            CComVariant vLong(iii);
            CCommandBarControl oControl = oMenuBarControls.get_Item(vLong);
            if ( oControl.get_Caption().Compare( "&Window" ) == 0 )
            {
                MsoControlType type = msoControlPopup;

                m_oWordFindPopup = oMenuBarControls.Add( CComVariant(type), 
                                      vOpt, vOpt, CComVariant(iii), vOpt);

                m_oWordFindPopup.put_Tag("WordFindFacility Popup");
                m_oWordFindPopup.put_Caption("&My Menu");
                m_oWordFindPopup.put_Visible(TRUE);
                m_oWordFindPopup.put_TooltipText(
                                     "A Fairly Useless Word Find Facility");
                break;
            }
        }
这里发生的情况是,获取 Microsoft Word 主菜单栏控件,并依次检查每个菜单栏选项以定位标准“窗口”选项。然后,在“窗口”选项之前插入一个新的菜单栏选项。为新的菜单栏选项设置了一些属性,包括标题“_My Menu_”。现在当然必须添加菜单项本身。这是通过以下代码完成的,该代码附加到上面显示的 AddMenuItems 方法的代码中。
CCommandBarControls controls = m_oWordFindPopup.get_Controls();

MsoControlType type = msoControlButton;
m_oWordFindButton = controls.Add( CComVariant(type), vOpt, vOpt, 
                                  CComVariant(1), vOpt);
m_oWordFindButton.put_Tag("WordFindFacility Popup");
m_oWordFindButton.put_Caption("&Find...");
m_oWordFindButton.put_Visible(TRUE);
m_oWordFindButton.put_TooltipText("A Fairly Useless Word Find Facility");
MsoButtonStyle style = msoButtonCaption;
m_oWordFindButton.put_Style(style);
m_oWordFindButton.put_OnAction( "WordFindFacility" );
这里将一个标题为“Find...”的菜单项添加到新创建的菜单栏选项中。此菜单项的操作使用 put_OnAction 方法设置。新菜单项的操作是调用一个名为“WordFindFacility”的 Microsoft Word 宏。此宏(稍后描述)将在选择菜单项时执行回调。这不是让 Microsoft Word 回调的最优雅方式。使用事件接收器更性感,但为了简单起见,本文将使用宏的方式。从 Microsoft Word 端查看您的加载项也具有一些指导意义。为了完成 AddMenuItems 方法,强制 try 的 catch 语句附加到末尾,如下所示:
}
catch( DotOleException* e )
{
   DotOleErrors::OleErrorMessages(e);
}
catch( DotOleDispatchException* e )
{
   DotOleErrors::DispatchErrorMessages(e);
} 
RemoveMenuItems 方法的代码如下所示
void CConnect::RemoveMenuItems()
{
    try
    {
        CCommandBars oCommBars = m_oWordApp.get_CommandBars();
        CCommandBar0 oMenuBar = oCommBars.get_ActiveMenuBar();
        CCommandBarControls oMenuBarControls = oMenuBar.get_Controls();

        long count = oMenuBarControls.get_Count();
        for (long iii=1; iii<=count; iii++)
        {
            CComVariant vLong(iii);
            CCommandBarPopup oPopup = oMenuBarControls.get_Item(vLong);
            if ( oPopup.get_Caption().Compare( "&My Menu" ) == 0 )
            {
                CCommandBarControls oPopupControls = oPopup.get_Controls();
                while ( oPopupControls.get_Count() > 0 )
                {
                    CCommandBarButton button = oPopupControls.get_Item(
                                                    CComVariant((long)1) );
                    button.Delete(vOpt);
                }
                oPopup.Delete(vOpt);
                count--;
            }
        }
    }
    catch( DotOleException* e )
    {
        DotOleErrors::OleErrorMessages(e);
    }
    catch( DotOleDispatchException* e )
    {
        DotOleErrors::DispatchErrorMessages(e);
    }
}

此方法与 AddMenuItems 方法并无不同。再次获取 Microsoft Word 主菜单栏的控件,并在菜单栏中进行搜索。但是,在这种情况下,搜索目标是“我的菜单”菜单选项的出现次数。如果找到任何,则删除菜单栏选项及其下的菜单项。此方法编写得相当保守。当出现严重错误时,相同的菜单选项可能会多次出现。因此,全面清理是明智之举。显然,以下 include 声明必须出现在 Connect.cpp 源文件顶部附近的其他 include 声明中,以说明 AddMenuItemsRemoveMenuItems 方法中使用的额外数据类型:

#include "CCommandBar0.h"
#include "CCommandBars.h"
#include "CCommandBarControl.h"
#include "CCommandBarControls.h"
然而,细心的读者可能也注意到了 MsoControlTypeMsoButtonStyle 数据类型,这些类型在任何可用的头文件中都不存在。这里的答案在于本文主机 COM 接口帮助一节中描述的 OLE/COM 查看器。因此,启动 OLE/COM 查看器,并对 Microsoft Office 类型库文件 _MSO.dll_ 执行“类型库视图”。正是在这里,可以找到 MsoControlTypeMsoButtonStyle,它们的定义如下:
typedef enum {
    msoControlCustom = 0,
    msoControlButton = 1,
    msoControlEdit = 2,
    msoControlDropdown = 3,
    msoControlComboBox = 4,
    msoControlButtonDropdown = 5,
    msoControlSplitDropdown = 6,
    msoControlOCXDropdown = 7,
    msoControlGenericDropdown = 8,
    msoControlGraphicDropdown = 9,
    msoControlPopup = 10,
    msoControlGraphicPopup = 11,
    msoControlButtonPopup = 12,
    msoControlSplitButtonPopup = 13,
    msoControlSplitButtonMRUPopup = 14,
    msoControlLabel = 15,
    msoControlExpandingGrid = 16,
    msoControlSplitExpandingGrid = 17,
    msoControlGrid = 18,
    msoControlGauge = 19,
    msoControlGraphicCombo = 20,
    msoControlPane = 21,
    msoControlActiveX = 22,
    msoControlSpinner = 23,
    msoControlLabelEx = 24,
    msoControlWorkPane = 25,
    msoControlAutoCompleteCombo = 26
} MsoControlType;
typedef enum {
    msoButtonAutomatic = 0,
    msoButtonIcon = 1,
    msoButtonCaption = 2,
    msoButtonIconAndCaption = 3,
    msoButtonIconAndWrapCaption = 7,
    msoButtonIconAndCaptionBelow = 11,
    msoButtonWrapCaption = 14,
    msoButtonIconAndWrapCaptionBelow = 15
} MsoButtonStyle;

目前处理 MsoControlTypeMsoButtonStyle 类型定义需求最简单的方法是,将上述代码直接复制并粘贴到 Connect.cpp 源文件中。显然,除非在加载项过程中调用 AddMenuItemsRemoveMenuItems 方法,否则它们不会产生任何影响。因此,将以下行添加到 Connect.cpp 源文件中向导生成的 OnStartupComplete 方法中:

RemoveMenuItems();
AddMenuItems();
然后将以下行添加到向导生成的 OnBeginShutdown 方法中
RemoveMenuItems();
要编译和安装加载项以查看其目前的外观,请按以下步骤操作
  1. 编译 WordFindFacility 项目。为此,在“解决方案资源管理器”窗口中右键单击项目名称 WordFindFacility,然后选择“重新生成”。
  2. 编译 WordFindFacilitySetup 项目。为此,在“解决方案资源管理器”窗口中右键单击项目名称 WordFindFacilitySetup,然后选择“重新生成”。
  3. 安装加载项。为此,在“解决方案资源管理器”窗口中右键单击项目名称 WordFindFacilitySetup,然后选择“安装”。

现在,如果您启动 Microsoft Word,您会发现加载项确实已安装。添加的菜单项目前没有任何功能,但它至少在那里!此时我应该提到,如果您最终得到一个即使卸载加载项也无法消失的菜单栏选项,摆脱它的步骤如下。首先,在 Microsoft Word 中从主菜单中选择“工具”和“自定义...”。然后只需用鼠标左键抓住不需要的菜单栏选项,将其拖到显示的“自定义”对话框中,然后关闭对话框。

创建新对话框

好的,到目前为止,我们在 Microsoft Word 中有一个新菜单项,但它做得不多。是时候创建一个对话框了。因此,启动 Visual Studio,并在解决方案资源管理器窗口中右键单击 WordFindFacility 项目。从弹出菜单中选择“添加”和“类...”。在“添加类”对话框中,从树视图中选择“ATL”,然后选择“ATL 对话框”作为模板。按“添加”,键入“WordFind”作为短名称,然后按“完成”。就个人而言,我发现一些 ATL 向导将类方法的执行体放在头文件中的习惯非常令人恼火。以至于我有时会做的第一件事就是进行一些剪切和粘贴来纠正问题。我鼓励您进入 WordFind.hWordFind.cpp 并照做——头文件中只包含方法声明。现在从解决方案资源管理器切换到资源视图,并在树视图中双击带有标签 IDD_WORDFIND 的对话框。使用资源编辑器,直到您得到如下所示的内容:

“关闭”按钮只是默认的“确定”按钮,但标题已修改。“查找”按钮是新增的,其 ID 为 IDC_FIND,通过“属性”窗口设置。在“属性”窗口中,单击闪电按钮并添加一个方法来处理“查找”按钮的 BN_CLICK 事件。默认方法名称即可。编辑控件使用“属性”窗口设置 ID 为 IDC_WORD。现在为新对话框编写一些代码。首先,ATL 为其对话框控件提供了包装器,这里将用于编辑控件。因此,将以下行添加到 WordFind.h 头文件中的 CWordFind 类中。

private:
    CWindow m_cWord;

然后将以下行添加到 OnInitDialog 方法中,我相信现在它在 WordFind.cpp 源文件中。

m_cWord.Attach( GetDlgItem( IDC_WORD ) );

要搜索的 Microsoft Word 文档将在构造期间传递给 CWordFind 类,因此在 WordFind.h 文件顶部附近的其他包含声明中添加以下行:

#include "CDocument0.h""
在同一个文件中,修改 CWordFind 构造函数声明,使其显示如下
CWordFind( CDocument0* poDocument );
同样在同一个文件中,将以下行添加到 CWordFind 类中
private:
    CDocument0* m_poDocument;

最后,修改构造函数代码,我相信现在它在 WordFind.cpp 源文件中,使其显示如下:

CWordFind::CWordFind( CDocument0* poDocument )
{
    m_poDocument = poDocument;
}
在单词搜索过程中,需要维护表示当前单词索引和总单词数的变量。因此,需要在 WordFind.h 头文件顶部附近的其他 include 声明中添加以下行:
#include "CWords.h"

然后,需要将以下代码行添加到同一文件中的 CWordFind 类中。

private:
    long m_lTotalNumberOfWords;
    long m_lWordIndex;
    CWords m_oWords;

最后,需要将以下行添加到 WordFind.cpp 源文件中的 CWordFind 类构造函数中:

m_lWordIndex = 1;
m_oWords = poDocument->get_Words();
m_lTotalNumberOfWords = m_oWords.get_Count();
这里发生的事情相当简单。从提供的文档中获取一个表示文档中单词的对象。然后从“Words”对象中获取单词数量。在继续进行单词查找例程本身之前,还需要一个包含声明。这次在 WordFind.cpp 源文件中,需要在其他包含声明中添加以下行:
#include "CRange.h"
现在开始单词搜索业务。将以下行添加到 WordFind.cpp 源文件中的 OnBnClickedFind 方法中:
CString text;
m_cWord.GetWindowText( text );
while ( m_lWordIndex <= m_lTotalNumberOfWords )
{

    CRange oRange = m_oWords.Item( m_lWordIndex );
    m_lWordIndex++;
    CString word = oRange.get_Text();
    word.TrimRight();
    if ( text == word )
    {
        oRange.Select();
        return 0;
    }
}
::MessageBox( NULL, text + " Not Found", "Not Found", MB_OK|MB_ICONSTOP );

这里发生的第一件事是从编辑控件中检索文本。然后使用单词索引遍历文档,检索每个索引值引用的单词并将其与编辑控件中的文本进行比较。如果找到匹配项,则选择该单词使其变为黑色。如果文档被遍历而没有找到任何匹配项,则会给出错误消息。有人可能会说,所呈现的代码不难理解,但首先找出要使用的确切命令肯定很难。我在这方面唯一的建议在本文的主机 COM 接口帮助一节中给出。

创建新接口

奇怪的是,Visual Studio 似乎没有提供创建新接口的自动化方法。因此,首先要做的是为拟议的接口创建一个全局唯一 ID (GUID)。Microsoft 提供了一个工具来执行此操作。它位于“C:\Program Files\Microsoft Visual Studio 8\Common7\Tools”文件夹中,可执行文件是“_guidgen.exe_”。只需启动它,您立即就会得到一个新的 GUID。有了新的 GUID,回到 Visual Studio 并从解决方案资源管理器中打开文件 _AddIn.idl_。使用您自己的新 GUID,而不是我的,在导入语句之后立即将以下行添加到 _AddIn.idl_ 中:

[
    object,
    uuid( BABAF1C5-12A9-406b-A76C-49461A918286 ),
    dual,
    nonextensible,
    helpstring("IWordFindInterface Interface"),
    pointer_default(unique)
]
interface IWordFindInterface : IDispatch
{
};
在同一个文件中的 WordFindFacilityLib 库中的 Connect coclass 中,替换以下行
[default] interface IUnknown;
用这个
[default] interface IWordFindInterface;
现在编译项目,并在“类视图”中右键单击 CConnect 类。从弹出菜单中选择“添加”和“实现接口...”。刚刚在 AddIn.idl 文件中定义的 IWordFindInterface 接口现在将列在“实现接口向导”对话框中。按“>”按钮选择 IWordFindInterface,然后按“完成”。参考“类视图”窗口中的类列表,可以看到 IWordFindInterface 现在是一个条目。因此,右键单击 IWordFindInterface,然后从弹出菜单中选择“添加”和“添加方法...”。在“添加方法向导”对话框中,将新方法命名为“WordFind”,然后按“完成”。打开 Connect.cpp 源文件将发现新方法 WordFind 已添加到 CConnect 类中。此 WordFind 方法是 Microsoft Word 将通过 IWordFindInterface 调用以显示前面编码的对话框的方法。因此,将以下行添加到 WordFind 方法中:
try
{
    CDocument0 oActiveDocument = m_oWordApp.get_ActiveDocument();
    CWordFind dlg( &oActiveDocument );
    dlg.DoModal();
}
catch( DotOleException* e )
{
    DotOleErrors::OleErrorMessages(e);
}
catch( DotOleDispatchException* e )
{
    DotOleErrors::DispatchErrorMessages(e);
}
此代码需要一些额外的 include 声明,因此在 Connect.cpp 源文件顶部附近的其他 include 声明中,添加以下行:
#include "WordFind.h"
#include "CDocument0.h""
到目前为止,我可能过度使用局部变量来定义 COM 包装器类可能已被注意到。任何有 COM 经验的人都会听说过引用计数器。引用计数器决定了 COM 对象的生命周期。本文描述的 COM 包装器在隐藏所有这些引用计数器方面做得很好。然而,引用计数器的存在不能完全被忽略。因此,必须对 COM 包装器类的构造和销毁保持非常严格的控制。我发现大量使用局部变量是一种相当轻松的实现这种严格控制的方法。在开发共享加载项时,偶尔检查 Windows 任务管理器以确保宿主应用程序在您认为应该关闭时确实正在关闭是值得的。如果关闭没有按预期发生,并且您一直严格使用 COM 包装器类,则请检查您是否了解每个 COM 包装器类实例的生命周期。为确保 Microsoft Word 在执行回调时不会创建 CConnect 类的新实例,需要在 Connect.h 文件中的其他“DECLARE”宏中添加以下行:
DECLARE_CLASSFACTORY_SINGLETON(CConnect))

如果您在此阶段尝试编译,您会很快意识到向导生成的内容实际上无法编译。问题在于链接器找不到 _LIBID_WordFindFacilityLib GUID。那么解决方案就是在 Connect.h 文件中,替换这一行:

public IDispatchImpl<IWordFindInterface, &__uuidof(IWordFindInterface),
          &LIBID_WordFindFacilityLib, /* wMajor = */ 1, /* wMinor = */ 0>

用这一行

public IDispatchImpl<IWordFindInterface, &__uuidof(IWordFindInterface),
        &__uuidof(WordFindFacilityLib), /* wMajor = */ 1, /* wMinor = */ 0>

现在,您应该能够重新生成 WordFindFacilityWordFindFacilitySetup 项目并重新安装。至此,项目加载项端的工作就完成了。现在剩下的就是设置 Microsoft Word 实际进行调用。

一些 Visual Basic

如前所述,处理宿主应用程序回调最优雅的方式是使用事件接收器。它们肯定避免了在宿主端添加脚本的麻烦。然而,为了简单起见及其教学价值,我们在这里将在宿主端添加一个脚本。因此,启动 Microsoft Word,并从主菜单中选择“工具”、“宏”和“宏...”在“宏”对话框中按“编辑”。进入 Visual Basic 编辑器后,首先要做的是找到 WordFindFacility 类型库。从 Visual Basic 编辑器主菜单中选择“工具”和“引用...”。如果运气好,WordFindFacility 类型库将直接出现在“引用”对话框的“可用引用”列表中。如果是,只需勾选它并按“确定”。如果不是,则按“浏览...”并查找与 WordFindFacility 项目关联的 AddIn.tlb 文件。要确认 Visual Basic 编辑器确实知道 WordFindFacility,从主菜单中选择“视图”和“对象浏览器”。在“对象浏览器”对话框中,您应该能够选择 WordFindFacilityLib。完成此操作后,Connect 类及其 WordFind 方法应该会列出。要从 Visual Basic 编辑器主菜单中添加所需的宏,请选择“插入”和“过程...”在“添加过程”对话框中,键入“WordFindFacility”作为名称,将“类型”保留为“子”,然后按“确定”。将以下行添加到新创建的过程:

Dim oWordFind As Object
Set oWordFind = New WordFindFacilityLib.Connect
oWordFind.WordFind

所以现在,如果您回到 Microsoft Word 主窗口,输入一些文本,然后选择“我的菜单”和“查找...”,您的新单词查找功能应该可以运行了。

部署

现在您已经成功创建了一个加载项,毫无疑问您会希望有某种方式来分发它。在项目开发过程中,安装加载项只需右键单击 WordFindFacilitySetup 并选择“安装”即可。但是,如果您查看 Visual Studio 作为项目的一部分创建的 WordFindFacilitySetup 文件夹,您会发现 WordFindFacilitySetup 本身就是一个项目,包含自己的 Debug 和 Release 文件夹。在 Debug 或 Release 文件夹中是 WordFindFacilitySetup.msi 文件,这几乎是安装加载项所需的全部内容。msi 文件当然是一个 Windows 安装程序数据库文件,在这种情况下,它实际上嵌入了加载项的可执行代码。顺便说一句,Microsoft 实际上有一个用于创建和编辑 msi 文件的工具,名为 _orca.exe_。详细信息可在 http://msdn.microsoft.com/library/default.asp?url=/library/en-us/msi/setup/orca_exe.aspp 找到。

要删除加载项,需要使用通过“开始”按钮和“控制面板”访问的“添加或删除程序”。

© . All rights reserved.