本系列描述的是如何使用C++/COM来编写PowerPoint插件,使用的开发工具是 Visual Studio 2017。

前言

此处的“插件”,官方的翻译是“加载项”。一个是Addin, 一个是Plugin。

项目上需要实现一个Office/WPS都通用的插件,来实现业务系统与Office/WPS的联动。

那么实现Office/WPS的插件都有哪些方式呢?

Office插件的实现方式

COM

COM (Component Object Model, 组件对象模型)是微软的一套软件组件的二进制接口标准。

Office插件就是一个实现了IDTExtensibility2和IRibbonExtensibility两个接口的COM组件。IDTExtensibility2提供了Office插件接口,IRibbonExtensibility提供了Ribbon界面接口。

VSTO

VSTO (Visual Studio Tools for Office) 是指Visual Studio 为 Office开发人员提供的一系统工具。

通过VSTO, 可以方便的创建Office插件。从本质上讲, VTSO也是使用的COM。它使用 COM 包装器 (RCW) 通过托管 API 与 Office 进行通信。

Web

基于较新版本的Office, 提供了基于Web技术的Office加载项

COM 或 VSTO 加载项是旧 Office 集成解决方案,仅在 Windows 版 Office 上运行。与 COM 加载项不同,Office 加载项不涉及在用户设备或 Office 客户端中运行的代码。对于 Office 加载项,该应用程序(例如 Excel)会读取加载项清单,并挂钩 UI 中的加载项自定义功能区按钮和菜单命令。如果需要,它加载加载项的 JavaScript 和 HTML 代码,此代码在沙盒中的浏览器上下文范围内执行。

意思是Office内嵌了一个浏览器,自定义功能区中的按钮对应一个Web页面。页面与页面之间共享上下文,且提供了Office JavaScript API用来与Office进行通信。

WPS 插件的实现方式

复用 Office的COM插件

通过注册表的配置,WPS可以直接使用Office中COM实现的插件。

包括基于COM实现的插件以及VSTO实现的插件。

Web

Office加载项 类似, WPS也使用Web技术玩了一套插件机制。官方称为WPS加载项

不能说大概一致,只能说一模一样。

WPS内嵌了一个Chromium 开源浏览器,自定义功能区中的按钮对应一个Web页面。页面与页面之间共享上下文,且提供了wpsjs用来与WPS进行通信。

为什么使用COM

  • 项目上需要同时支持Office和WPS,这一条打枪了Web的方式
  • VSTO依赖.NET Framework,这可能带来部署的问题,如果部署时还需要安装一个.NET Framework,多不爽啊。打枪VSTO
  • 可以用C++开发

COM的缺点

  • 健壮性差。这是因为COM插件是与Office主进程在同一个进程间通信的,如果插件出什么问题,可能会影响到Office本身
  • 这是20年前的技术,文档少。

接下来我们就一步一步的实现第一个插件程序。

实现第一个插件程序

Step 1:创建一个ATL项目

  1. 打开 Visual Studio 2017

  2. 创建一个ATL项目

  3. 在ATL项目设置页,保持默认设置,点击确定

    确定后会生成初始工程代码,此时会看到该项目下有两个工程:NativePPTAddin和NativePPTAddinPS。

    NativePPTAddinPS中的PS是指Proxy/Stub,当我们开发的组件需要用到代理/存根时,会用到此工程。

    在这个例子中,此工程无用,后面所有的操作都是在NativePPTAddin工程进行的。

Step 2:添加一个ATL对象

  1. 右键点击NativePPTAddin工程,添加 -> 新建项。

  2. 选择 ATL 简单对象

  3. 在ATL简单对象的向导页中,ProgID中输入 NativePPTAddin.Connect

    点击完成后,工程中会生成Connect.h/Connect.cpp这两个文件。

Step 3:实现接口

  1. 切换到类视图,展开NativePPTAddin节点

  2. 右键CConnect节点,添加 -> 实现接口

  3. 在向导中,实现接口的位置选择注册表 ,可用类型库选择Microsoft Add-In Designer<1.0>

    _IDTExtensibility2 从可用接口框移动到实现接口中

  4. 点击确定,CConnect类已经继承了_IDTExtensibility2相关的接口。

    1
    2
    3
    4
    5
    class ATL_NO_VTABLE CConnect :
    public CComObjectRootEx<CComSingleThreadModel>,
    public CComCoClass<CConnect, &CLSID_Connect>,
    public IDispatchImpl<IConnect, &IID_IConnect, &LIBID_NativePPTAddinLib, /*wMajor =*/ 1, /*wMinor =*/ 0>,
    public IDispatchImpl<_IDTExtensibility2, &__uuidof(_IDTExtensibility2), &LIBID_AddInDesignerObjects, /* wMajor = */ 1, /* wMinor = */ 0>

    _IDTExtensibility2是所有Office插件必须要实现的接口,它定义了插件与Office之间的通信。

Step 4:启动PPT试一下

  1. 打开Connect.h

  2. 将所有方法中的E_NOTIMPL替换成S_OK

  3. 在OnConnection函数中添加如下代码进行测试

    1
    2
    ::MessageBoxW(NULL, L"OnConnection", L"Native PPT Addin", MB_OK);
    return S_OK;
  4. 打开资源文件中的Connect.rgs,在末尾添加注册表信息

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    HKCU
    {
    NoRemove Software
    {
    NoRemove Microsoft
    {
    NoRemove Office
    {
    NoRemove PowerPoint
    {
    NoRemove Addins
    {
    NativePPTAddin.Connect
    {
    val Description = s 'Native PPT Addin'
    val FriendlyName = s 'Native PPT Addin (ATL)'
    val LoadBehavior = d 3
    val CommandLineSafe = d 1
    }
    }
    }
    }
    }
    }
    }

PowerPoint在启动时会读取该注册表项的值,LoadBehavior 为 3表示在启动时自动加载。更详细的信息请参阅这里

此时,如果您构建项目,则会正确的注册表项将放入注册表中。

  1. 右键点击NativePPTAddin工程,点击属性,设置调试命令

    点击确定。

Step 5: 启动调试

  1. 启动调试,PowerPoint启动时,将会弹出一个消息框

下一篇我们将演示如何为在PowerPoint功能区添加一个Tab页,以及在Tab页添加按钮。

完整的代码在这里

Reference