本系列描述的是如何使用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项目
打开 Visual Studio 2017
-
创建一个ATL项目
在ATL项目设置页,保持默认设置,点击确定
确定后会生成初始工程代码,此时会看到该项目下有两个工程:NativePPTAddin和NativePPTAddinPS。
NativePPTAddinPS中的PS是指Proxy/Stub,当我们开发的组件需要用到代理/存根时,会用到此工程。
在这个例子中,此工程无用,后面所有的操作都是在NativePPTAddin工程进行的。
Step 2:添加一个ATL对象
右键点击NativePPTAddin工程,添加 -> 新建项。
选择 ATL 简单对象
- 在ATL简单对象的向导页中,ProgID中输入 NativePPTAddin.Connect
点击完成后,工程中会生成Connect.h/Connect.cpp这两个文件。
Step 3:实现接口
切换到类视图,展开NativePPTAddin节点
右键CConnect节点,添加 -> 实现接口
- 在向导中,实现接口的位置选择注册表 ,可用类型库选择Microsoft Add-In Designer<1.0>
将_IDTExtensibility2 从可用接口框移动到实现接口中
-
点击确定,CConnect类已经继承了_IDTExtensibility2相关的接口。
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试一下
打开
Connect.h
将所有方法中的E_NOTIMPL替换成S_OK
-
在OnConnection函数中添加如下代码进行测试
::MessageBoxW(NULL, L"OnConnection", L"Native PPT Addin", MB_OK); return S_OK;
-
打开资源文件中的Connect.rgs,在末尾添加注册表信息
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表示在启动时自动加载。更详细的信息请参阅这里。
此时,如果您构建项目,则会正确的注册表项将放入注册表中。
右键点击NativePPTAddin工程,点击属性,设置调试命令
点击确定。
Step 5: 启动调试
- 启动调试,PowerPoint启动时,将会弹出一个消息框
下一篇我们将演示如何为在PowerPoint功能区添加一个Tab页,以及在Tab页添加按钮。
完整的代码在这里。