1. 程式人生 > >用VS2010開發支援MFC的office外掛

用VS2010開發支援MFC的office外掛

通過參考網上的文章,再加上自己的摸索,走了不少彎路,終於用VS2010開發出MS Office 2007的外掛.特寫此文以作備忘.(記性太差,之前開發過一次,後來再開發又忘了怎麼用^ _^)


下面以開發Excel外掛為例,其他office外掛開發過程類似.

首先建立一個支援MFC的ATL DLL解決方案,介面如下:


在"Allow merging of proxy/stub code"和"Support MFC"兩個選項前打勾,然後點選"Finish",到此最原始的解決方案已經建成.接著通過類嚮導給專案增加一個"ATL Simple Object"類.除了輸入類名之外,"ISupportErrorInfo

"選項可選可不選,其他預設即可,如下圖:


接下來在上面建立的ATL類Cexceladdin中實現庫"Microsoft Add-In Designer<1.0>"中的"_IDTExtensibility2"介面,後面需要用到此介面中的"OnConnection","OnDisconnection","OnAddInsUpdate","OnStartupComplete"和"OnBeginShutdown"方法.這四個方法是宿主(Office軟體)執行時載入和解除安裝外掛相關的.

以上操作用"Implement Interface Wizard"即可完成,如下圖:


編譯一下,沒有什麼錯誤.到這裡還不能在Excel軟體的介面看到外掛,還需要實現下面另一個介面,也是我花費最多時間摸索的地方.

還是在類Cexceladdin中實現介面,通過介面實現嚮導,選擇"Microsoft Office 12.0 Object Library<2.4> "庫(office 2007版本對應的就是12),新增"IRibbonExtensibility"介面.如下圖:


新增好介面之後,再編譯,會出現以下錯誤資訊:

1>d:\my documents\visual studio 2010\projects\2007exceladdin\2007exceladdin\stdafx.h(41): warning C4278: 'RGB': identifier in type library 'C:\Program Files\Common Files\Microsoft Shared\OFFICE12\MSO.DLL' is already a macro; use the 'rename' qualifier
1>d:\my documents\visual studio 2010\projects\2007exceladdin\2007exceladdin\stdafx.h(41): warning C4278: 'RGB': identifier in type library 'C:\Program Files\Common Files\Microsoft Shared\OFFICE12\MSO.DLL' is already a macro; use the 'rename' qualifier
1>d:\my documents\visual studio 2010\projects\2007exceladdin\2007exceladdin\stdafx.h(41): warning C4278: 'RGB': identifier in type library 'C:\Program Files\Common Files\Microsoft Shared\OFFICE12\MSO.DLL' is already a macro; use the 'rename' qualifier
1>d:\my documents\visual studio 2010\projects\2007exceladdin\2007exceladdin\stdafx.h(41): warning C4278: 'RGB': identifier in type library 'C:\Program Files\Common Files\Microsoft Shared\OFFICE12\MSO.DLL' is already a macro; use the 'rename' qualifier
1>d:\my documents\visual studio 2010\projects\2007exceladdin\2007exceladdin\stdafx.h(41): warning C4278: 'RGB': identifier in type library 'C:\Program Files\Common Files\Microsoft Shared\OFFICE12\MSO.DLL' is already a macro; use the 'rename' qualifier
1>d:\my documents\visual studio 2010\projects\2007exceladdin\2007exceladdin\debug\mso.tlh(2082): error C2011: 'IAccessible' : 'struct' type redefinition
1>          c:\program files\microsoft sdks\windows\v7.0a\include\oleacc.h(556) : see declaration of 'IAccessible'
1>d:\my documents\visual studio 2010\projects\2007exceladdin\2007exceladdin\debug\mso.tlh(2169): error C2504: 'IAccessible' : base class undefined

根據錯誤資訊的提示,大概意思是說"Iaccessible"這個結構體重複定義了.只要把stdafx.h檔案中的

#import "C:\Program Files\Common Files\Microsoft Shared\OFFICE12\MSO.DLL" raw_interfaces_only, raw_native_types, no_namespace, named_guids, auto_search

改成

#import "C:\Program Files\Common Files\Microsoft Shared\OFFICE12\MSO.DLL" raw_interfaces_only, raw_native_types, no_namespace, named_guids, auto_search rename("IAccessible", "IMSOAccessible")

即可消除此錯誤.

將GetCustomUI的實現改成以下:

STDMETHOD(GetCustomUI)(BSTR RibbonID, BSTR * RibbonXml)
	{
		if (!RibbonXml)
			return E_POINTER; 

		*RibbonXml = SysAllocString(

			_T("<customUI xmlns=\"http://schemas.microsoft.com/office/2006/01/customui\">")
			_T(" <ribbon>")
			_T("   <tabs>")
			_T("    <tab id=\"CustomTab\"" )
			_T("         label=\"Test\">" )
			_T("     <group id=\"CustomGroup\"" )
			_T("            label=\"Tools\">" )
			_T("       <button id=\"CustomButton\"" )
			_T("               imageMso=\"HappyFace\"")
			_T("               size=\"large\"")
			_T("               label=\"Button\"")
			_T("               onAction=\"OnStart\"/>")
			_T("     </group>")
			_T("    </tab>")
			_T("   </tabs>")
			_T(" </ribbon>")
			_T("</customUI>")

			); 
		return (*RibbonXml ? S_OK : E_OUTOFMEMORY); 
	}

同時先把_IDTExtensibility2介面對應的4個方法"OnConnection"等改成如下:

STDMETHOD(OnConnection)(LPDISPATCH Application, ext_ConnectMode ConnectMode, LPDISPATCH AddInInst, SAFEARRAY * * custom)
	{
		return S_OK;
	}
	STDMETHOD(OnDisconnection)(ext_DisconnectMode RemoveMode, SAFEARRAY * * custom)
	{
		return S_OK;
	}
	STDMETHOD(OnAddInsUpdate)(SAFEARRAY * * custom)
	{
		return S_OK;
	}
	STDMETHOD(OnStartupComplete)(SAFEARRAY * * custom)
	{
		return S_OK;
	}
	STDMETHOD(OnBeginShutdown)(SAFEARRAY * * custom)
	{
		return S_OK;
	}

也就是把各個函式的
return E_NOTIMPL;

改為
return S_OK;

此時最功能最簡單的外掛基本完成,但這個外掛還不知道他的宿主是誰,是Excel還是Word?這個需要修改一下"exceladdin.rgs"檔案,它是負責往登錄檔註冊外掛資訊的.此檔案原始內容大致如下:
HKCR
{
	NoRemove CLSID
	{
		ForceRemove {0F1D173C-61FE-44E6-BBA2-7D16D37C9DAC} = s 'exceladdin Class'
		{
			ForceRemove Programmable
			InprocServer32 = s '%MODULE%'
			{
				val ThreadingModel = s 'Apartment'
			}
			TypeLib = s '{D4E1923A-0EF0-42B2-B00E-32978D0E8C66}'
			Version = s '1.0'
		}
	}
}

HKCU
{
	Software
	{
		Microsoft
		{
			Office
			{
				Excel
				{
					Addins
					{
						'My2007exceladdin.exceladdin'
						{
							val FriendlyName = s 'Excel2007 Addin'
							val Description = s 'Excel2007 Addin'
							val LoadBehavior = d '00000003'
							val CommandLineSafe = d '00000001'
						}
					}
				}
			}
		}
	}
}

編譯之後,執行excel 2007,沒發現這個外掛.看來還是少了什麼.經過對以上參考文章原始碼的研究,發現文章裡少講了一部分,還需要在rgs檔案裡的HKCR部分增加程式碼,修改後是這樣子:

HKCR
{
My2007exceladdin.exceladdin.1 = s 'ExcelAddin Class'
{
CLSID = s '{0F1D173C-61FE-44E6-BBA2-7D16D37C9DAC}'
}
My2007exceladdin.exceladdin = s 'ExcelAddin Class'
{
CLSID = s '{0F1D173C-61FE-44E6-BBA2-7D16D37C9DAC}'
CurVer = s 'My2007exceladdin.exceladdin.1'
}


NoRemove CLSID
{
ForceRemove {0F1D173C-61FE-44E6-BBA2-7D16D37C9DAC} = s 'exceladdin Class'
{
ForceRemove Programmable
InprocServer32 = s '%MODULE%'
{
val ThreadingModel = s 'Apartment'
}
TypeLib = s '{D4E1923A-0EF0-42B2-B00E-32978D0E8C66}'
Version = s '1.0'
}
}
}

注意紅色部分,別改錯了,不然也是外掛執行不起來的.到這裡外掛就可以顯示了.晒一下效果圖:


雖然可以運行了,但按鈕點選會出錯,接下來也是麻煩事....

==========================================>>

首先, 通過嚮導在介面Iexceladdin中增加引數為IDispatch*的OnStart方法, 這個方法在GetCustomUI函式中的onAction呼叫到.大致如圖:


接下來在OnStart方法中新增測試程式碼,方便測試此方法是否有執行到.在此示例中直接新增彈窗提示,如下:

STDMETHODIMP Cexceladdin::OnStart(IDispatch* RibbonControl)
{
	AFX_MANAGE_STATE(AfxGetStaticModuleState());

	// TODO: Add your implementation code here
	MessageBox(NULL, _T("Test"), _T("Excel"), MB_OK);

	return S_OK;
}

最後,開啟"exceladdin.h"檔案,找到對映巨集程式碼部分,增加"COM_INTERFACE_ENTRY2(IDispatch, Iexceladdin)", 如下程式碼所示:

BEGIN_COM_MAP(Cexceladdin)
		COM_INTERFACE_ENTRY(Iexceladdin)
		COM_INTERFACE_ENTRY2(IDispatch, Iexceladdin)//此語句不會自動新增,需手動新增,否則Ribbon的按鈕無法對映對應的函式
		COM_INTERFACE_ENTRY2(IDispatch, _IDTExtensibility2)
		COM_INTERFACE_ENTRY(ISupportErrorInfo)
		COM_INTERFACE_ENTRY(_IDTExtensibility2)
		COM_INTERFACE_ENTRY(IRibbonExtensibility)
	END_COM_MAP()

到這裡按鈕的事件基本完成,編譯之後,再次開啟Excel2007,點選那個大大的笑臉,即可看到熟悉的視窗.


注:由於第一次發文章,前面的截圖沒先儲存再上傳,而是直接貼上上來,導致截圖丟失.如果哪個步驟不清楚,歡迎交流.

本文參考以下文章: