Office 應用程式透過 COM 增益集機制實現功能擴展。增益集實作 _IDTExtensibility2 介面,經由登錄機碼被宿主應用程式 (Word/Excel/Outlook) 發現並載入。相較於 VBA 巨集或 VSTO,COM 增益集具備:效能優異 (原生代碼)、版本獨立 (二進位相容)、部署靈活 (註冊即用)、安全性可控 (代碼簽章) 等優勢。本文以 VC6.0 ATL COM 專案為基礎,詳細演示 Word 2003 增益集的完整開發流程,涵蓋專案建立、介面實作、登錄部署、類型庫匯入、UI 建立 (工具列/按鈕/圖示) 以及事件處理。
atlbase.h、atlcom.h 等)WordAddin。
Connect (自動生成 CConnect、IConnect、Connect 等)。

_IDTExtensibility2 介面CConnect 右鍵 → Implement Interface。msaddndr.dll 或 Office 安裝目錄)。



精靈自動生成五個空實作:
OnConnection:增益集載入時呼叫,建立 UI、初始化資源。OnDisconnection:卸載時呼叫,清理 UI、釋放資源。OnAddInsUpdate:增益集集合變更通知。OnStartupComplete:宿主啟動完成通知。OnBeginShutdown:宿主開始關閉通知。編輯專案的 .rgs 登錄腳本檔案,在 HKCU 下建立鍵值:
HKCU
{
Software
{
Microsoft
{
Office
{
Word
{
Addins
{
'WordAddin.Connect' // 【關鍵】ProgID:專案名.類別名
{
val FriendlyName = s 'Word Custom Addin'
val Description = s 'Word Custom Addin'
val LoadBehavior = d '3' // 0x03 = 載入時啟動 | 使用者手動啟動
val CommandLineSafe = d '1'
}
}
}
}
}
}
}
三大注意事項:
Office 下的子鍵決定宿主:Word/Excel/Outlook 等。在 stdafx.h 加入:
// Office 核心類型庫 (命令列、命令欄等)
#import "C:\\Program Files\\Common Files\\Microsoft Shared\\OFFICE11\\mso.dll" \
rename_namespace("Office") named_guids exclude("Pages")
using namespace Office;
// VBA 延伸類型庫 (選用,視需求)
#import "C:\\Program Files\\Common Files\\Microsoft Shared\\VBA\\VBA6\\VBE6EXT.olb" \
rename_namespace("VBE6")
using namespace VBE6;
// Word 應用程式類型庫
#import "C:\\Program Files\\Microsoft Office\\OFFICE11\\MSWORD.OLB" \
rename("ExitWindows", "ExitWindowsEx") \
rename_namespace("Word") raw_interfaces_only named_guids exclude("Pages")
using namespace Word;
路徑說明:OFFICE11 對應 Office 2003;2002 為 OFFICE10,2000 為 OFFICE9。實際部署時建議使用相對路徑或環境變數。
編譯一次驗證類型庫匯入是否成功 (生成 .tlh/.tli)。
OnConnection 建立自訂工具列與按鈕STDMETHODIMP CConnect::OnConnection(IDispatch* Application, ext_ConnectMode ConnectMode,
IDispatch* AddInInst, VARIANT* custom) {
// 1. 取得 Word Application 介面
CComQIPtr spApp(Application);
if (!spApp) return E_FAIL;
// 2. 取得命令欄集合
CComPtr spCmdBars;
HRESULT hr = spApp->get_CommandBars(&spCmdBars);
if (FAILED(hr)) return hr;
// 3. 建立新工具列
CComVariant vName("MyAddinToolbar");
CComVariant vPos(1); // msoBarTop
CComVariant vEmpty(DISP_E_PARAMNOTFOUND, VT_ERROR);
CComVariant vTrue(VARIANT_TRUE);
CComPtr spNewBar;
spNewBar = spCmdBars->Add(vName, vPos, vEmpty, vTrue);
// 4. 取得控制項集合並加入按鈕
CComPtr spControls;
spControls = spNewBar->GetControls();
CComVariant vType(1); // msoControlButton
CComPtr spCtrl;
spCtrl = spControls->Add(vType, vEmpty, vEmpty, vEmpty, vTrue);
// 5. 轉為按鈕介面並設定屬性
CComQIPtr spBtn(spCtrl);
spBtn->PutStyle(Office::msoButtonIconAndCaption);
spBtn->PutCaption(OLESTR("MyAddin"));
spBtn->PutEnabled(VARIANT_TRUE);
spBtn->PutTooltipText(OLESTR("測試增益集按鈕"));
spBtn->PutTag(OLESTR("MyAddinBtn"));
// 6. 設定按鈕圖示:從資源載入位圖 → 剪貼簿 → PasteFace
HBITMAP hBmp = (HBITMAP)::LoadImage(_Module.GetResourceInstance(),
MAKEINTRESOURCE(IDB_BITMAP), IMAGE_BITMAP, 0, 0, LR_LOADMAP3DCOLORS);
::OpenClipboard(NULL);
::EmptyClipboard();
::SetClipboardData(CF_BITMAP, (HANDLE)hBmp);
::CloseClipboard();
::DeleteObject(hBmp);
hr = spBtn->PasteFace();
if (FAILED(hr)) return hr;
// 7. 顯示工具列並保存按鈕指標供事件處理使用
spNewBar->PutVisible(VARIANT_TRUE);
m_spCmdButton = spBtn; // 類別成員:CComPtr m_spCmdButton;
return S_OK;
}

Office 按鈕事件為 _CommandBarButtonEvents 介面 (dispid 0x01 = Click)。ATL 使用 IDispEventSimpleImpl 處理發散介面事件。
class ATL_NO_VTABLE CConnect :
public CComObjectRootEx,
public CComCoClass,
public IDispatchImpl,
public IDispEventSimpleImpl<1, CConnect, &__uuidof(Office::_CommandBarButtonEvents)>
{
// ...
};
_ATL_FUNC_INFO)在 Connect.h:
extern _ATL_FUNC_INFO OnClickInfo;
在 Connect.cpp:
_ATL_FUNC_INFO OnClickInfo = { CC_STDCALL, VT_EMPTY, 2, { VT_DISPATCH, VT_BYREF | VT_BOOL } };
BEGIN_SINK_MAP(CConnect)
SINK_ENTRY_INFO(1, __uuidof(Office::_CommandBarButtonEvents), 0x01, OnClickButton, &OnClickInfo)
END_SINK_MAP()
// Connect.h
void __stdcall OnClickButton(IDispatch* pDispCtrl, VARIANT_BOOL* pCancelDefault);
// Connect.cpp
void __stdcall CConnect::OnClickButton(IDispatch* /*pDispCtrl*/, VARIANT_BOOL* /*pCancelDefault*/) {
MessageBox(NULL, _T("增益集按鈕被點擊!"), _T("WordAddin"), MB_OK | MB_ICONINFORMATION);
}
在 OnConnection 尾部:
// 1 是 SINK_MAP 中的 ID
DispEventAdvise(m_spCmdButton, &__uuidof(Office::_CommandBarButtonEvents));
在 OnDisconnection:
DispEventUnadvise(m_spCmdButton, &__uuidof(Office::_CommandBarButtonEvents));
OnDisconnection 清理資源STDMETHODIMP CConnect::OnDisconnection(ext_DisconnectMode RemoveMode, VARIANT* custom) {
// 斷開事件
if (m_spCmdButton) {
DispEventUnadvise(m_spCmdButton, &__uuidof(Office::_CommandBarButtonEvents));
m_spCmdButton.Release();
}
// 移除工具列 (可選,視 LoadBehavior 決定)
return S_OK;
}
regsvr32 WordAddin.dll。WINWORD.EXE,在 OnConnection 設中斷點驗證載入流程。regedit 確認 HKCU\Software\Microsoft\Office\Word\Addins\WordAddin.Connect 鍵值正確。| Office 版本 | 類型庫路徑關鍵字 | ProgID 範例 |
|---|---|---|
| Office 2000 (9.0) | OFFICE9 | WordAddin.Connect |
| Office XP (10.0) | OFFICE10 | WordAddin.Connect |
| Office 2003 (11.0) | OFFICE11 | WordAddin.Connect |
同一組原始碼,僅需調整 #import 路徑與註冊機碼的 Office 版本段,即可支援三個版本。建議使用安裝程式 (如 InstallShield) 自動偵測版本並寫入正確路徑。
SINK_ENTRY_INFO 的 ID/dispid 不符。LoadBehavior 非 3,或命令欄名稱衝突,或 PutVisible(VARIANT_TRUE) 遺漏。LR_LOADMAP3DCOLORS、剪貼簿操作失敗。OnDisconnection 中未 Release 介面指標、未 Unadvise 事件、順序錯誤。pDispCtrl 參數可 QueryInterface 取得 _CommandBarButton,進而獲取 Parent 命令欄與宿主視窗,實現上下文感知操作。透過 ATL COM 實作 _IDTExtensibility2,配合 Office 物件模型操作命令欄與應用程式邏輯,即可構建高效能、緊密整合的 Word 增益集。本文涵蓋從專案建立、介面實作、登錄部署、類型庫匯入、UI 建構到事件處理的完整閉環,並針對版本相容與常見錯誤給出實戰方案。掌握此技術棧,可輕鬆擴展至 Excel、Outlook 等所有 Office 宿主應用。