本系列描述的是如何使用C++/COM來編寫PowerPoint插件,使用的開發(fā)工具是 Visual Studio 2017。
Step 1:清理 pch.h
pch.h是預編譯頭文件,如果你在更老的Visual Studio版本上進行開發(fā),這個文件可能是StdAfx.h
-
刪除
pch.h中原來的import語句,原來的import語句長這樣#import "C:\Program Files (x86)\Common Files\Designer\MSADDNDR.DLL" raw_interfaces_only, raw_native_types, no_namespace, named_guids, auto_search -
加入下面的代碼
#import "libid:AC0714F2-3D04-11D1-AE7D-00A0C90F26F4" \ auto_rename auto_search raw_interfaces_only rename_namespace("AddinDesign") // Office type library (i.e., mso.dll). #import "libid:2DF8D04C-5BFA-101B-BDE5-00AA0044DE52" \ auto_rename auto_search raw_interfaces_only rename_namespace("Office") using namespace AddinDesign; using namespace Office;
Step 2:修改 Connect.h,實現(xiàn)IRibbonExtensibility接口
-
我們添加一些typedef來簡化代碼的編寫,再加上IRibbonExtensibility接口的實現(xiàn)
大概長這樣:
typedef IDispatchImpl<_IDTExtensibility2, &__uuidof(_IDTExtensibility2), &__uuidof(__AddInDesignerObjects), /* wMajor = */ 1> IDTImpl; typedef IDispatchImpl<IRibbonExtensibility, &__uuidof(IRibbonExtensibility), &__uuidof(__Office), /* wMajor = */ 2, /* wMinor = */ 5> RibbonImpl; // CConnect 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 IDTImpl, public RibbonImpl -
添加下面的代碼到 ATL COM MAP
COM_INTERFACE_ENTRY(IRibbonExtensibility) -
實現(xiàn)IRibbonExtensibility接口
STDMETHOD(GetCustomUI)(BSTR RibbonID, BSTR * RibbonXml) { if(!RibbonXml) return E_POINTER; *RibbonXml = CComBSTR("XML GOES HERE"); return S_OK; }這個接口的輸出參數(shù)RibbonXml是用來定義功能區(qū)Tab頁的UI,格式是XML。
Step 3:添加XML到工程
- 右鍵NativePPTAddin工程,添加->新建項,選擇XML

-
打開剛剛添加的
RibbonManifest.xml,輸入以下內容<?xml version="1.0" encoding="gb2312"?> <customUI xmlns="http://schemas.microsoft.com/office/2006/01/customui"> <ribbon> <tabs> <tab id="NativePPTAddinTab" label="Native測試"> <group id="userGroup" label="用戶"> <button id="loginButton" screentip="登錄" label="登錄" size="large" imageMso="WebPagePreview" onAction="ButtonClicked" /> </group> <group id="actionGroup" label="操作"> <button id="uploadButton" screentip="上傳" label="上傳" size="large" imageMso="WebPagePreview" onAction="ButtonClicked" /> </group> </tab> </tabs> </ribbon> </customUI>在此XML中,我們定義了兩個組(用戶和操作),在每個組中都有一個按鈕。
Step 4:實現(xiàn)GetCustomUI接口
在實現(xiàn)之前,我們先將Connect.h中函數(shù)定義的位置轉移到Connect.cpp。(光標移到函數(shù)名,Alt+Enter -> 移動定義位置)
切換到資源視圖,展開NativePPTAddin節(jié)點,在NativePPTAddin.rc節(jié)點右鍵,添加資源
在彈出的對話框中選擇導入
在文件選擇對話框中,將文件類型改為所有文件。然后選擇剛剛創(chuàng)建的
RibbonManifest.xml此時會彈出自定義資源類型對話框,輸入XML

-
添加下面的代碼到Connect.cpp用來處理XML文件
namespace { HRESULT HrGetResource(int nId, LPCTSTR lpType, LPVOID* ppvResourceData, DWORD* pdwSizeInBytes) { HMODULE hModule = _AtlBaseModule.GetModuleInstance(); if (!hModule) return E_UNEXPECTED; HRSRC hRsrc = FindResource(hModule, MAKEINTRESOURCE(nId), lpType); if (!hRsrc) return HRESULT_FROM_WIN32(GetLastError()); HGLOBAL hGlobal = LoadResource(hModule, hRsrc); if (!hGlobal) return HRESULT_FROM_WIN32(GetLastError()); *pdwSizeInBytes = SizeofResource(hModule, hRsrc); *ppvResourceData = LockResource(hGlobal); return S_OK; } BSTR GetXMLResource(int nId) { LPVOID pResourceData = NULL; DWORD dwSizeInBytes = 0; HRESULT hr = HrGetResource(nId, TEXT("XML"), &pResourceData, &dwSizeInBytes); if (FAILED(hr)) return NULL; // Assumes that the data is not stored in Unicode. CComBSTR cbstr(dwSizeInBytes, reinterpret_cast<LPCSTR>(pResourceData)); return cbstr.Detach(); } SAFEARRAY* GetOFSResource(int nId) { LPVOID pResourceData = NULL; DWORD dwSizeInBytes = 0; if (FAILED(HrGetResource(nId, TEXT("OFS"), &pResourceData, &dwSizeInBytes))) return NULL; SAFEARRAY* psa; SAFEARRAYBOUND dim = { dwSizeInBytes, 0 }; psa = SafeArrayCreate(VT_UI1, 1, &dim); if (psa == NULL) return NULL; BYTE* pSafeArrayData; SafeArrayAccessData(psa, (void**)&pSafeArrayData); memcpy((void*)pSafeArrayData, pResourceData, dwSizeInBytes); SafeArrayUnaccessData(psa); return psa; } } // End of anonymous namespace -
修改GetCustomUI函數(shù)的實現(xiàn)
STDMETHODIMP_(HRESULT __stdcall) CConnect::GetCustomUI(BSTR RibbonID, BSTR * RibbonXml) { if (!RibbonXml) return E_POINTER; *RibbonXml = GetXMLResource(IDR_XML1); return S_OK; } 驗證一下。啟動調試我們將看到功能區(qū)有我們新加的Tab頁。

Step 5:實現(xiàn)按鈕點擊事件
-
打開 NativePPTAddin.idl 文件
IDL是用來描述軟件組件接口的一種計算機語言。IDL通過一種獨立于編程語言的方式來描述接口,使得在不同平臺上運行的對象和用不同語言編寫的程序可以相互通信交流;比如,一個組件用C++寫成,另一個組件用Java寫成。
-
添加接口。代碼大概長這樣
[ object, uuid(CE895442-9981-4315-AA85-4B9A5C7739D8), dual, nonextensible, helpstring("IRibbonCallback Interface"), pointer_default(unique) ] interface IRibbonCallback : IDispatch { [id(0x4000), helpstring("Button Callback")] HRESULT ButtonClicked([in]IDispatch* pControl); }; -
修改NativePPTAddin.idl中Connect的定義為
coclass Connect { interface IConnect; [default] interface IRibbonCallback; }; -
實現(xiàn)IRibbonCallback接口
在Connect.h中添加如下typedef
typedef IDispatchImpl<IRibbonCallback, &__uuidof(IRibbonCallback)> CallbackImpl;同時修改Connect類的繼承關系
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 IDTImpl, public RibbonImpl, public CallbackImpl添加接口到ATL COM MAP
BEGIN_COM_MAP(CConnect) COM_INTERFACE_ENTRY2(IDispatch, IRibbonCallback) COM_INTERFACE_ENTRY(IConnect) COM_INTERFACE_ENTRY2(IDispatch, _IDTExtensibility2) COM_INTERFACE_ENTRY(_IDTExtensibility2) COM_INTERFACE_ENTRY(IRibbonExtensibility) COM_INTERFACE_ENTRY(IRibbonCallback) END_COM_MAP() -
實現(xiàn)ButtonClicked接口
STDMETHODIMP_(HRESULT __stdcall) CConnect::ButtonClicked(IDispatch * ribbon) { MessageBoxW(NULL, L"Button Clicked!", L"NativePPTAddin", MB_OK); return S_OK; } 驗證一下。啟動調試后我們點擊按鈕,將會彈出消息框。

-
修改一下ButtonClicked函數(shù),實現(xiàn)不同按鈕點擊彈出不同的消息框。
STDMETHODIMP_(HRESULT __stdcall) CConnect::ButtonClicked(IDispatch * control) { CComQIPtr<IRibbonControl> ribbonCtl(control); CComBSTR idStr; WCHAR msg[64]; if (ribbonCtl->get_Id(&idStr) != S_OK) return S_FALSE; if (idStr == OLESTR("loginButton")) { swprintf_s(msg, L"I am loginButton"); } else if (idStr == OLESTR("uploadButton")) { swprintf_s(msg, L"I am uploadButton"); } MessageBoxW(NULL, msg, L"NativePPTAddin", MB_OK); return S_OK; }
下一篇,我們將描述如何使用其他鉤子來實現(xiàn)按鈕的可見性、圖片、顯示文字等功能。
完整的代碼在這里。