














转:http://blog.5d.cn/user7/luhui/200601/197699.html
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;
};
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;
};
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中完成.//
设置当前的字体,并保留原字体//
得到当前的各种颜色.其中TranslateColor是为了把OLE_COLOR转换成COLORREF.
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;
//
画边框//
恢复设置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;
//
BOOL CActiveXContainerCntrItem::CreateControl(REFCLSID clsid)
{
IUnknown* pUnknown;
// 1.
// 2.
在控件中请求IOleObject接口,
CString strUserType;
GetUserType(USERCLASSTYPE_SHORT, strUserType);
// 3.
// 4.
初始化控件的某些基本信息.
BOOL bQuickActivate = FALSE;
// 5.
if (SUCCEEDED(hResult))
{
// 7.
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.
在此处设置对控件的事件处理和属性处理信息
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()
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 )
piid = (const IID*)pv;
if( *piid == m_infoEvents.GetIID() )
{
return( &m_xEventHandler );
}
4.
总结此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。