2.2 COM 模型的原始实现
描述:COM是一个结构模型,它用来实现面向对象的、与语言无关的、且位置透明的组件或软件模块,Microsoft对COM的实现是一套短小的Win32API函数和大量的COM接口声明.
2.2.1 客户程序和服务器程序
服务器 -- 为其他软件实体提供一些功能的COM组件,或实现某一接口的软件模块.
客户机 -- 使用组件功能的软件实体,或使用接口的软件模块
在多数情况下,一个组件既可以是一定数量的COM接口的实现者,也可以是一个使用者
2.2.2 OLE 和 ActiveX
简单地说,COM是一个系统级别的标准,它提供了基本的对象模型服务,是一个软件交互操作标准,OLE 和 ActiveX是高级别服务的范例,是建立在软件交互操作标准之上的高级别服务.
2.2.3 Microsoft Transaction Server(MTS)
Microsoft 事务处理服务器(MTS) -- 提供一个针对中间组件的执行环境
COM+ -- COM 和 MTS编程模型的融合
2.3 COM 的精髓: 接口
描述: COM 的实现包括了一些Win32 API,其目的是为了引入COM环境.COM的主要工作是处理接口的实现和使用上的问题.一个COM接口同C++类的公共接口非常相似,COM采用一种与语言无关的,位置透明的方式描述方法和属性,以及把内部实现的细节完全封装起来.
2.3.1 C++ 的虚函数表
COM接口是通过 C++的 Vtable(虚函数表) 建立起来的.虚函数表提供了类实例功能的滞后绑定.
2.3.1.1 纯虚函数和抽象类
1)一个抽象类(Abstract Class)为所有派生类提供了一个模型或摸板.如:
class Fruit
{
public:
void put_Color(string str)
{
m_strColor = str;
}
string get_Color()
{
return m_strColor;
}
virtual void Draw() {};
private:
string m_strColor;
};
水果本身就是一个抽象的东西,我们无法列举出一个通用的水果对象.
2) 开发者使用抽象类来把具有相似特性的东西进行分类和归类
3) 抽象类提供的属性和行为可以被所有的派生类所分享.
4) 通过声明一个纯虚函数使类变得抽象,这意味着类不能直接被实现,但你还是可以使用指向该类的指针
2.3.1.2 Vtable
后绑定 -- 在运行时绑定函数的地址.
前绑定 -- 在编译时绑定函数的地址.
1) 当你把一个函数声明为虚函数时,编译器将在你的类结构里加上一个指针,即虚指针vptr.vptr指向一个Vtable结构,
该结构包含了类中所有虚函数的地址,也包含了基类.
2) java 和 Smalltalk,都对所有函数进行了滞后绑定,C++ 则是让程序员决定对哪个函数进行滞后绑定,这主要出于性能考虑,比如:vptr的初始化,查找等..
2.3.1.3 COM 如何使用Vtable
1) C++ 使用 Vtable 来实现多态功能,COM 则使用C++ 的 Vtable 结构来建立COM接口.
2) COM 接口实际就是一个指向Vtable结构的指针,因此它也只接纳方法.一个COM接口的用户只能访问组件的公共方法.
3) 示例:把 C++ 的Math类转化为一个COM组件,并通过组件的公共方法把一个基本接口向外部公开出来.
class Math
{
public:
long Add(long ,long);
long Subtract(long,long);
long Multiply(long,long);
long Divide(long,long);
private:
//implementation here...
};
2.3.2 COM 的接口
1) 建立一个Vtable公开组件的公共接口
class IMath
{
public:
virtual long Add(long,long) = 0;
virtual long Subtract(long,long) = 0;
virtual long Multiply(long,long) = 0;
virtual long Divide(long,long) = 0;
};
2) 派生出子类并提供各自的实现
class Math:public IMath
{
public:
long Add(long Op1,long Op2);
long Subtract(long Op1,long Op2);
long Multiply(long Op1,long Op2);
long Divide(long Op1,long Op2);
};
3)基类里虚函数的使用是COM设计的核心,抽象类定义提供了一个仅仅包含类的公共方法(接口)的Vtable.它唯一的目的是强制派生类以虚函数的方式实现组件的接口方法.
4)COM 只能通过一个Vtable指针类提供对它组件的访问,所以对组件实现的访问是无法完成的.
2.4 对 COM 接口的访问
描述:COM API 中最重要的一个函数是CoCreateInstance,客户应用程序在创建组件实例时使用.
// Create an instance and return the IMath interface
IMath *pMath;
HRESULT hr = CoCreateInstance(CLSID_Math, -- 类标识符
NULL,
CLSCTX_INPROC, -- 类上下文
IID_IMath, -- 接口标识符
(void **) &pMath ); -- 返回接口
// Use IMath
long lResult = pMath->Multiply(44,33);
访问接口的另一方法
IMath *pMath;
hr = pUnk->QueryInterface(IID_IMath,(void **)&pMath);
pUnk->Release();
if (FAILED(hr))
{
cout << "QueryInterface() for IMath failed" << endl;
CoUninitialize();
return -1;
}
2.5 组件的多接口特性
描述:COM 最重要的特性之一是赋予某一组件实现多个接口的能力,每一个接口都把小的,准确定义的功能集展示出来.然后,使用组件的客户可以直接与所需的功能模块打交道.
2.5.1 标准 COM 接口
1) 所有的COM组件都需要实现一个被称为IUnknown的标准COM接口(WINDOWS.H中引入)
2) IUnknown有两项功能:
QueryInterface -- 用户可以在指定的组件里要求使用一个特定的接口
AddRef,Release -- 帮助组件在生存期内进行管理
3) IUnknown 的定义:
class IUnknown
{
virtual HRESULT QueryInterface(REFID riid,void ** ppv) = 0;
virtual ULONG AddRef() = 0;
virtual ULONG Release() = 0;
};
参数说明:
REFID -- 特定接口ID的引用,它是我们查询某一接口时所使用的特有标识符
ppv -- 接口指针返回位置
HRESULT -- 指向COM所定义的错误结构,它包含了所有错误信息
2.5.2 HRESULT
1) HRESULT 被定义为DWORD类型(32位双字类型),高位表示函数调用是否成功,接着15位用来表示设备,并提供了一种对相关的返回代码进行分组的方式,这种分组方式是根据Windows错误生成子系统的实现.
2) COM中判断对方法调用是否成功的宏,SUCCEEDED 与 FAILED.补充一点,这些宏并非COM和ActiveX所特有,而是Win32环境里普遍使用的.在WINERROR.H文件中定义,调用成功,Win32里的返回值将具有前缀S_,否则E_.
2.5.3 IUnknown 的实现
1) COM 需要IUnknown接口在任何COM对象中出现,并且所有的COM接口都包含IUnknown接口(这样可以获得任意指定接口的指针)
// public interface definition
// An abstract class that derives from IUnknown
class IMath : public IUnknown
{
public:
virtual long Add(long Op1,long Op2) = 0;
virtual long Subtract(long Op1,long Op2) = 0;
virtual long Multiply(long Op1,long Op2) = 0;
virtual long Divide(long Op1,long Op2) = 0;
};
class IAdvancedMath : public IUnknown
{
public:
virtual long Factorial(short sOp) = 0;
virtual long Fibonacci(short sOp) = 0;
};
class Math: public IMath,public IAdvancedMath {};
// 实现 QueryInterface 方法
HRESULT Math::QueryInterface(REFID riid,void **ppv)
{
*ppv = 0;
if (riid == IID_IUnknown)
*ppv = (IMath*)this;
else if (riid == IID_IMATH)
*ppv = (IMath *)this;
else if (riid == IID_IAdvancedMath)
*ppv = (IAdvancedMath*)this;
if (*ppv)
{
(IUnknown*)(*ppv)->AddRef();
return (S_OK);
}
return (E_NOINTERFACE);
}
2) 客户通过COM进行实例化并访问组件功能
// Create an instance an retrieve its IUnknown interface
IUnknown* pUnk;
HRESULT hr = CoCreateInstance(CLSID_Math,
NULL,
CLSCTX_INPROC,
IID_IUnknown,
(void **)&pUnk);
// Query for IMath
IMath* pMath;
pUnk->QueryInterface(IID_IMath,(void **)&pMath);
// Use IMath
long lResult = pMath->Multiply(44,33);
2.5.4 组件的生存期
1) 对一个组件接口的访问通过 IUnknown::QueryInterface完成的
2) 组件的生成管理由 IUnknown::AddRef,Release 所提供的方法加以管理
3) COM组件的一个实例可以有多个接口连接到多个客户上,所以需要使用一种引用计数的功能.
Math::Math()
{
// Initialize our reference counter
m_lRef = 0;
}
ULONG Math::AddRef()
{
return InterlockedIncrement(&m_lRef);
}
ULONG Math::Release()
{
// 计数值为0时删除自身
if (InterlockedDecrement(&m_lRef) == 0)
{
delete this;
return 0;
}
return m_lRef;
}
4)组件的用户是否能够在合适的时候,并且正确地调用AddRef 和 Release 来增大和减小组件的引用记数器非常重要.它类似于new/delete
5)QueryInterface总是返回一个接口指针之前调用AddRef,所以使用它的时候无须再调用AddRef.
2.5.5 全局特有标识符
1)在分布式对象和基于组件的环境里,组件和它们的接口的独有标识符是最为重要的.
2)通用独有标识符(UUID)是一个独有的、128位的、并且具有非常高的可靠率的数值.它把一个独有的网络地址(48)位和一个非常精细的时间戳(100纳秒)结合在一起
3)COM对UUID的实现称为全局特有标识符(GUID).COM使用GUID来标识组件的类(CLSID),接口(IID),类型库和组件类属(CATID)以及其他的一些东西.
4)DEFINE_GUID宏创建一个全局常量,你可以在程序里到处使用该常量,或客户端或服务器端,然而你只能定义一次(INITGUID.H中)
//{A888F560-58E4-11d0-A68A-0000837E3100}
DEFINE_GUID(CLSID_Math,0xA888F560,0x58E4,0x11d0,0xa6,0x8a,0x0,
0x0,0x83,0x7e,0x31,0x0);
DEFINE_GUID(IID_Math,....)
DEFINE_GUID(IID_...)
5)也可以使用CoCreateGuid函数生成GUID,或 Visual C++ 中UUIDGEN工具
6) 常用GUID辅助函数
函数名称 功能
CoCreateGuid(GUID*pGuid) 采用鞭策的方法生成一个独有的GUID
isEqualGUID(REFGUID,REFGUID) 比较两个GUID
isEqualIID(REFIID,REFIID) 比较两个IID
isEqualCLSID(REFCLSID,REFCLSID) 比较两个CLSID
CLSIDFromProgID(LPCOLESTR,LPCLSID) 为给定的ProgID返回CLSID
ProgIDFromCLSID(REFCLSID,LPOLESTR *) 从CLSID里返回ProgID
2.5.6 标题标识符
组件由它的CLSID来唯一标识的,但是要记住组件的CLSID并不容易.COM用标题标识符或ProgID类对组件进行命名.一个简单的ProgID是一个简单的字符串,它通过注册表和组件的CLSID相关联.
2.5.7 注册表
1) HKEY_CLASSES_ROOT的子键CLSID 描述了每一个安装在系统上的组件
2) 重要的注册表主键项
项目名称 功能
ProgID 为COM类指定ProgID串,它不能包含多于39个字符,但是可以包含句号
InprocServer32 包含了32位DLL文件的路径和文件名称,它并不是一定需要包含路径.
如果它的确没有包含文件路径,那么只有当它位于Windows的PATH指定
的路径下时才可以被载入,16位版本里不包括扩展名"32".
VersionIndependentProgID 指定组件的最高版本
LocalServer32 包含了32位EXE文件的路径和文件名称
CurVer 组件类的最新版本的ProgID
2.5.8 组件的类别
Microsoft 提供了一种新的机制来描述组件的功能,被称为组件类别.它包括系统定义类别和用户定义类别
2.5.8.1 类别标识(CATID)
1)组件类别通过使用一个类别标识(CATID)来唯一地标定出来.
2)本地标识(locale ID)同CATID一起出现,该标识是通过一个十六进制的数字串和一个用户可读的字符串指定的.
3)已知的CATID存储在注册表的HKEY_CLASSES_ROOT\Componet Categories 键下.OLEVIEW工具可以根据组件的类别来显示组件
2.5.8.2 对组件进行分类
1)根据组件的功能分类.Implemented Categories 项列出这些组件所提供的功能类别
2)根据组件的可能客户所需要的功能分类.Required Categories项列出客户需要组件所提供的组件类别
3)Microsoft 定义了两个新的COM接口 - ICatRegister 和 ICatInfomation 支持组件类别
4)组件分类管理器实现了上面两个接口.它是系统级别的组件
2.5.8.3 组件分类管理器(Component CateGories Manager)
1) 组件分类管理器(CCM)是用来实现ICatRegister 和 ICatInfomation接口的一个简单的COM组件
2) 可用CoCreateInstance函数并传递由Microsoft定义的CCM CLSID:CLSID_StdComponentCategoriesMgr来建立一个CCM实例
2.5.8.4 ICatRegister
1) ICatRegister 接口提供了在系统级别和组件级别上注册和注销分类的方法
2) ICatRegister接口定义
interface ICatRegister : IUnknown
{
HRESULT RegisterCategories(ULONG cCategories,CATEGORYINFO rgCategoryInfo[]);
HRESULT UnRegisterCategories(ULONG cCategories,CATID rgcatid[]);
HRESULT RegisterClassImplCategories(REFCLSID rclsid,ULONG cCategories,CATID rgcatid[]);
HRESULT UnRegisterClassImplCategories(REFCLSID rclsid,ULONG cCategories,CATID rgcatid[]);
HRESULT RegisterClassReqCategories(REFCLSID rclsid,ULONG cCategories,CATID rgcatid[]);
HRESULT UnRegisterClassReqCategories(REFCLSID rclsid,ULONG cCategories,CATID rgcatid[]);
}
3)RegisterCategories方法把组件类别身份注册到系统上,即HKEY_CLASSES_ROOT\Component Categories键下
4)CATEGORYINFO结构
typedef struct tagCATEGORYINFO {
CATID catid;
LCID lcid;
OLECHAR szDescription [128];
} CATEGORYINFO;
5)使用RegisterCategories方法
// include the component category interfaces and symbols
include "comcat.h"
...
HRESULT CreateComponentCategory(CATID catid,WCHAR*catDescription)
{
ICatRegister *pcr = NULL;
HRESULT hr = S_OK;
// Create an instance of the category manager
hr = CoCreateInstance(CLSID_StdComponentCategoriesMgr,
NULL,
CLSCTX_INPROC_SERVER,
IID_ICatRegister,
(void **)&pcr);
if (FAILED(hr))
return hr;
CATEGORYINFO catinfo;
catinfo.catid = catid;
// English locale id in hex
catinfo.lcid = 0x0409;
// Make sure the description isn't too large
int len = wcslen(catDescription);
if (len > 127)
len = 127;
wcsncpy(catinfo.szDescription,catDescription,len);
catinfo.szDescription[len] = '\0';
hr = pcr->RegisterCategories(1,&catinfo);
pcr->Release();
return hr;
}
6)使用RegisterClassImplCategories方法为某一组件添加\Implemented Categories 注册表项
ICatRegister *pcr = NULL;
HRESULT hr = S_OK;
// Create an instance of the category manager
hr = CoCreateInstance(CLSID_StdComponentCategoriesMgr,
NULL,
CLSCTX_INPROC_SERVER,
IID_ICatRegister,
(void **)&pcr);
if (SUCCEEDED(hr))
{
// Register that we supper the "Control" category
CATID rgcatid[1];
rgcatid[0] = CATID_Control;
hr = pcr->RegisterClassImplCategories(clsid,1,rgcatid);
}
if (pcr != NULL)
pcr->Release();
7)同样,使用RegisterClassReqCategories方法给组件添加"Category Required"条目.代码同上
2.5.9 OLEVIEW
OLEVIEW 是Visual C++ 提供的一个工具程序,它是从COM的角度检查注册表的一个优秀工具.
2.5.10 组件的宿主文件
1)基于COM的组件要么被包容在一个可执行文件(EXE文件)里,要么被包容在一个Windows的动态链接库里.
2)基于COM的服务的三种不同的配置中实现
本地服务器(local server) --是一个可执行的组件宿主文件,它还支持COM组件之外的其他功能
进程内服务器(in-process server) --是Windows的一种DLL文件,它容纳了基于COM的组件
远程服务器(remote server) --是在远程计算机上载入和执行的组件宿主文件
3)组件实现应考虑的主要因素:
性能 -- 进程内方式下组件性能最好,它是在客户进程的地址空间内执行的,不需要在方法参数之间进行进程间传递,但也有可能使客户应用程序崩溃
健壮性 -- 对于在进程外执行的组件,即使发生了重大的错误,也不会使客户应用程序崩溃
2.5.11 类工厂
1)COM提供了一个标准接口IClassFactory来实例化一个组件,它是一种与语言无关的方法,必须实现该接口组件才可以让外部客户可以创建自己的组件的实例.
2)IClassFactory接口
class IClassFactory : public IUnknown
{
virtual HRESULT CreateInstance(LPUNKNOWN pUnk,REFID riid,void **ppv) = 0;
virtual HRESULT LockServer(BOOL fLock) = 0;
}
CreateInstance -- 创建指定组件类的实例并返回该实例所请求的接口
LockServer -- 为客户提供了一个在内存里锁定一台服务器的途径
3) 类工厂的代码
MathClassFactory::MathClassFactory()
{
m_lRef = 0;
}
MathClassFactory::~MathClassFactory()
{}
STDMETHODIMP_ MathClassFactory::QueryInterface(REFIID riid,void ** ppv )
{
*ppv = 0;
if (riid == IID_IUnknown || riid == IID_IClassFactory)
*ppv = this;
if (*ppv)
{
AddRef();
return S_OK;
}
return (E_NOINTERFACE);
}
STDMETHODIMP_(ULONG) MathClassFactory::AddRef()
{
return InterlockedIncrement(&m_lRef);
}
STDMETHODIMP_(ULONG) MathClassFactory::Release()
{
if (InterlockedDecrement(&m_lRef) == 0)
{
delete this;
return 0;
}
return m_lRef;
}
STDMETHODIMP_ MathClassFactory::CreateInstance(LPUNKNOWN pUnkOuter,REFIID riid,void **ppvObj)
{
Math *pMath;
HRESULT hr;
**ppvObj = 0;
// Create our component instance
pMath = new Math;
if (pMath == 0)
return (E_OUTOFMEMORY);
hr = pMath->QueryInterface(riid,ppvObj);
if (FAILED(hr))
delete pMath;
return hr;
}
STDMETHODIMP_ MathClassFactory::LockServer(BOOL fLock)
{
if (fLock)
InterlockedIncrement(&g_lLocks);
else
InterlockedDecrement(&g_lLocks);
return S_OK;
}
4)CoGetClassObject函数获取某一组件类工厂的直接访问
int main()
{
IClassFactory *pCF;
IMath *pMath;
HRESULT hr;
// Get the class factory for the Math class
hr = CoGetClassObject(CLSID_Math,
CLSCTX_INPROC,
NULL,
IID_IClassFactory,
&pCF);
// using the class factory interface create an instance of the
// component and return the IMath interface
pCF->CreateInstance(NULL,IID_IMath,*pMath);
// Release the class factory
pCF->Release();
// Use the component to do some work
long lResult = pMath->Add(100,433);
// Release it when we're finished
pMath->Release();
}
2.6 基于COM的复用技术
描述:COM提供了两种二进制方式的复用:包容(containment)和集合(aggregation)
2.6.1 包容(Containment)
1)包容和组合分别使用了COM对象的服务功能或内部嵌入C++类来实现代码的复用.被包容的组件接口只能以间接的方式展示给外部的用户这种间接的方法是通过包容(或外部)组件所提供的方法完成的.
2)同C++,内部对象的生存期完全受外部组件的控制.
2.6.2 集合(Aggrgation)
1)集合与包容的差别:在集合里,内部COM对象的接口可以直接被用户使用,外部集合对象的IUnknown接口提供了对所有内部对象接口的访问
2)对外部对象和内部对象生存期的管理必须通过IUnknown的实现来进行协调
2.7 COM的API函数
描述:Microsoft提供了一些COM,ActiveX和OLE专用的Win32 API函数.下面列举了一些:
基本的 COM 函数
函数名称 功能
CoInitialize,CoInitializeEx 初始化COM库以供进程使用
CoUninitialize(client and server) 释放COM库,在进程内服务器上不能使用该函数
CoGetClassObject(client) 为一个指定的COM对象获取一个类工厂实例
CoCreateGUID(client and server) 创建一个新的,特有的GUID
CoCreateInstance(client) 创建一个特定COM对象的实例,它可以位于远程计算机上
CoRegisterClass(server) 为一个特定COM对象的类工厂进行注册,表明它的存在
DllCanUnloadNow(in-process server) COM将定时调用它,用以决定DLL是否可以被下载,该函数又进程内服务器来实现
DllGetClassObject(server) 入口点由进程内服务器实现,从而客户进程可以获得它的类工厂接口
2.8 其他涉及COM的问题
2.8.1 C++ 里关于COM的宏:STDMETHOD 和 STDMETHODIMP
1)COM接口的声明和定义中可以使用四种宏
STDMETHOD,STDMETHODIMP -- 表明从某一方法中返回的HRESULT
STDMETHOD_,STDMETHODIMP_ -- 只能和IUnknown的AddRef和Release一起使用
2)除了两种情况以外,每一个COM的接口方法都应该返回一个HRESULT.
class IMath
{
public:
virtual long Add(long,long) = 0;
virtual long Subtract(long,long) = 0;
virtual long Multiply(long,long) = 0;
virtual long Divide(long ,long) = 0;
}
接口可以按照如下方法声明,使用COM的宏并且像指针一样把返回值移动到尾部
class IMath:public IUnknown
{
public:
STDMETHOD(ADD)(long,long,long *) PURE;
STDMETHOD(Subtract)(long,long,long*) PURE;
STDMETHOD(Multiply)(long,long,long*) PURE;
STDMETHOD(Divide)(long,long,long*) PURE;
};
3)STDMETHOD的实际展开情况和目标平台的类型,以及你究竟是使用C还是C++有关
// C++,Win32 下 OBJBASE.H
#define STDMETHODCALLTYPE __stdcall
...
#define STDMETHOD(method) virtual HRESULT STDMETHODCALLTYPE method
#define STDMETHOD_(type,method) virtual type STDMETHODCALLTYPE method
#define PURE = 0
#define STDMETHODIMP HRESULT STDMETHODCALLTYPE
#define STDMETHODIMP_(type) type STDMETHODCALLTYPE
其中__stdcall 是Microsoft特有的函数调用约定,它要求主函数在调用完毕后应该清理堆栈
4)STDMETHOD宏也是用在所实现类的接口方法的声明里,特别之处在于你不必使用PURE说明符
class Math:public IMath,public IAdvancedMath
{
// IMath
STDMETHOD(Add)(long,long,long*);
STDMETHOD(Subtract)(long,long,long*);
STDMETHOD(Multiply)(long,long,long*);
STDMETHOD(Divide)(long,long,long*);
};
5)STDMETHODIMP宏用在接口函数的实现里(.cpp)
STDMETHODIMP Math::Add(long Op1,long Op2,long *pResult)
{
*pResult = Op1 + Op2;
return S_OK;
}
STDMETHODIMP Math::Subtract(long lOp1,long lOp2,long *pResult)
{
*pReuslt = lOp1 - lOp2;
return S_OK;
}
2.8.2 COM和Unicode
1)所有COM函数和标准接口方法都需要使用Unicode字符串.
2)当我们把字符串传递给COM函数或让它们通过一个COM接口方法时,就必须把我们的字符串从ANSI型转变为Unicode型.有以下几种方法
方法一:在字符串的前面加上一个"L"
//Get the unique CLSID from the ProgID
HRESULT hr = ::CLSIDFromProgID(L"Chapter2.Math.1",&clsid);
方法二:使用自带的Win32 API函数
// conver the ProgID to Unicode
char *szProgID = "Chapter2.Math.1";
WCHAR szWideProgID[128];
CLSID clsid;
long lLen = MultiByteToWideChar(CP_ACP,
0,
szProgID,
strlen(szProgID),
szWideProgID,
sizeof(szWideProgID));
// Teminate the returned string
szWideProgID[lLen] = '\0';
方法三:使用MFC或ATL提供的宏
// provide temp variable
USES_CONVERSION;
// Get the unique CLSID from the ProgID
char * szProgID = "Chapter2.Math.1";
HRESULT hr = ::CLSIDFromProgID(A2W(szProgID),&clsid); // 相应的W2A
2.8.3 COM和多态性
1)多态性是指一个对象对同一个消息(C++中)可以有不同的回应方式
int main()
{
Fruit* pFruitList[4];
pFruitList[0] = new Apple;
pFruitList[1] = new Oranage;
pFruitList[2] = new Grape;
pFruitList[3] = new GrannySmith;
for (int i = 0; i < 4; i ++)
pFruitList[i]->Draw();
}
或
class IFruit
{
virtual void Draw() = 0;
... ...
};
IFruit* pFruit[4];
CoCreateInstance(CLSID_Apple,IID_IFruit,...,(void **)&(pFruit[0]));
CoCreateInstance(CLSID_Orange,IID_IFruit,...,(void **)&(pFruit[1]));
CoCreateInstance(CLSID_Grape,IID_IFruit,...,(void **)&(pFruit[1]));
CoCreateInstance(CLSID_GrannySmith,IID_IFruit,...,(void **)&(pFruit[1]));
for (int i = 0; i < 4; i ++)
pFruit[i]->Draw();