前两天,在CSDN瞎逛悠,见一老兄问到此问,却没有人作答(顶的人倒还不少,国内的论坛是不是都这样?),还发了些牢骚,俺也顺便跟着发了点牢骚:)
于是坐下来静下心研究了一下,今日终于成了正果,不敢私吞成果,特搬弄出来,让大家分享分享(切,无非就是虚荣而已啦,把自己说得那么伟大?!)!

点击这里下载工程源代码

我看还是做一篇教程写好了,写清楚一点,呵呵:)
哦,先说明白,俺用的是VC6啊(俺的工程是以Outlook插件为例的,因为CSDN上那位老兄问的就是Outlook,所以咱就对症下药啦,不过Office系列的插件都大同小异啦,只是做具体功能时才会天差地别哦):
1. 新建ATL工程,全部默认,完成Wizard。
2. 新建ATL Object,选择Simple Object,其他默认。
3. 打开stdafx.h,这里需要导入几个库:
#import "C:\Documents and Settings\Administrator\My Documents\msoffice9\mso9.dll" rename_namespace("Office")
using namespace Office;

#import "C:\Program Files\Common Files\Microsoft Shared\VBA\VBA6\VBE6EXT.OLB" rename_namespace("VBE6")
using namespace VBE6;

#import "C:\Documents and Settings\Administrator\My Documents\msoffice9\MSOUTL9.OLB" named_guids,rename_namespace("MSOutlook")
using namespace MSOutlook;

4. 打开.rgs文件,需要添加几行注册脚本:
HKCU
{
 NoRemove Software
 {
  NoRemove Microsoft
  {
   NoRemove Office
   {
    NoRemove Outlook
    {
     NoRemove Addins
     {
      'OutlookAddin.COutlookAddinSample'
      {
       val FriendlyName = s 'Azhi sample'
       val Description = s 'Azhi sample'
       val LoadBehavior = d '00000003'
       val CommandLineSafe = d '00000000'
      }
     }
    }
   }
  }
 }
}
这里注意了:OutlookAddin.COutlookAddinSample这玩意是该COM的ProgID。

5. 现在打开COutlookAddinSample.h文件。
a. 先引入一个库:
#import "C:\Program Files\Common Files\Designer\MSADDNDR.DLL" raw_interfaces_only, raw_native_types, no_namespace, named_guids

b. 在接口(类)定义中添加对_IDTExtensibility2接口的支持:
class ATL_NO_VTABLE CCOutlookAddinSample : 
 . 
 .
 public IDispatchImpl<_IDTExtensibility2, &IID__IDTExtensibility2, &LIBID_AddInDesignerObjects>
 .

c. 添加基于_IDTExtensibility2接口方法的重载:
.
// _IDTExtensibility2
public:
 STDMETHOD(OnConnection)(IDispatch * Application, ext_ConnectMode ConnectMode, IDispatch * AddInInst, SAFEARRAY * * custom);
 STDMETHOD(OnDisconnection)(ext_DisconnectMode RemoveMode, SAFEARRAY * * custom);
 STDMETHOD(OnAddInsUpdate)(SAFEARRAY * * custom);
 STDMETHOD(OnStartupComplete)(SAFEARRAY * * custom);
 STDMETHOD(OnBeginShutdown)(SAFEARRAY * * custom);
 .
 .

d. 定义一个变量保存Outlook的Application实例:
private:
 CComPtr<MSOutlook::_Application> m_spApp;

e. 修改COM_MAP映射表:
 .
// COM_INTERFACE_ENTRY(IDispatch) // 删除此行,以下两行皆为新添加的
 COM_INTERFACE_ENTRY2(IDispatch, ICOutlookAddinSample)
 COM_INTERFACE_ENTRY(_IDTExtensibility2)
 .
 .
至此,COutlookAddinSample.h文件的第一轮修改已经完成。
好了,我们接着改(为我们的COM按钮添加事件响应):
f. 回到文件顶部,添加申明:
extern _ATL_FUNC_INFO OnClickButtonInfo1;
extern _ATL_FUNC_INFO OnClickButtonInfo2;

g. 回到类定义处,添加对按钮事件的支持:
class ATL_NO_VTABLE CCOutlookAddinSample : 
 . 
 .
 public IDispEventSimpleImpl<1,CCOutlookAddinSample,&__uuidof(Office::_CommandBarButtonEvents)>,
 public IDispEventSimpleImpl<2,CCOutlookAddinSample,&__uuidof(Office::_CommandBarButtonEvents)>
 .

h. 在类中添加类型定义:
 typedef IDispEventSimpleImpl<1,CCOutlookAddinSample, &__uuidof(Office::_CommandBarButtonEvents)> CommandButtonEvents1;
 typedef IDispEventSimpleImpl<2,CCOutlookAddinSample, &__uuidof(Office::_CommandBarButtonEvents)> CommandButtonEvents2;

i. 添加事件连接映射:
BEGIN_SINK_MAP(CCOutlookAddinSample)
 SINK_ENTRY_INFO(1,__uuidof(Office::_CommandBarButtonEvents),/*dispid*/ 0x01, OnClickButton1, &OnClickButtonInfo1)
 SINK_ENTRY_INFO(2,__uuidof(Office::_CommandBarButtonEvents),/*dispid*/ 0x01, OnClickButton2, &OnClickButtonInfo2)
END_SINK_MAP()

j. 添加两个方法以响应按钮事件:
// ICOutlookAddinSample
public:
 VOID __stdcall OnClickButton1(IDispatch * /*Office::_CommandBarButton**/ Ctrl,VARIANT_BOOL * CancelDefault);
 VOID __stdcall OnClickButton2(IDispatch * /*Office::_CommandBarButton**/ Ctrl,VARIANT_BOOL * CancelDefault);

k. 定义两个变量保存按钮实例:
private:
 .
 .
 CComPtr<Office::_CommandBarButton> m_spButton1;
 CComPtr<Office::_CommandBarButton> m_spButton2;
 .
恩,至此该文件就全部修改完毕了(别得意哦,下面还有很多事要做啊)。

6. 打开COutlookAddinSample.cpp文件。
a. 定义事件连接变量:
_ATL_FUNC_INFO OnClickButtonInfo1 = {CC_STDCALL,VT_EMPTY,2,{VT_DISPATCH,VT_BYREF | VT_BOOL}};
_ATL_FUNC_INFO OnClickButtonInfo2 = {CC_STDCALL,VT_EMPTY,2,{VT_DISPATCH,VT_BYREF | VT_BOOL}};

b. 添加按钮事件响应方法:
VOID __stdcall CCOutlookAddinSample::OnClickButton1(IDispatch* /*Office::_CommandBarButton* */ Ctrl,VARIANT_BOOL * CancelDefault)
{
 MessageBox(GetForegroundWindow(),"¹þ¹þ£¬²âÊÔ°´Å¥£¡","sample",MB_OK | MB_ICONINFORMATION);
}

VOID __stdcall CCOutlookAddinSample::OnClickButton2(IDispatch* /*Office::_CommandBarButton* */ Ctrl,VARIANT_BOOL * CancelDefault)
{
 MessageBox(GetForegroundWindow(),"¹þ¹þ£¬²âÊÔµ¯³ö²Ëµ¥£¡","sample",MB_OK | MB_ICONINFORMATION);
}

c. 添加继承自_IDTExtensibility2接口的方法:
STDMETHODIMP CCOutlookAddinSample::OnConnection(IDispatch * Application, ext_ConnectMode ConnectMode, IDispatch * AddInInst, SAFEARRAY * * custom)
{
 return S_OK;
}

STDMETHODIMP CCOutlookAddinSample::OnDisconnection(ext_DisconnectMode RemoveMode, SAFEARRAY * * custom)
{
 return S_OK;
}
STDMETHODIMP CCOutlookAddinSample::OnAddInsUpdate(SAFEARRAY * * custom)
{
 return E_NOTIMPL;
}

STDMETHODIMP CCOutlookAddinSample::OnStartupComplete(SAFEARRAY * * custom)
{
 return E_NOTIMPL;
}

STDMETHODIMP CCOutlookAddinSample::OnBeginShutdown(SAFEARRAY * * custom)
{
 return E_NOTIMPL;
}
休息一下,到这里,这个插件的基本框架已经搭建完毕。

以下我们接着完成按钮和弹出菜单的创建以及按钮事件到方法的连接:
e. 在OnConnection方法中首先获得CommandBars接口指针
 .
 CComQIPtr<MSOutlook::_Application> spApp(Application);
 ATLASSERT(spApp);

 CComPtr<MSOutlook::_Explorer> spExplorer;
 spExplorer = spApp->ActiveExplorer();
 ATLASSERT(spExplorer);

 CComPtr<Office::_CommandBars> spCmdBars;
 HRESULT  hr = spExplorer->get_CommandBars(&spCmdBars);
 .
 .
在Outlook中比Word以及Excel都要多用一个方法才能得到CommandBars。
在Word以及Excel中直接可以通过Application对象获得:spApp->get_CommandBars(&spCmdBars);

f. 接着创建自己的CommandBar:
 CComVariant vName("sample");
 CComVariant vPos(Office::msoBarTop);
 CComVariant vTemp(VARIANT_TRUE);
 CComVariant vEmpty(DISP_E_PARAMNOTFOUND,VT_ERROR);
 CComPtr<Office::CommandBar>  spNewCmdBar;

 spNewCmdBar = spCmdBars->Add(vName, vPos, vEmpty, vTemp);

 // 得到CommandBar的Controls集合,通过集合的Add方法添加自己的按钮以及其他
 CComPtr<Office::CommandBarControls> spBarControls;
 spBarControls = spNewCmdBar->GetControls();
 ATLASSERT(spBarControls);

g. 为自己的CommandBar添加按钮:
 CComVariant vToolBarType(Office::msoControlButton);
 CComVariant vShow(VARIANT_TRUE);
 CComPtr<Office::CommandBarControl> spNewBar;

 spNewBar = spBarControls->Add(vToolBarType, vEmpty, vEmpty, vEmpty, vShow);
 ATLASSERT(spNewBar);

 CComQIPtr<Office::_CommandBarButton> spCmdButton1(spNewBar);
 ATLASSERT(spCmdButton1);

 spCmdButton1->PutStyle(Office::msoButtonIconAndCaption);
 spCmdButton1->PutVisible(VARIANT_TRUE);
 spCmdButton1->PutCaption("sample1");
 spCmdButton1->PutEnabled(VARIANT_TRUE);
 spCmdButton1->PutTooltipText("sample1");
 spCmdButton1->PutTag("sample1");

 HBITMAP hBmp = (HBITMAP)LoadImage(_Module.GetResourceInstance(),MAKEINTRESOURCE(IDB_SAMPLE),IMAGE_BITMAP,0,0,LR_LOADMAP3DCOLORS);
 ::OpenClipboard(NULL);
 ::EmptyClipboard();
 ::SetClipboardData(CF_BITMAP,(HANDLE)hBmp);
 ::CloseClipboard();
 ::DeleteObject(hBmp);
 hr = spCmdButton1->PasteFace();
 if(FAILED(hr))return hr;

h. 好了,我们就要创建弹出菜单了,看好了:
// 首先我们需要创建一个Popup类型的CommandBarButton
// 实际上微软称他为:CommandBarPopup

 CComVariant vPopupType(Office::msoControlPopup);
 spNewBar = spBarControls->Add(vPopupType, vEmpty, vEmpty, vEmpty, vShow);
 ATLASSERT(spNewBar);

 CComQIPtr<Office::CommandBarPopup> spNewPopup(spNewBar);
 ATLASSERT(spNewPopup);

 spNewPopup->PutVisible(VARIANT_TRUE);
 spNewPopup->PutCaption("popup");
 spNewPopup->PutEnabled(VARIANT_TRUE);
 spNewPopup->PutTooltipText("popup");
 spNewPopup->PutTag("popup");

 // 同样可以有自己的Controls集合
 // 以后往该集合中添加的按钮都是这个Popup中的一个菜单项啦
 CComPtr<Office::CommandBarControls> spPopupControls;
 spPopupControls = spNewPopup->GetControls();
 ATLASSERT(spPopupControls);

i. 恩,创建一个菜单项试试:
 spNewBar = spPopupControls->Add(vToolBarType, vEmpty, vEmpty, vEmpty, vShow);
 ATLASSERT(spNewBar);

 CComQIPtr<Office::_CommandBarButton> spCmdButton2(spNewBar);
 ATLASSERT(spCmdButton2);

 spCmdButton2->PutStyle(Office::msoButtonIconAndCaption);
 spCmdButton2->PutVisible(VARIANT_TRUE);
 spCmdButton2->PutCaption("sample2");
 spCmdButton2->PutEnabled(VARIANT_TRUE);
 spCmdButton2->PutTooltipText("sample2");
 spCmdButton2->PutTag("sample2");

 hBmp = (HBITMAP)LoadImage(_Module.GetResourceInstance(),MAKEINTRESOURCE(IDB_SAMPLE),IMAGE_BITMAP,0,0,LR_LOADMAP3DCOLORS);
 ::OpenClipboard(NULL);
 ::EmptyClipboard();
 ::SetClipboardData(CF_BITMAP,(HANDLE)hBmp);
 ::CloseClipboard();
 ::DeleteObject(hBmp);
 hr = spCmdButton2->PasteFace();
 if(FAILED(hr))return hr;

j. 现在可以保存变量以及进行事件连接了:
 m_spApp = spApp;

 m_spButton1 = spCmdButton1;
 hr = CommandButtonEvents1::DispEventAdvise((IDispatch*)m_spButton1);
 if(FAILED(hr))return hr;

 m_spButton2 = spCmdButton2;
 hr = CommandButtonEvents2::DispEventAdvise((IDispatch*)m_spButton2);
 if(FAILED(hr))return hr;
到这里,OnConnection方法就已经完成了:)

7. 在OnDisconnection方法中断开事件的连接:
 HRESULT hr = CommandButtonEvents1::DispEventUnadvise((IDispatch*)m_spButton1);
 if(FAILED(hr))return hr;

 hr = CommandButtonEvents2::DispEventUnadvise((IDispatch*)m_spButton2);
 if(FAILED(hr))return hr;

好了,终于又完成一篇文章了。
写的很累啊,即使是像我这样CP,也觉得很累啊(老了?!)!
呵呵,也不知道写的是不是清楚,或许大家看起来很觉得不知所然吧?
唉,文学细胞太少,看样子不是做文学的料啊,实在不明白的还是请大家读我的工程源代码吧:)