1.COM基礎
2.ActiveX控件及實作
3.ActiveX控件容器及實作
4.總結
COM是一種元件開發技術, 它實際上是一種在二進制層上相容的軟體開發方法的規範. COM技術是與具體的程式設計語言無關的技術, 隻要是支援COM開發的開發工具都可以用來進行COM應用開發, 而它們在二進制上相容的要求由各個開發工具來實作, 絕大部分是由編譯器實作的.
COM的基礎概念有以下幾部分組成,1)接口的定義及實作, 2)IUnknown接口, 3)GUID (COM中所涉及的概念還有很多,具體的可以參閱其他資料 ). 下面分别簡單的介紹它們.
1).接口的定義及實作
一個接口實際上就是一組定義了具體的功能的函數的集合, 這些定義沒有具體的實作. 接口的定義類似于C++中的純虛類定義, 它定義了接口函數的傳回類型、參數個數及函數的功能, COM元件就是靠這些接口互相進行通信. 一個簡單的例子如下.(MFC為我們提供了許多友善的宏用來定義接口, 而且在一般情況下, 我們是使用IDL或者是ODL來定義接口, 而不是使用下面這種形式).
interface IStack:IUnknown {
virtual void Pop(int* pvalue) = 0;
virtual void Push(int value) = 0;
};
上面定義的就是一個簡單的接口IStack. 它定義了兩個方法,并且描述了這兩個方法的傳回類型 (void), 參數個數和類型, 兩個函數都用純虛函數實作,在定義接口的檔案裡并不需要這兩個函數的具體實作. 一般情況下接口的實作可以通過對接口的繼承來完成, 但在一個元件實作了多個接口的情況下MFC采用了嵌套子類的實作方法, 具體情況可以參閱其他文檔. ]
2).IUnknown接口
在上面的例子中, IStack從一個叫做IUnknown的接口繼承而來, 那麼IUnknown接口是一個什麼樣的接口呢? 再COM規範中要求, 任何一個COM元件必須實作IUnknown接口, IUnknown接口的主要作用是用來維護COM元件的引用計數和對COM元件實作的接口進行查詢, 先讓我們看一下IUnknown接口的定義.
interface IUnknown {
virtual void QueryInterface(REFIID riid, void** ppvObject) = 0;
virtual HRESULT AddRef() = 0;
virtual HRESULT Release() = 0;
在上面IUnknown接口中, AddRef和Release是用來維護引用計數的. 因為一個COM元件可以同時為多個應用程式服務, 如果沒有一種适當的機制來維護COM元件的生存期的話, 那麼當一個使用COM元件的應用程式結束時, 這個元件也會被同時釋放掉, 那麼其他使用這個元件的應用程式就會出現要求通路的元件不存在的錯誤.是以COM子系統就是用引用計數來解決這個問題, 當一個應用程式要求使用某個元件時, 它就增加這個元件的引用計數, 當這個應用程式結束時, 它就減少這個元件的引用計數, 當一個元件的引用計數為0時, COM子系統就會釋放這個元件.
QueryInterface方法是用來在COM元件中查詢一個接口是否被實作的方法, 因為每一個接口都擁有一個能唯一辨別它自己的一個ID, 稱為IID, 通過傳遞這IID, 我們就可以查詢一個接口是否被該 COM元件實作, 如果該元件實作了該接口,我們就可以利用QueryInterface方法的第二個參數傳回的值來使用這個接口的方法.
3).GUID
上面提到, 每個接口都由一個唯一辨別自己的ID, IID, 同樣每個實作了某個接口的C++類也有一個ID, 稱為CLSID, 在OLE Automation中, 廣泛使用了一種稱為類型庫的技術, 一個類型庫包含了一個COM元件中所有的類型資訊, 包括它實作的接口, 枚舉類型, 接口的方法, 及接口參數等一些相關的資訊, 同樣類型庫也是用一個表示自己的ID, LIBID. COM子統為了能在衆多的COM技術中盡快的找的某個類型的COM元件, 又對COM元件進行了分類管理, 而每個類又有一個類别ID,CATID, 實際上我們可以利用這個CATID來列出系統中的所有的控件(CATID_Control). 上面說的所有這些ID, 實際上是一種類型, GUID. 它們隻不過是GUID的不同的typedef.
GUID是一種利用系統時間和網卡具有的唯一編号的特性生成的一個具有128位的數字. 這個數字在時間和空間上保證了它的唯一性. 是以接口及相關的一些概念都利用GUID來進行區分, 而不是利用它們的名字.
ActiveX控件的最早原型應該是随着VB出現的VBX控件, 由于VBX控件的16位結構并不能适應32位作業系統的要求,于是就誕生了OCX控件, OCX控件是一種32位的自包含的簡單應用, 它實際上是一組完成指定的功能函數集合.它實際上是DLL的另外一種表現形式. OCX控件可以有自己的界面,也可以沒有界面, 它擁有屬性, 方法, 而且一個OCX控件可以觸發出某種類型的事件, 用來通知容器它的狀态的改變或者是某種外部狀态的改變或事件的發生, 實作一個OCX控件必須實作一系列既定的接口, 這使得OCX控件顯得有些龐大和備援, 因為有些控件隻需要實作這些接口的一部分, 而且對于Internet 來說, 實作這些多餘的接口無疑增加了控件的體積.是以在1996年PDC大會上, 微軟提出了它的 Activate Internet的概念, 并把它的一些技術改稱為ActiveX技術, ActiveX控件就在原先的OCX控件上經過對要實作的接口的削減而誕生了, 現在隻要一個COM元件實作IUnknown接口就可以被稱為 ActiveX控件. 是以可以說一個ActiveX控件就是一個實作了IUnknown接口并且支援自注冊的簡單的 COM元件.
但是實作一個IUnknown接口的控件顯然是沒有實際用處的, 是以真正的ActiveX控件還是要實作原先OCX控件定義的一些接口, 用來和它的容器進行互動操作. 下面簡要的說明一下一個真正的 ActiveX控件的實作.除了IUnkown接口外, 一個ActiveX控件一般要實作下面接口中的一部分. IOleObject,IOleInPlaceObject,IOleInPlaceActiveObject,IOleControl, IDataObject,IViewObject2, IDispatch, IConnectionPointContainer, ProviderClassInfo[2], ISpecifyPropertyPages, IPerPropertyBrowsing, IPersistStream, IPersistStreamInit,IPersistMemory, IPersistStorage, IPersistMoniker, IPersistPropertyBag,IOleCache[2],IExternalConnection,IRunnableObject, IClassFactory實作要求可以檢視MSDN.
一個ActiveX控件通常具有一些屬性和事件.控件的屬性一般情況下是通過IDispatch接口實作的.在定義相應的控件屬性時, 有一個被稱為DISPID的值,這個值是用來被其他使用該控件的容器調用屬性時使用的, 因為它們必須通過IDispatch接口的Invoke方法來調用相應的屬性.IDispach的方法 Invoke是用來調用響應的屬性的關鍵方法,但是這個方法在調用控件的屬性時, 并不是用屬性的名字, 而是被稱為DISPID的ID值. 在一般情況下, 一個控件通常有它自己的類型庫, 容器通過查詢控件的類型庫得到相應的屬性和方法及事件的清單, 并取得它們的DISPID,然後就可以通過Invoke方法來操作它們.
一個ActiveX控件一般具有三種屬性, 固有屬性(stock property), 環境屬性(ambient property), 自定義屬性(custom property). 固有屬性是大部分ActiveX控件具有的屬性, 比如前景色, 字型等, 環境屬性是控件處于容器中時, 有容器提供的一些屬性, 如LocaleID, UserMode. 這些屬性具有固定的DISPID值, 在控件中可以通過GetAmbientxxxx方法得到這些屬性的值. 自定義屬性是一個控件要實作自己的某些特定的功能特征時,定義的一些屬性, 在容器中這些屬性可以通過類型庫來得到, 通過對IDispatch接口的調用來處理.
控件的事件是由控件觸發的一個消息或通知, 如果一個控件支援事件, 它必須實作 IConnectionPointContainer和IConnectionPoint接口, 然後控件定義自己的出接口, 這個接口一般是通過用dispinterface聲明, 在容器對控件進行事件響應時, 必須使用IDispatch接口的Invoke 方法進行處理, 根據Invoke調用傳進來的DISPID我們就可以知道是控件觸發了哪一個事件, 根據其他資訊, 我們就可以對這個事件進行處理.
下面簡單介紹一下如何利用MFC來進行ActiveX控件的開發. 首先我們使用AppWizard來生成 ActiveX控件的架構, 實際上這個架構已經是一個完整的控件, 在向導的幫助下這個控件已經實作了上面提到的ActiveX控件要實作的接口的一部分重要的接口, 象對事件的基本支援,屬性的支援.我們可以在這個架構的幫助下添加我們自己要實作的功能, 為這個控件添加屬性方法和事件.VC中的 ClassWizard在這方面提供大量的友善的操作, 在ClassWizard的AcitveX Automation頁提供了對 ActiveX控件的屬性事件方法的添加.
對于一個ActiveX控件來說你需要首先弄清楚哪些是要在控件中完成,哪些是要在容器中實作.那麼,需要控件完成的你就要考慮用屬性或者是方法來實作,而需要容器來完成的你隻需将參數通過事件觸發傳遞給容器,在容器端來實作.
另外,一個比較實際的問題是你的控件将是什麼樣子.比較簡單的方法是在ClassWizard的時候指定控件将繼承自那個類,進而擁有該類的外觀.但這種方法不夠靈活.如果你想定做控件的外觀,那麼最好的方法還是你自己手繪控件,或者是通過在控件内部添加一些控件形成組合控件.你可以在OnDraw (CDC* pdc, const CRect& rcBounds, const CRect& rcInvalid)中來繪制控件.該函數負責控件的繪制,其中pdc是目前系統用的環境裝置,rcBounds是目前控件的rect範圍,你可以用它來定位.繪制控件還是比較簡單的,但前提是你必須要了解Windows的繪圖機制.主要是會使用CBrush,CDC,CFont等 MFC的基本繪圖類.
實際上對于ActiveX Control來說,在對它程式設計完全可以像是對一般的程式一樣使用各種MFC的類,但是很多的類将不得不動态的建立,是以你必須掌握好定位.主要是掌握好對各種子類的重繪和重新整理的時機和方法.
關于屬性表的建立,屬性表允許控件顯示它的各種屬性,以供察看和編輯.屬性表通常以表的對話框的形式實作.你可以在這裡改變一些控件的屬性.對于大多數的控件來說這已經足夠了.
下面我們來看看VC的AppWinzard Control都為我們做了些什麼.用VC的AppWinzard Control你可以快速生成一個ActiveX Control在這裡VC自動為我們聲稱了兩個接口:一個用來負責屬性和方法. 另一個用來負責事件.這個控件可以在容器運作,但是它什麼也不做.并且它的外觀也非常簡陋.首先讓我們來重新繪制它的外觀,這在OnDraw中完成.
// 設定目前的字型,并保留原字型
CFont* pOldfont;
pOldfont = SelectFontObject(pdc,m_customfont);
// 得到目前的各種顔色.其中TranslateColor是為了把OLE_COLOR轉換成COLORREF.
COLORREF textbkcolor = ::GetSysColor(COLOR_BTNFACE);
COLORREF textforecolor = this->TranslateColor(this->GetForeColor());
COLORREF edgebkcolor = ::GetSysColor(COLOR_3DFACE);
COLORREF edgeforecolor = ::GetSysColor(COLOR_3DFACE);
COLORREF oldbkcolor = pdc->SetBkColor(textbkcolor);
COLORREF oldforecolor = pdc->SetTextColor(textforecolor);
if(m_brush.m_hObject = NULL)
m_brush.CreateSolidBrush(textbkcolor);
CBrush* pOldbrush = pdc->SelectObject(&m_brush);
pdc->Rectangle(&rcBounds);
CSize osize = pdc->GetTextExtent(m_cstrCaption);
m_size = osize;
pdc->ExtTextOut((rcBounds.right-osize.cx)/2,
(rcBounds.bottom-osize.cy)/2,
ETO_CLIPPED|ETO_OPAQUE,
rcBounds,
m_cstrCaption,
m_cstrCaption.GetLength(),
NULL);
UINT borderstyle = EDGE_RAISED;
UINT borderflags = BF_RECT;
//畫邊框
pdc->SetBkColor(edgebkcolor);
pdc->SetTextColor(edgeforecolor);
pdc->DrawEdge((LPRECT)(LPCRECT)rcBounds,borderstyle,borderflags);
// 恢複設定
pdc->SetBkColor(oldbkcolor);
pdc->SetTextColor(oldforecolor);
pdc->SelectObject(pOldfont);
pdc->SelectObject(pOldbrush);
以上代碼将為控件繪制一個比較好的外觀,當然你可以任意改變直到你滿意為止.以後你就可以根據需要來添加一些屬性和方法并寫出相對的實作.大部分的定義都是由CLASS WINZARD來維護的,是以你可以輕松的添加它們. 一些建議:在控件的屬性頁的編寫過程中,需要将屬性頁上的标準控件與ActiveX 控件的屬性相聯系,這樣當你在動态的改變标準控件的值時,ActiveX控件的屬性會随之改變.但問題是如果你使用别的方法來動态改變屬性頁上的标準控件的值,則ActiveX控件的屬性不會随之改變. 原因很簡單,ActiveX控件的屬性不知道自己已經發生改變,是以沒有接受從标準控件傳來的值.這一過程是在DoDataExchange()中的DD_P函數來完成的.由于你手動的改變了标準控件的值,是以你需要使用SetModified()來通知ActiveX控件的屬性發生改變,這樣DD_P函數就會有效了.另外,在ActiveX控件的屬性中的資料類型有一些是OLE_XXX類型,這些類型實際上是一些LONG型的值,并且COLECONTROL 中有一些函數用來轉換它們.在類型轉換過程中盡量不要使用強制轉換,這可能會帶來一些意想不到的錯誤,鼓勵使用緩沖區機制.
關于控件部分其實還有很多東西,可以參閱MFC或其他的文檔來了解.
ActiveX控件的容器實際上是ActiveX控件的用戶端, 它使用ActiveX控件提供的各種功能.但是它也同時為控件提供了一些屬性和其他的特征, 使得控件可以更好的和它進行互動和操作. ActiveX 控件的容器實際上是一個OLE容器,然後在實作了相應的接口來支援ActiveX控件後成為ActiveX控件的容器.
除了IUnknown外,容器程式需要用到下列接口的一部分: IOleInplaceFrame, IOleInPlaceUIWindow, IOleClientSite,IOleInPlaceSite, IAdviseSink, IOleControlSite, IOleControlSite, IDispatch, IProperytNotifySink, IStorage, IOleContainer接口的具體定義請參照MSDN. 在MFC附帶的例子中有一個很好的例子, 就是VC中附帶的工具ActiveX Control Test Container.
下面就以這個例子來解釋一個ActiveX控件容器的實作及對某些問題的處理. 在這個例子中,使用了VC的向導來生成一個具有Container支援的應用程式, 在生成的類中有一個用來包裝每一個嵌入到問檔中的OLE對象的類, 一般被稱為xxxCntrItem, 在這個例子中被改名CTestContainer98Item. 建立每一個ActiveX控件時都是通過這個類來直接生成,這個類維護了ActiveX控件的一些屬性特征.而且這個類支援序列化,
這樣我們就可以通過序列化來儲存控件的屬性狀态等資訊. 1).動态建立控件. 這應該是一個ActiveX控件容器最重要的任務. 為了能管理容器中的控件, 首先它必須能動态的建立控件. 因為每一個COM元件都具有一個唯一的ID, CLSID, ActiveX控件也不例外, 但是針對系統中成百上千的COM對象, 我們如何确定哪一個是ActiveX控件呢? 在COM基礎中我們提到了為了能更快的定位COM元件并加載它,COM子系統對COM元件實行了分類别管理即利用CATID來分類各種不同的COM元件, ActiveX 控件的CATID是CATID_Control,是以我們可以通過這個資訊來找到所有在系統中注冊的控件, 一般情況下我們是通過生成一個清單來表示所有這些控件. 下面是經過改寫的
CInsertControlDlg::RefreshControlList()函數
CArray m_aImplementedCategories;
CListBox m_lbControls;
ICatInformationPtr m_pCatInfo;
CList m_lControls;
void CInsertControlDlg::RefreshControlList()
{
BOOL bDone;
HRESULT hResult;
IEnumGUIDPtr pEnum;
ULONG nImplementCategories;
CATID* pcatidImpl;
CLSID clsid;
LPOLESTR pszName;
CString strName;
ULONG iCategory;
int iItem;
POSITION posControl;
CString strServerPath;
CString strString;
// 首先,清空清單框,并用m_aImplementCategories的資料填充pcatidImpl, 作為m_pCatInfo函數
// EnumClassedOfCategories的第二個參數,來擷取CLSID的枚舉器
m_lbControls.ResetContent();
nImplementCategories = m_aImplementCategories.GetSize();
if (nImplementCategories == 0)
nImplementCategories = (ULONG)-1;
pcatidImpl = NULL;
}
else
// 為pcatidImpl配置設定記憶體,将m_aImplementCategories資料傳給pcatidImpl
pcatidImpl = (CATID*)_alloca(nImplementCategories * sizeof(CATID));
for ( iCategory = 0; iCategory < nimplementcategories; iCategory++ )
pcatidImpl[iCategory]="m_aImplementCategories[iCategory]; // 擷取CLSID的枚舉器
hResult = m_pCatInfo->EnumClassesOfCategories(nImplementCategories, pcatidImpl, 0, NULL, &pEnum);
if (FAILED(hResult)) return;
//然後通過枚舉器枚舉所有ActiveX Control的CLSID, 并取得相應的使用者類型名稱,加入到清單框中.
bDone = FALSE;
while (!bDone)
hResult = pEnum->Next(1, &clsid, NULL); // 獲得下一個ActiveX Control的CLSID
if (hResult == S_OK)
pszName = NULL;
hResult = OleRegGetUserType(clsid, USERCLASSTYPE_FULL, &pszName);//得到相應的使用者類型名稱
if (SUCCEEDED(hResult))
strName = pszName;
CoTaskMemFree(pszName);
iItem = m_lbControls.AddString(strName);
posControl = m_lControls.AddTail(clsid);
m_lbControls.SetItemDataPtr(iItem, posControl);
bDone = TRUE;
OnControlsSelChange();
上面這個函數示範了如何從衆多的COM元件中提取ActiveX控件并把它們添加到一個清單框中,同時保留了它們的CLSID. 其中m_pCatInfo在InitDialog中調用CreateInstance建立自己的執行個體.
在我們的到了某個ActiveX控件的CLSID以後, 我們就可以利用CoCreateInstanse函數來生成該控件的執行個體. 如上面所說的, 每一個ActiveX控件都有一個包裝類, 我們在建立控件的時候, 實際上都是通過這個包裝類來進行,下面我們看一下, 在這個包裝類中建立控件的代碼(删除了一些不是很重要的代碼).
BOOL CActiveXContainerCntrItem::CreateControl(REFCLSID clsid)
IUnknown* pUnknown;
// 1. 建立控件自己的執行個體, 在下面的步驟将對控件的一些狀态進行初始化
HRESULT hResult = CoCreateInstance(clsid, NULL, CLSCTX_INPROC|CLSCTX_SERVER,
IID_IUnknown, (void**)&pUnknown);
if (FAILED(hResult))
return FALSE;
// 2. 在控件中請求IOleObject接口,
hResult = pUnknown->QueryInterface(IID_IOleObject, (void**)&m_lpObject);
pUnknown->Release();
CString strUserType;
GetUserType(USERCLASSTYPE_SHORT, strUserType);
// 3. 建立一個唯一的名稱, 用來維護每個控件執行個體的唯一性
GetDocument()->CreateUniqueItemName(this, strUserType, m_strDisplayName);
// 4. 初始化控件的某些基本資訊.
InitControlInfo();
BOOL bQuickActivate = FALSE;
// 5. 如果控件支援IQuickActivate接口, 利用IQuickActive接口激活控件
bQuickActivate = QuickActivate();
if (!bQuickActivate)
// 6. 如果控件不支援IQuickActiveX接口, 通過IOleObject接口設定控件的ClientSite.
m_lpObject->GetMiscStatus(DVASPECT_CONTENT, &m_dwMiscStatus);
if (m_dwMiscStatus & OLEMISC_SETCLIENTSITEFIRST)
hResult = m_lpObject->SetClientSite(GetClientSite());
TRACE0("Can't SetClientSite for the Control"n");
// 7. 支援IQuickActivate接口的控件必須使用下面的步驟.
IPersistStreamInitPtr pPersistStreamInit;
IPersistStoragePtr pPersistStorage;
pPersistStreamInit = m_lpObject;
if (pPersistStreamInit != NULL)
hResult = pPersistStreamInit->InitNew();
if (hResult == E_NOTIMPL)
hResult = S_OK;
pPersistStorage = m_lpObject;
if (pPersistStorage != NULL)
hResult = pPersistStorage->InitNew(m_lpStorage);
return FinishCreate(hResult); // 8. 在此處設定對控件的事件處理和屬性處理資訊
下面針對上面注釋中提到的一些内容進行說明,
注釋2請求IOleObject接口是用來對後面的一些設定做準備, 因為這個接口要在很多的地方使用, 是以被儲存在一個成員變量中.
注釋3是用來差別一個控件的多個執行個體, 這個方法被文檔類實作, 它根據控件的名稱和一個數字來維護同一種控件的執行個體.
注釋4是用來初始化控件的一些基本資訊,這些資訊是通過讀取控件類型庫将控件的屬性和事件放在各自的清單中, 以後好用來對控件的屬性變化和事件進行響應.
注釋5是針對QuickActivate()方法的, QuickActivate()方法首先向控件請求IQuickActivate 接口, 如果控件不支援該接口, 傳回FALSE, 如果控件支援該接口, 則初始化兩個結構QACONTAINER 和QACONTROL, 然後用這兩個結構調用IQuickActivate接口的QuickActivate方法, IQuickActivate接口是為了提高ActiveX控件的加載速度而設計的.在調用了IQuickActivate接口的 QuickActivate()方法之後, IPersist*::Init和IPersist*::InitNew方法必須被調用, 控件應該在QuickActivate方法中建立它的連接配接點與容器的接收器之間的連接配接, 如果沒有調用 IPersist*::Init和IPersist*::InitNew, 那麼這些連接配接就不會生效.
到這裡控件已經被建立了, 但是這裡并沒有涉及到與容器有關的内容. 下面講述具體的容器的實作. 我們在例子中可以看到, 程式的文檔類繼承自COleDocument, 在COleDocument中文檔為我們實作了作為容器所必須實作的一個接口, IOleContainer, 我們在程式中可以通過GetStartPosition (), GetNextItem()等方法來使用這個接口, 這個接口的主要作用是用來周遊容器中的控件或其他的 OLE對象.另外還有一些必須實作的接口實際上已經在MFC中實作, 我們在一般情況下隻要簡單的使用這些經過封裝的函數就可以了, 這裡主要講述一些與控件的屬性和事件處理相關的一些問題, 在 ActiveX控件及實作中我們提到, 控件的屬性和事件一般是通過IDispatch來實作, 在Test Container中我們可以看到下面的一段用來實作接口映射的代碼.
BEGIN_INTERFACE_MAP( CTestContainer98Item, COleClientItem )
INTERFACE_PART( CTestContainer98Item, IID_IServiceProvider, ServiceProvider )
INTERFACE_PART( CTestContainer98Item, IID_IPropertyNotifySink, PropertyNotifySink )
INTERFACE_PART( CTestContainer98Item, IID_IDispatch, AmbientProperties )
INTERFACE_PART( CTestContainer98Item, IID_IOleControlSite, OleControlSite )
// INTERFACE_PART( CTestContainer98Item, IID_IOleInPlaceSiteEx, OleInPlaceSiteWindowless )
// INTERFACE_PART( CTestContainer98Item, IID_IOleInPlaceSiteWindowless, OleInPlaceSiteWindowless )
END_INTERFACE_MAP()
我們可以看到, 在上面的接口映射中一共出現了6個接口, 但是有兩個入口是被注釋的. 下面我們逐一解釋這些接口:
第一個IServiceProvider在這裡主要是用來提供IBindHost接口的. 實際上在實作一個容器的時候, 這個接口并不是必須的.
第二個IPropertyNotifySink是用來實作控件的屬性變化通知的接收器. 如果希望你的容器能在其中的控件的屬性改變時得到相應的通知, 就要實作這個接口,在這個接口的OnChange方法中你可以得到相應的被改編的屬性的DISPID, 有了這個DISPID,你就可以更進一步的控制控件的某些屬性特征了.
第三個接口是用來為控件提供環境屬性的. 為控件提供環境屬性這個功能是由IDispatch接口實作的, 每一個環境屬性都具有特定的DISPID, 是以當控件調用GetAmbientxxx方法時, 控件就會要求容器提供相應的屬性的實作,這些屬性都是被IDispatch接口實作的.
第四個接口是IOleControlSite. 這個接口的主要作用是提供一些在容器内部的Site對象對内嵌在其中的控件的管理. 實作這個接口是可選的.
在上面的接口映射中, 我們并沒有看到對控件的事件的處理的接口映射, 在Test Container的代碼中我們可以看到下面這段代碼.
BEGIN_INTERFACE_PART( EventHandler, IDispatch )
STDMETHOD( GetIDsOfNames )( REFIID iid, LPOLESTR* ppszNames, UINT nNames, LCID lcid, DISPID* pDispIDs );
STDMETHOD( GetTypeInfo )( UINT iTypeInfo, LCID lcid, ITypeInfo** ppTypeInfo );
STDMETHOD( GetTypeInfoCount )( UINT* pnInfoCount );
STDMETHOD( Invoke )( DISPID dispidMember, REFIID iid, LCID lcid, WORD wFlags, DISPPARAMS* pdpParams,
VARIANT* pvarResult, EXCEPINFO* pExceptionInfo, UINT* piArgError );
END_INTERFACE_PART( EventHandler )
很顯然這段代碼是用來處理事件的, 但是為什麼在接口的映射部分沒有它呢? 如果你檢視 CTestContainer98Item類的代碼時你會發現一個叫做GetInterfaceHook()的方法, 這個方法有一個類型為const void*的參數pv, 這個參數實際上是一個IID類型的指針,看看下面的代碼:
piid = (const IID*)pv;
if( *piid == m_infoEvents.GetIID() )
return( &m_xEventHandler );
現在我們知道了控件的事件是怎麼處理的, GetInterfaceHook()方法是CCmdTarget的一個方法, 但是在MSDN中卻并沒有文檔說明.在這個方法中同樣也實作了其他幾個接口的映射關系.
到這裡我們已經可以了解到要實作一個ActiveX控件的容器所需要實作的接口及相關的一些問題了, MFC的類庫為我們做了許多的工作, 它們實作了一些作為控件容器所必須實作的接口, 使我們在開發這類應用程式的時候有了很好的起點.
上面所談到的隻是一些基本的概念及簡單的實作, 開發一個ActiveX控件或者是它的容器都需要很多的知識和技術, 因為COM本身就是一項十分龐大的技術規範, 它涉及了很多方面的知識, 而這些又往往是其它基于COM的技術的基礎.