activex控件的作用,内部控件与ActiveX控件有什么区别
1.COM基金会
2.ActiveX控件及其实现
3.ActiveX控件容器及其实现
4.摘要
1.COM基金会
COM是一种组件开发技术,实际上是在二进制级别上兼容的软件开发方法的规范。COM技术是一种与具体编程语言无关的技术。任何支持COM开发的开发工具都可以用来开发COM应用程序,它们的二进制兼容性要求是由每个开发工具实现的,大部分是由编译器实现的。
COM的基本概念由以下几个部分组成:1)接口的定义和实现,2) IUKNOWN接口,3)GUID(COM中还涉及很多其他概念,具体请参考其他资料)。在此,简单介绍一下。
1)接口的定义和实现
接口实际上是一组定义特定功能的函数。这些定义没有具体的实现。接口的定义类似于C中纯虚类的定义,定义了接口函数的返回类型、参数个数和函数。COM组件通过这些接口相互通信。一个简单的例子如下。(MFC为我们提供了很多方便的宏来定义接口,一般情况下我们使用IDL或者ODL来定义接口,而
接口IStack:IUnknown {
虚拟void Pop(int * pvalue)=0;
虚拟void Push(int值)=0;
};
上面的定义是一个简单的接口IStack。它定义了两个方法,并描述了这两个方法的返回类型(void)、参数的数量和类型。这两个函数都是由纯虚函数实现的,接口定义文件中不需要这两个函数的具体实现。一般接口的实现可以通过继承接口来完成,但MFC在一个组件实现多个接口时采用嵌套子类的实现方法。详情请参考其他。
2).我不知道接口
在上面的例子中,IStack是从一个叫做IUKNOWN的接口继承而来的,那么IUKNOWN接口是一个什么样的接口呢?在COM的规范中,要求任何COM组件都必须实现IUKNOWN接口。IUKNOWN接口主要用于维护COM组件的引用计数,查询COM组件实现的接口。我们先来看看IUKNOWN接口的定义。
接口IUnknown {
虚拟void QueryInterface(REFIID riid,void * * ppvObject)=0;
虚拟HRESULT AddRef()=0;
虚拟HRESULT Release()=0;
};
在上面的IUnknown接口中,AddRef和Release用于维护引用计数。因为一个COM组件可以同时服务于多个应用程序,如果没有适当的机制来维护COM组件的生存期,那么当一个使用COM组件的应用程序结束时,这个组件也会同时被释放。则使用该组件的其他应用程序将会出现错误,即需要访问的组件不存在。因此,COM子系统使用引用计数来解决这个问题。当应用程序需要使用一个组件时,它将增加该组件的引用计数,当应用程序结束时,它将减少该组件的引用计数。当组件的引用计数为0时,COM子系统将释放该组件。
query方法用于查询COM组件中是否实现了某个接口,因为每个接口都有一个可以唯一标识自己的ID,称为IID。通过传递这个IID,我们可以查询一个接口是否由COM组件实现。如果组件实现了接口,我们可以使用QueryInterface方法的第二个参数返回的值来使用这个接口方法。
3).全局唯一标识符
如上所述,每个接口都有一个唯一标识自己的ID,IID。类似地,每个实现接口的C类也有一个ID,称为CLSID。在OLE自动化中,一种称为类型库的技术被广泛使用。类型库包含COM组件中的所有类型信息,包括它实现的接口、枚举类型、接口的方法以及一些相关信息,如接口参数。同一类型库也使用ID来表示自身。Libid.com子系统对COM组件进行分类管理,以便在众多的COM技术中尽快找到某一类型的COM组件,每个类都有一个类别ID CATID,实际上我们可以用这个CATID来列出系统中所有的控件(CATID_Control)。上面提到的这些ID其实都是一个类型,GUID。它们只是GUIDs的不同typedef。
GUID是一个128位的数字,由系统时间和网卡的唯一编号生成。这个数字保证了它在时间和空间上的唯一性。因此,接口和一些相关概念是通过GUID而不是名称来区分的。
2.ActiveX控件及其实现
ActiveX控件最早的原型应该是伴随VB出现的VBX控件。由于VBX控件的16位结构不能满足32位操作系统的要求,OCX控件诞生了。OCX控制是一个32位独立的简单应用程序,它实际上是一组功能函数来完成指定的功能。实际上是DLL的另一种表现形式。OCX控件可以有自己的接口,也可以没有接口,它有属性和方法,一个OCX控件可以触发某类事件,用来通知容器它的状态的变化或者某个外部状态的变化或者某个事件的发生。要实现一个OCX控件,必须实现一系列已建立的接口,这使得OCX控件显得有点笨重和多余,因为有些控件只需要实现其中的一些接口。而且对于互联网来说,这些冗余接口的实现无疑会增加控件的体积。于是在1996年的PDC大会上,微软提出了激活互联网的概念,并将其部分技术更名为ActiveX技术。ActiveX控件是通过切割要实现的接口,在原有的OCX控件上诞生的。现在一个COM组件只要实现了IUKNOWN接口就可以被称为ActiveX控件,所以可以说ActiveX控件就是一个简单的实现了IUKNOWN接口并支持自注册的COM组件。
但是用IUKNOWN接口实现一个控件显然是没有用的,所以真正的ActiveX控件还是需要实现一些原OCX控件定义的接口才能与其容器互操作。下面是一个真实ActiveX控件实现的简要描述。除了IUnknown接口之外,通常ActiveX控件应实现以下一些接口:ioleobject、ioleinplaceobject、ioleinplaceactiveobject、iolecontrol、idataobject、iviewobject2、idispatch、IConnectionPointContainer、ProviderClassInfo[2]、ISpecifyPropertyPages、IPerPropertyBrowsing、IPersistStream、IPersistMemory、IpersistStorage、ipersistMonitor、ipersistPropertyBag、Iolecache [2]、IexternalConnection、IRS
ActiveX控件通常有一些属性和事件。通常,控件的属性是通过IDispatch接口实现的。在定义相应的控件属性时,有一个名为DISPID的值,当使用该控件的其他容器调用这些属性时,将使用该值。因为它们要通过IDispatch接口的Invoke方法调用相应的属性。idispatch的Invoke方法是用于调用responded属性的关键方法,但在调用控件的属性时,此方法不使用属性的名称,而是使用名为DISPID的ID值。一般来说,一个控件通常有自己的类型库,通过查询控件的类型库,容器得到相应的属性、方法和事件的列表,并获得它们的DISPID,然后就可以通过调用方法来操作它们。
ActiveX控件通常有三个属性:库存属性、环境属性和自定义属性。固有属性是大多数ActiveX控件都有的属性,比如前景色、字体等。当控件在容器中时,环境属性为。容器提供了一些属性,比如LocaleID,UserMode。这些属性有固定的DISPID值,这些属性的值可以通过控件中的GetAmbientxxxx方法获得。自定义属性是控件在希望实现自己的某些特定功能时定义的一些属性。这些属性可以通过容器中的类型库获取,并通过调用IDispatch接口进行处理。
控件的事件是由控件触发的消息或通知。如果控件支持事件,它必须实现IConnectionPointContainer和IConnectionPoint接口,然后控件定义自己的退出接口,该接口通常由dispinterface声明。当容器响应控件的事件时,必须通过IDispatch接口的Invoke方法进行处理。根据Invoke调用传入的DISPID,我们可以知道控件触发了哪个事件,我们可以根据其他信息来处理这个事件。
下面简单介绍一下如何使用MFC开发ActiveX控件。首先,我们使用AppWizard生成ActiveX控件的框架。其实这个框架已经是一个完整的控件了。在向导的帮助下,这个控件已经实现了前面提到的ActiveX控件接口的一些重要接口,比如对事件的基本支持。在这个框架的帮助下,我们可以添加自己的函数,为这个控件添加属性方法和事件。VC中的ClassWizard在这方面提供了很多方便的操作,ClassWizard的AcitveX自动化页面提供了ActiveX控件的属性事件方法的添加。
对于ActiveX控件,您需要首先确定哪些要在控件中完成,哪些要在容器中实现。那么,如果需要完成控件,就要考虑使用属性或者方法来实现,而如果需要完成容器,只需要通过事件触发将参数传递给容器,在容器端实现即可。
另外,一个实际的问题是你的控件会是什么样子。更简单的方法是在ClassWizard中指定控件将从该类继承,以便具有该类的外观。但是这种方法不够灵活。如果您想自定义控件的外观,那么最好的方法是自己手绘控件。或者在控件内部添加一些控件,形成一个复合控件。可以在ONDRAW中绘制控件(CDC * pdc,Const CRECT rcBounds,Const CRECT RC无效)。这个函数负责绘制控件,其中PDC是当前系统使用的环境设备,RCBounds是当前控件的rect范围。可以用来定位。绘制控件相对简单,但前提是你必须知道Windows的绘制机制。可以主要使用C Brush、CDC、CFONT等MFC的基本画图类。
其实对于ActiveX控件来说,在编写它的时候可以像普通程序一样使用各种MFC类,但是很多类会不得不动态创建,所以一定要掌握定位,主要是重绘和刷新各种子类的时机和方法。
关于属性表的创建,属性表允许控件显示其各种属性以供查看和编辑。属性表通常以表格对话框的形式实现。您可以在这里更改某些控件的属性。这对大多数控件来说已经足够了。
让我们来看看VC的AppWinzard控件为我们做了什么。用VC的AppWinzard控件可以快速生成ActiveX控件。在这里,VC自动为我们声明了两个接口:一个负责属性和方法,一个负责事件。这个控件可以在容器中运行,但是它什么也不做。而且它的外观也很简洁。首先,让我们重新绘制它的外观,这是在OnDraw中完成的。
//设置当前字体并保留原来的字体
CFont * pOldfont
pOldfont=SelectFontObject(pdc,m _ custom font);
//获取当前颜色。TranslateColor用于将OLE_COLOR转换为COLORREF。
COLORREF textbkcolor=:GetSysColor(COLOR _ BTN face);
COLORREF text forecolor=this-translate color(this-GetForeColor());
COLORREF edgebkcolor=:GetSysColor(COLOR _ 3d face);
COLORREF edgeforecolor=:GetSysColor(COLOR _ 3d face);
COLORREF oldbkcolor=PDC-SetBkColor(textbkcolor);
COLORREF oldforecolor=PDC-SetTextColor(textforecolor);
if(m_brush.m_hObject=NULL)
m _刷。create solid brush(textbkcolor);
CB rush * pold brush=PDC-select object(m _ brush);
pdc-矩形(RC bounds);
CSize osize=PDC-gettext extent(m _ CSTR caption);
m _ size=osize
PDC-ext textout((RC bounds . right-osize . CX)/2,
(rcBounds.bottom-osize.cy)/2,
ETO_CLIPPEDETO_OPAQUE,
rcBounds,
m_cstrCaption,
m_cstrCaption。GetLength(),
NULL);
UINT borderstyle=EDGE _ RAISED
UINT border flags=BF _ RECT;
//绘制边框
PDC-SetBkColor(edgebkcolor);
PDC-SetTextColor(edgeforecolor);
PDC-draw edge((LPRECT)(LPC rect)RC bounds,borderstyle,border flags);
//恢复设置
PDC-SetBkColor(oldbkcolor);
PDC-SetTextColor(oldforecolor);
PDC-select object(pold font);
PDC-select object(pold brush);
上面的代码将为控件绘制更好的外观。当然,你可以随意改,直到你满意为止。以后可以根据需要添加一些属性和方法,编写相对的实现。大多数定义都是由WINZARD类维护的,所以您可以很容易地添加它们。一些建议:在编写控件属性页的过程中,需要将属性页上的标准控件与ActiveX控件的属性关联起来,这样当你动态改变标准控件的值时,ActiveX控件的属性也会随之改变。但问题是,如果用其他方法动态改变属性页上标准控件的值,ActiveX控件的属性不会改变。原因很简单,ActiveX控件的属性不知道自己变了。因此,不接受从标准控件传递的值。这个过程是由DoDataExchange()中的DD_P函数完成的。由于您手动更改了标准控件的值,因此需要使用SetModified()将属性更改通知给ActiveX控件,以便DD_P函数生效。另外,ActiveX控件的属性中有些数据类型是OLE_XXX类型,实际上是长值,COLECONTROL中的一些函数用来转换。尽量不要在类型转换的过程中使用强制,这样可能会带来一些意想不到的错误,鼓励使用缓冲机制。
其实关于控件的东西有很多,在MFC或者其他文档里都能找到。
3.ActiveX控件容器及其实现
ActiveX控件的容器实际上是ActiveX控件的客户端,使用ActiveX控件提供的各种功能。但它也为控件提供了一些属性和其他功能,以便控件可以更好地与之交互和操作。ActiveX控件的容器实际上是一个OLE容器,在实现了相应的接口来支持ActiveX控件之后,就变成了ActiveX控件的容器。
除了IUnknown之外,容器程序需要使用以下接口的一部分:ioleinplaceframe、ioleinplaceuwindow、ioleclientsite、ioleinplacesite、iadvisesink、iolecontrolsite、iolecontrolsite,idispatch、iproperytnotifyink、istorage、iolecontainer接口的具体定义请参考MSDN。MFC附带的一个例子是ActiveX控件测试容器,一个VC附带的工具。
下面举例说明ActiveX控件容器的实现以及一些问题的处理。本例中使用VC的向导生成一个支持容器的应用程序,生成的类中有一个用于包装查询文件中嵌入的每个OLE对象的类,一般称为xxxCntrItem。在本例中,它被重命名为CTestContainer98Item。在创建每个ActiveX控件时,都是通过这个类直接生成的。该类维护ActiveX控件的一些属性功能。而且,这个类支持序列化。
这样我们就可以通过序列化来保存控件的属性状态等信息。1).动态创建控件。这应该是一个微软倡导的微软倡导的ActiveX网络化多媒体对象技术网络化多媒体对象技术控件容器最重要的任务。为了能管理容器中的控件,首先它必须能动态的创建控件。因为每一个计算机输出缩微胶片组件都具有一个唯一的ID,CLSID,ActiveX控件也不例外,但是针对系统中成百上千的计算机输出缩微胶片对象,我们如何确定哪一个是微软倡导的微软倡导的ActiveX网络化多媒体对象技术网络化多媒体对象技术控件呢?在计算机输出缩微胶片基础中我们提到了为了能更快的定位计算机输出缩微胶片组件并加载它,COM子系统对计算机输出缩微胶片组件实行了分类别管理即利用类别来分类各种不同的计算机输出缩微胶片组件,ActiveX控件的类别是CATID_Control,所以我们可以通过这个信息来找到所有在系统中注册的控件,一般情况下我们是通过生成一个列表来表示所有这些控件。下面是经过改写的
CInsertControlDlg:RefreshControlList()函数
CArray m _ aImplementedCategories;
列表框m _ lbControls
ICatInformationPtr m _ pCatInfo
列表m _控件
void CInsertControlDlg:RefreshControlList()
{
布尔博登
HRESULT hResult
IEnumGUIDPtr pEnum
ULONG nImplementCategories
CATID * pcatidImpl
CLSID
LPOLESTR pszName
CString字符串名称
ULONG iCategory
项目间;
位置位置控制
CString strServerPath
CString字符串
//首先,清空列表框,并用m_aImplementCategories的数据填充pcatidImpl,作为m_pCatInfo函数
//EnumClassedOfCategories的第二个参数,来获取类标识符的枚举器
m_lbControls .重置内容();
nImplementCategories=m _ aImplementCategories .GetSize();
if (nImplementCategories==0)
{
nImplementCategories=(ULONG)-1;
pcatidImpl=NULL
}
其他
{
//为pcatidImpl分配内存,将m_aImplementCategories数据传给pcatidImpl
pcatidImpl=(CATID *)_ alloca(nImplementCategories * sizeof(CATID));
for(I类别=0;icate gory nimplementcategoriesiCategory)
pcatidImpl[I类]= m _ aImplementCategories[I类];//获取类标识符的枚举器
hResult=m _ pCatInfo-EnumClassesOfCategories(nImplementCategories,pcatidImpl,0,NULL,pEnum);
如果(失败(hResult))返回;
//然后通过枚举器枚举所有微软倡导的ActiveX网络化多媒体对象技术控件的CLSID,并取得相应的用户类型名称,加入到列表框中。
bDone=FALSE
而(!bDone)
{
hResult=pEnum- Next(1,clsid,NULL);//获得下一个微软倡导的ActiveX网络化多媒体对象技术控件的类标识符
if (hResult==S_OK)
{
pszName=NULL
hResult=olegreggetusertype(clsid,USERCLASSTYPE_FULL,pszName);//得到相应的用户类型名称
如果(成功(hResult))
{
strName=pszName
CoTaskMemFree(pszName);
pszName=NULL
iItem=m_lbControls .AddString(strName);
posControl=m_lControls .AddTail(clsid);
m_lbControls .SetItemDataPtr(iItem,pos control);
}
}
其他
{
bDone=TRUE
}
}
oncontrolselchange();
}
上面这个函数演示了如何从众多的计算机输出缩微胶片组件中提取微软倡导的微软倡导的ActiveX网络化多媒体对象技术网络化多媒体对象技术控件并把它们添加到一个列表框中,同时保留了它们的CLSID .其中m_pCatInfo在InitDialog中调用创建一个副本重置记录创建自己的实例。
在我们的到了某个微软倡导的微软倡导的ActiveX网络化多媒体对象技术网络化多媒体对象技术控件的类标识符以后,我们就可以利用CoCreateInstanse函数来生成该控件的实例。如上面所说的,每一个微软倡导的微软倡导的ActiveX网络化多媒体对象技术网络化多媒体对象技术控件都有一个包装类,我们在创建控件的时候,实际上都是通过这个包装类来进行,下面我们看一下,在这个包装类中创建控件的代码(删除了一些不是很重要的代码).
BOOL CActiveXContainerCntrItem:create control(ref clsid clsid)
{
我知道*我知道
//1.创建控件自己的实例,在下面的步骤将对控件的一些状态进行初始化
HRESULT HRESULT=cocreate实例(clsid,NULL,CLSCTX_INPROCCLSCTX_SERVER
IID_IUnknown,(void * *)双关已知);
如果(失败(hResult))
返回错误的
//2.在控件中请求超正析象管对象接口,
hResult=pun known-query接口(IID _ io leobject,(void * *)m _ LP对象);
如果(失败(hResult))
{
pun known-Release();
返回错误的
}
pun known-Release();
CString结构类型
get usertype(用户classtype _ SHORT,strUserType);
//3.创建一个唯一的名称,用来维护每个控件实例的唯一性
get document()-CreateUniqueItemName(this,strUserType,m _ strDisplayName);
//4.初始化控件的一些基本信息。
InitControlInfo();
BOOL bQuickActivate=FALSE
//5.如果控件支持IQuickActivate接口,请使用IQuickActive接口来激活控件。
bQuickActivate=quick activate();
如果(!bQuickActivate)
{
//6.如果控件不支持IQuickActiveX接口,则通过IOleObject接口设置控件的ClientSite。
m _ lpObject-GetMiscStatus(dv aspect _ CONTENT,m _ dwMiscStatus);
if(m _ dwMiscStatus ole misc _ SETCLIENTSITEFIRST)
hResult=m _ lpObject-set client site(get client site());
if(失败(hResult))
TRACE0(“无法为控件“n”设置client site”);
}
if(成功(hResult))
{
//7.支持IQuickActivate接口的控件必须使用以下步骤。
IPersistStreamInitPtr pPersistStreamInit;
IPersistStoragePtr pPersistStorage;
pPersistStreamInit=m _ lpObject
if (pPersistStreamInit!=空)
{
hResult=pPersistStreamInit-init new();
if (hResult==E_NOTIMPL)
hResult=S _ OK
}
其他
{
pPersistStorage=m _ lpObject
if (pPersistStorage!=空)
{
hResult=pPersistStorage-init new(m _ LP storage);
}
其他
{
hResult=S _ OK
}
}
}
返回finish create(hResult);//8.在此设置控件的事件处理和属性处理信息。
}
将解释上述注释中提到的一些内容,
2.IOleObject接口用于准备以下设置。因为这个接口用在很多地方,所以它存储在一个成员变量中。
注释3用于区分控件的多个实例。此方法由document类实现,该类根据控件的名称和编号维护同一控件的实例。
注释是用于初始化控件的一些基本信息。通过读取控件类型库,这些信息将控件的属性和事件放入各自的列表中,以便它们可以在以后用于响应控件的属性更改和事件。
注意5是针对QuickActivate()方法的。QuickActivate()方法首先从控件请求IQuickActivate接口。如果控件不支持此接口,它将返回FALSE。如果控件支持此接口,它将初始化两个结构:QACONTAINER和QACONTROL。然后用这两个结构调用IQuickActivate接口的QuickActivate方法,目的是提高ActiveX控件的加载速度。调用IQuickActivate接口的QuickActivate()方法后,必须调用IPersist*:Init和IPersist*:InitNew方法,控件应在QuickActivate方法中建立其连接点与容器接收方之间的连接。如果没有调用IPersist*:Init和IPersist*:InitNew,那么这些连接将不会生效。
到目前为止,已经建立了控件,但是这里没有与容器相关的内容。容器的实现描述如下。我们在例子中可以看到,程序的Document类继承自COleDocument,其中document实现了一个我们必须实现为容器的接口IOleContainer,在我们的程序中,我们可以通过GetStartPosition()、GetNextItem()等方法使用这个接口。该接口的主要功能是遍历容器中的控件或其他OLE对象。此外,一些必须实现的接口实际上已经在MFC中实现了。一般来说,我们只需要简单地使用这些封装的函数。本文主要讨论与控件的属性和事件处理相关的一些问题。在ActiveX控件及其实现中,我们提到了控件的属性和事件一般是由IDispatch实现的。在测试容器中,我们可以看到以下实现接口映射的代码。
BEGIN _ INTERFACE _ MAP(ctest container 98 item,COleClientItem)
接口_部件(CTestContainer98Item,IID_IServiceProvider,ServiceProvider)
INTERFACE _ PART(ctestcontainer 98 item,IID_IPropertyNotifySink,PropertyNotifySink)
INTERFACE _ PART(ctest container 98 item,IID_IDispatch,AmbientProperties)
接口_部件(CTestContainer98Item,IID_IOleControlSite,OleControlSite)
//INTERFACE _ PART(ctest container 98 item,IID_IOleInPlaceSiteEx,OleInPlaceSiteWindowless)
//INTERFACE _ PART(ctestcontainer 98 item,IID _ ioleiplacesitewindowless,OleInPlaceSiteWindowless)
END_INTERFACE_MAP()
正如我们所看到的,在上面的接口图中有6个接口,但是有两个条目被标注。让我们逐一解释这些接口:
第一个IServiceProvider在这里主要用来提供IBindHost接口。实际上,在实现容器时,这个接口是不必要的。
第二个IPropertyNotifySink用于实现控件属性更改的通知接收者。如果希望容器中的控件的属性发生更改时,容器能够获得相应的通知,则应该实现此接口。在这个接口的OnChange方法中,可以得到适配属性的对应DISPID。使用此DISPID,您可以进一步控制控件的某些属性特征。
第三个接口用于为控件提供环境属性。这个为控件提供环境属性的功能是由IDispatch接口实现的,每个环境属性都有一个特定的DISPID。因此,当控件调用GetAmbientxxx方法时,控件会要求容器提供相应的属性,这些属性都是由IDispatch接口实现的。
第四个接口是IOleControlSite。这个接口的主要功能是在容器内部提供一些站点对象来管理嵌入其中的控件。实现这个接口是可选的。
在上面的接口映射中,我们没有看到用于处理控件事件的接口映射。我们可以在测试容器的代码中看到下面的代码。
BEGIN _ INTERFACE _ PART(EventHandler,IDispatch)
STD method(GetIDsOfNames)(REFIID iid,LPOLESTR* ppszNames,UINT nNames,LCID lcid,DISPID * pDispIDs
STD method(GetTypeInfo)(UINT iTypeInfo,LCID lcid,iTypeInfo * * ppTypeInfo);
STD method(GetTypeInfoCount)(UINT * pnInfoCount);
STD method(Invoke)(DISPID DISPID member,REFIID iid,LCID lcid,WORD wFlags,DISPPARAMS* pdpParams,
VARIANT* pvarResult,EXCEPINFO * pExceptionInfo,UINT * piar gerror);
结束接口部分(事件处理程序)
很明显,这段代码是用来处理事件的,但是为什么不在接口的映射部分呢?如果你查看CTestContainer98Item类的代码,你会发现一个名为GetInterfaceHook()的方法。这个方法有一个const void*类型的参数pv,它实际上是一个IID类型的指针。请看下面的代码:
piid=(常数IID *)PV;
if( *piid==m_infoEvents。GetIID())
{
return(m _ xEventHandler);
}
现在我们知道了控件的事件是如何处理的。GetInterfaceHook()方法是CCmdTarget的一个方法,但是在MSDN中没有记载。在该方法中,还实现了其他接口的映射关系。
在这里,我们已经可以知道实现一个ActiveX控件容器所需的接口以及一些相关的问题。MFC类库为我们做了很多工作,实现了一些作为控件容器必须实现的接口,让我们在开发这类应用时有了一个很好的起点。
4.摘要
上面提到的只是一些基本概念和简单实现。开发一个ActiveX控件或其容器需要大量的知识和技术,因为COM本身就是一个庞大的技术规范,涉及到很多方面的知识,这些知识往往是其他基于COM的技术的基础。