ActiveX控件和它的容器

ActiveX控件和它的容器 (轉)


1.COM基礎

2.ActiveX控件及實現

3.ActiveX控件容器及實現

4.總結


1.COM基礎

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來進行區分, 而不是利用它們的名字.

2.ActiveX控件及實現

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或其他的文檔來了解.

3.ActiveX控件容器及實現

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);
pszName = NULL;
iItem = m_lbControls.AddString(strName);
posControl = m_lControls.AddTail(clsid);
m_lbControls.SetItemDataPtr(iItem, posControl);
}
}
else
{
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);
if (FAILED(hResult))
{
pUnknown->Release();
return FALSE;
}
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());
if (FAILED(hResult))
TRACE0("Can't SetClientSite for the Control"n");
}

if (SUCCEEDED(hResult))
{
// 7. 支持IQuickActivate接口的控件必須使用下面的步驟.
IPersistStreamInitPtr pPersistStreamInit;
IPersistStoragePtr pPersistStorage;

pPersistStreamInit = m_lpObject;
if (pPersistStreamInit != NULL)
{
hResult = pPersistStreamInit->InitNew();
if (hResult == E_NOTIMPL)
hResult = S_OK;
}
else
{
pPersistStorage = m_lpObject;
if (pPersistStorage != NULL)
{
hResult = pPersistStorage->InitNew(m_lpStorage);
}
else
{
hResult = S_OK;
}
}
}

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的類庫爲我們做了許多的工作, 它們實現了一些作爲控件容器所必須實現的接口, 使我們在開發這類應用程序的時候有了很好的起點.

4.總結
上面所談到的只是一些基本的概念及簡單的實現, 開發一個ActiveX控件或者是它的容器都需要很多的知識和技術, 因爲COM本身就是一項十分龐大的技術規範, 它涉及了很多方面的知識, 而這些又往往是其它基於COM的技術的基礎.
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章