基于MinGW开发Office COM插件(IDTExtensibility2接口)技术咨询
用MinGW开发Office COM插件(基于IDTExtensibility2)的完整指南
我刚好折腾过用MinGW写Office COM插件,给你一步步捋清楚,应该能解决你的问题:
一、先搞定IDTExtensibility2等接口定义的问题
IDTExtensibility2属于Office的Extensibility类型库,你可以通过以下步骤生成对应的头文件:
- 找到Office的Extensibility类型库文件:
- 对于Office 2016/2019/365,路径通常是
C:\Program Files\Microsoft Office\root\VFS\ProgramFilesCommonX86\Microsoft Shared\OFFICE16\MSOUTLB.TLB(版本号OFFICE16对应你的Office版本,比如2013是OFFICE15)
- 对于Office 2016/2019/365,路径通常是
- 用MinGW的
midl编译器生成头文件:
打开MinGW命令行,切换到TLB文件所在目录,执行:
这会生成midl /out . MSOUTLB.TLBMSOUTLB.h、MSOUTLB_i.c和MSOUTLB_p.c几个文件,其中MSOUTLB.h就包含了IDTExtensibility2的完整接口定义。 - 适配MinGW语法(基本不用改):
生成的头文件里的__declspec(uuid(...))语法MinGW完全支持,只要把#include <windows.h>放在最前面就能正常编译。
二、MinGW创建Office COM-DLL的完整示例
下面是一个最小化的可运行插件实现,你可以直接抄来改:
1. 项目文件结构
OfficeAddin/ ├─ OfficeAddin.cpp # 核心实现代码 ├─ OfficeAddin.def # 导出函数定义 └─ Makefile # 编译脚本
2. OfficeAddin.cpp 代码
#define WIN32_LEAN_AND_MEAN #include <windows.h> #include "MSOUTLB.h" // 刚才生成的头文件 // 给你的插件生成一个唯一UUID(用MinGW的uuidgen工具生成新的) static const GUID CLSID_OfficeAddin = {0x12345678, 0xABCD, 0xEF01, {0x23,0x45,0x67,0x89,0xAB,0xCD,0xEF,0x01}}; class COfficeAddin : public IDTExtensibility2 { private: long m_cRef; // 引用计数 public: COfficeAddin() : m_cRef(1) {} ~COfficeAddin() {} // IUnknown 接口实现 STDMETHODIMP QueryInterface(REFIID riid, void** ppvObject) override { if (riid == IID_IUnknown || riid == IID_IDTExtensibility2) { *ppvObject = static_cast<IDTExtensibility2*>(this); AddRef(); return S_OK; } *ppvObject = nullptr; return E_NOINTERFACE; } STDMETHODIMP_(ULONG) AddRef() override { return InterlockedIncrement(&m_cRef); } STDMETHODIMP_(ULONG) Release() override { ULONG cRef = InterlockedDecrement(&m_cRef); if (cRef == 0) delete this; return cRef; } // IDTExtensibility2 接口实现 STDMETHODIMP OnConnection(IDispatch* Application, ext_ConnectMode ConnectMode, IDispatch* AddInInst, SAFEARRAY** custom) override { // 插件加载时触发,这里写你的初始化逻辑 MessageBox(nullptr, L"插件已加载!", L"提示", MB_OK); return S_OK; } STDMETHODIMP OnDisconnection(ext_DisconnectMode RemoveMode, SAFEARRAY** custom) override { // 插件卸载时触发 return S_OK; } STDMETHODIMP OnAddInsUpdate(SAFEARRAY** custom) override { return S_OK; } STDMETHODIMP OnStartupComplete(SAFEARRAY** custom) override { return S_OK; } STDMETHODIMP OnBeginShutdown(SAFEARRAY** custom) override { return S_OK; } }; // COM 类工厂实现 class CAddinClassFactory : public IClassFactory { private: long m_cRef; public: CAddinClassFactory() : m_cRef(1) {} ~CAddinClassFactory() {} STDMETHODIMP QueryInterface(REFIID riid, void** ppvObject) override { if (riid == IID_IUnknown || riid == IID_IClassFactory) { *ppvObject = static_cast<IClassFactory*>(this); AddRef(); return S_OK; } *ppvObject = nullptr; return E_NOINTERFACE; } STDMETHODIMP_(ULONG) AddRef() override { return InterlockedIncrement(&m_cRef); } STDMETHODIMP_(ULONG) Release() override { ULONG cRef = InterlockedDecrement(&m_cRef); if (cRef == 0) delete this; return cRef; } STDMETHODIMP CreateInstance(IUnknown* pUnkOuter, REFIID riid, void** ppvObject) override { if (pUnkOuter != nullptr) return CLASS_E_NOAGGREGATION; COfficeAddin* pAddin = new COfficeAddin(); if (!pAddin) return E_OUTOFMEMORY; HRESULT hr = pAddin->QueryInterface(riid, ppvObject); pAddin->Release(); return hr; } STDMETHODIMP LockServer(BOOL fLock) override { return S_OK; } }; // DLL 导出函数 extern "C" { HRESULT __stdcall DllGetClassObject(REFCLSID rclsid, REFIID riid, void** ppvObject) { if (rclsid != CLSID_OfficeAddin) return CLASS_E_CLASSNOTAVAILABLE; CAddinClassFactory* pFactory = new CAddinClassFactory(); if (!pFactory) return E_OUTOFMEMORY; HRESULT hr = pFactory->QueryInterface(riid, ppvObject); pFactory->Release(); return hr; } HRESULT __stdcall DllRegisterServer() { // 注册COM组件到注册表 wchar_t szModule[MAX_PATH]; GetModuleFileNameW(nullptr, szModule, MAX_PATH); // 注册CLSID wchar_t szCLSID[40]; StringFromGUID2(CLSID_OfficeAddin, szCLSID, 40); wchar_t szKey[256]; swprintf_s(szKey, L"CLSID\\%s", szCLSID); HKEY hKey; RegCreateKeyW(HKEY_CLASSES_ROOT, szKey, &hKey); RegSetValueExW(hKey, nullptr, 0, REG_SZ, (BYTE*)L"MinGW Office Addin", sizeof(L"MinGW Office Addin")); RegCloseKey(hKey); swprintf_s(szKey, L"CLSID\\%s\\InprocServer32", szCLSID); RegCreateKeyW(HKEY_CLASSES_ROOT, szKey, &hKey); RegSetValueExW(hKey, nullptr, 0, REG_SZ, (BYTE*)szModule, (wcslen(szModule)+1)*2); RegSetValueExW(hKey, L"ThreadingModel", 0, REG_SZ, (BYTE*)L"Apartment", sizeof(L"Apartment")); RegCloseKey(hKey); // 注册为Word加载项(改成Excel就是Excel插件) swprintf_s(szKey, L"Software\\Microsoft\\Office\\Word\\AddIns\\MinGW.Addin"); RegCreateKeyW(HKEY_CURRENT_USER, szKey, &hKey); RegSetValueExW(hKey, L"Description", 0, REG_SZ, (BYTE*)L"MinGW开发的Office插件", sizeof(L"MinGW开发的Office插件")); RegSetValueExW(hKey, L"FriendlyName", 0, REG_SZ, (BYTE*)L"MinGW Office插件", sizeof(L"MinGW Office插件")); RegSetValueExW(hKey, L"LoadBehavior", 0, REG_DWORD, (BYTE*)&(DWORD){3}, sizeof(DWORD)); // 3=启动时加载 RegCloseKey(hKey); return S_OK; } HRESULT __stdcall DllUnregisterServer() { // 注销COM组件 wchar_t szCLSID[40]; StringFromGUID2(CLSID_OfficeAddin, szCLSID, 40); wchar_t szKey[256]; swprintf_s(szKey, L"CLSID\\%s", szCLSID); RegDeleteTreeW(HKEY_CLASSES_ROOT, szKey); // 注销Office加载项 swprintf_s(szKey, L"Software\\Microsoft\\Office\\Word\\AddIns\\MinGW.Addin"); RegDeleteTreeW(HKEY_CURRENT_USER, szKey); return S_OK; } HRESULT __stdcall DllCanUnloadNow() { // 简单实现,实际项目需要跟踪全局引用计数 return S_OK; } }
3. OfficeAddin.def 文件
LIBRARY "OfficeAddin" EXPORTS DllGetClassObject @1 DllRegisterServer @2 DllUnregisterServer @3 DllCanUnloadNow @4
4. Makefile 文件
CC = g++ CFLAGS = -Wall -O2 -DWIN32 -D_WIN32 -DUNICODE -D_UNICODE LDFLAGS = -shared -lole32 -loleaut32 -luuid all: OfficeAddin.dll OfficeAddin.dll: OfficeAddin.cpp OfficeAddin.def $(CC) $(CFLAGS) -c OfficeAddin.cpp -o OfficeAddin.o $(CC) $(LDFLAGS) OfficeAddin.o -o OfficeAddin.dll -def:OfficeAddin.def clean: rm -f OfficeAddin.o OfficeAddin.dll
5. 编译与注册
- 打开MinGW命令行,切换到项目目录,执行:
会生成makeOfficeAddin.dll。 - 以管理员身份打开命令提示符,执行:
注册成功会弹出提示框。regsvr32.exe OfficeAddin.dll
6. 测试插件
打开Word,依次点击「文件」→「选项」→「加载项」→「COM加载项」→「转到」,勾选「MinGW Office插件」并确定,下次启动Word就会弹出「插件已加载!」的提示。
小提示
- 一定要生成自己的UUID:示例里的CLSID是随便写的,用
uuidgen生成一个唯一的替换掉,避免冲突。 - 适配其他Office软件:把注册表中的
Word改成Excel/PowerPoint,就能做成对应软件的插件。 - 调试:可以用gdb附加到Office进程调试,或者在代码里加
OutputDebugStringW输出日志。
内容的提问来源于stack exchange,提问作者user2102508
相关产品推荐
相关产品推荐

