You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

基于MinGW开发Office COM插件(IDTExtensibility2接口)技术咨询

用MinGW开发Office COM插件(基于IDTExtensibility2)的完整指南

我刚好折腾过用MinGW写Office COM插件,给你一步步捋清楚,应该能解决你的问题:

一、先搞定IDTExtensibility2等接口定义的问题

IDTExtensibility2属于Office的Extensibility类型库,你可以通过以下步骤生成对应的头文件:

  1. 找到Office的Extensibility类型库文件:
    • 对于Office 2016/2019/365,路径通常是 C:\Program Files\Microsoft Office\root\VFS\ProgramFilesCommonX86\Microsoft Shared\OFFICE16\MSOUTLB.TLB(版本号OFFICE16对应你的Office版本,比如2013是OFFICE15)
  2. 用MinGW的midl编译器生成头文件:
    打开MinGW命令行,切换到TLB文件所在目录,执行:
    midl /out . MSOUTLB.TLB
    
    这会生成MSOUTLB.h、MSOUTLB_i.c和MSOUTLB_p.c几个文件,其中MSOUTLB.h就包含了IDTExtensibility2的完整接口定义。
  3. 适配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. 编译与注册

  1. 打开MinGW命令行,切换到项目目录,执行:
    make
    
    会生成OfficeAddin.dll。
  2. 以管理员身份打开命令提示符,执行:
    regsvr32.exe OfficeAddin.dll
    
    注册成功会弹出提示框。

6. 测试插件

打开Word,依次点击「文件」→「选项」→「加载项」→「COM加载项」→「转到」,勾选「MinGW Office插件」并确定,下次启动Word就会弹出「插件已加载!」的提示。

小提示

  • 一定要生成自己的UUID:示例里的CLSID是随便写的,用uuidgen生成一个唯一的替换掉,避免冲突。
  • 适配其他Office软件:把注册表中的Word改成Excel/PowerPoint,就能做成对应软件的插件。
  • 调试:可以用gdb附加到Office进程调试,或者在代码里加OutputDebugStringW输出日志。

内容的提问来源于stack exchange,提问作者user2102508

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.29 07:08:25