副标题#e#
把该文中实现的代码整理汇总到一个项目中。今朝只是实现到一其中间阶段,重点在说明COM接口的实现道理,还没有包括类厂的部门。今后还需连续添加类厂等高级成果。
文件构成:
ifoo.h COM接口IFoo,接口ID IID_IFoo 声明文件。
outside.c COM接话柄现。这里实现IFoo的是一个布局体COutside.
util.h 一些宏界说、全局函数、变量声明文件。
main.c 笔者为实现项目添加的文件。提供main函数、内存打点函数Alloc,Free的实现(封装C运行库函数malloc和free.)、接口ID界说。
COM接口到底是什么?
COM接口是一个指向虚函数表的指针。通过这个指针可以会见内存中某处的各个成果块,执行预界说的成果,完成用户的任务。这些成果块以函数的形式存在(想不出尚有其他形式:))并被挪用。它们有一个配合点:都包括一个指针参数,指向这些成果要操纵的数据地点。在C++中,这个地点就是工具的首地点,也就是类成员函数中隐含的this指针。在C函数中并没有这种现成的便利,因此代码实现中在接口界说时仍利用了接口指针(HRESULT (__stdcall * QueryInterface) (IFoo * This, const IID * const, void **)),而在接口函数实现时按照布局体机关布局,从这个接口指针推算获得工具实例指针。
typedef struct IFoo
{
struct IFooVtbl * lpVtbl;
} IFoo;
typedef struct IFooVtbl IFooVtbl;
struct IFooVtbl
{
HRESULT (__stdcall * QueryInterface) (IFoo * This, const IID * const, void **) ;
ULONG (__stdcall * AddRef) (IFoo * This) ;
ULONG (__stdcall * Release) (IFoo * This) ;
HRESULT (__stdcall * SetValue) (IFoo * This, int) ;
HRESULT (__stdcall * GetValue) (IFoo * This, int *) ;
};
COM接口的要求:
每一个COM接口(指向的虚函数表)的头三个函数必需是IUnknown接口的函数:QueryInterface,AddRef和Release.在C++中,称为从IUnknown接口担任。
对付挪用QueryInterface响应查询IID_IUnknwon获得的接口指针值,同一个工具实现的所有接口必需沟通。这是判定两个COM工具是否是同一个工具的尺度。
宏界说“#define IUNK_VTABLE_OF(x) ((IUnknownVtbl *)((x)->lpVtbl))”说明
#p#副标题#e#
在预处理惩罚输出文件main.i中可以找到IUnknownVtbl和IFooVtbl的声明:
typedef struct IUnknownVtbl
{
HRESULT ( __stdcall *QueryInterface )(
IUnknown * This,
const IID * const riid,
void **ppvObject);
ULONG ( __stdcall *AddRef )(
IUnknown * This);
ULONG ( __stdcall *Release )(
IUnknown * This);
} IUnknownVtbl;
struct IUnknown
{
struct IUnknownVtbl *lpVtbl;
};
struct IFooVtbl
{
HRESULT (__stdcall * QueryInterface) (IFoo * This, const IID * const, void **) ;
ULONG (__stdcall * AddRef) (IFoo * This) ;
ULONG (__stdcall * Release) (IFoo * This) ;
HRESULT (__stdcall * SetValue) (IFoo * This, int) ;
HRESULT (__stdcall * GetValue) (IFoo * This, int *) ;
};
该宏界说的浸染就是把IFoo接口中的IFooVtbl范例的指针拿出来((x)->lpVtbl)),并强制转换((IUnknownVtbl *))成IUnknownVtbl.
“强制转换”的功效是什么呢?是怎么做到的呢?
很明明,功效就是获得的指针不再是IFooVtbl *范例,而是酿成了IUnknownVtbl *范例。至于做法,系统应该记录每一个变量、表达式的范例。当举办强制范例转换时,就(姑且地)修改其范例为转换到的范例。
同理,QueryInterface, AddRef, Release宏界说中的(IUnknown *)也是这种用法。
可以看到,宏“IUNK_VTABLE_OF”的浸染是供宏QueryInterface,宏AddRef,宏Release引用,把IFooVtbl *范例转换为IUnknownVtbl *范例,最终到达挪用IUnknownVtbl中界说的三个QueryInterface,AddRef,Release函数。
那么,这种大费周章的目标是什么呢?为什么不以IFooVtbl中三个函数的界说形式(不通过强制转换来转换成必需的范例),直接挪用IFooVtbl中界说的函数呢?固然强制转换在参数值上并不会造成改变,最终挪用的也是IFooVtbl界说的函数(FooQueryInterface,FooAddRef,FooRelease)。
为什么必然要通过IUnknown接口指针挪用这三个函数呢?修改QueryInterface宏界说如下:
#define QueryInterface(pif, iid, pintf) \
(((pif)->lpVtbl)->QueryInterface(pif, iid, (void **)(pintf)))
即通过IFoo接口指针来挪用由IUnknown引入的函数,有什么差池的处所吗?
试验表白,将QueryInterface宏界说如下也可以编译通过,执行起来也没有呈现任何异常。
#define QueryInterface(pif, iid, pintf) \
(((pif)->lpVtbl)->QueryInterface(pif, iid, (void **)(pintf)))
#p#分页标题#e#
对付IUnknown接口的三个函数,挪用时通报的参数是IUnknown *范例(见QueryInterface, AddRef, Release宏界说),而函数界说中(FooQueryInterface, FooAddRef, FooRelease)声明的参数是IFoo *范例,这种纷歧致的环境是怎么呈现的?这种纷歧致不会有问题吗?
这种纷歧致的发生是由于从差异的角度对待引起的。假如从IUnknown接口来看,那么接口函数中的第一个参数范例就是IUnknown *;假如从IFoo来看,那么第一个参数的范例就是IFoo *.
这种纷歧致性只是针对付编译器对付范例的编译要求有意义的,在接话柄现及利用时,通报给lpVtbl->QueryInterface, lpVtbl->AddRef,lpVtbl->Release的第一个参数在值上都是沟通的,都是实现该接口的内存地点(在本例中是COutside工具的首地点)。
一些语法现象回首
函数指针变量界说、赋值及挪用。
HRESULT (__stdcall * pQI) (IFoo * This, const IID * const, void **) ;
界说一个函数指针变量pQI,该变量指向“返回HRESULT,取3个参数别离为范例IFoo *,const IID * const, void **”的函数。
typedef HRESULT (__stdcall * QIType) (IFoo * This, const IID * const, void **) ;
界说一个函数指针范例,该范例的指针指向“返回HRESULT,取3个参数别离为范例IFoo *,const IID * const, void **”的函数。
HRESULT __stdcall QueryInterface(IFoo * This, const IID * const, void **) ;//函数声昭示例
pQI = 0; // 函数指针赋值,0暗示不指向任何函数。
pQI = QueryInterface; // 函数指针赋值,pQI指向QueryInterface。
pQI = &QueryInterface; // 与上面等价。
QueryInterface(&this->ifoo, riid, ppv); // 利用函数名直接挪用
pQI(&this->ifoo, riid, ppv); // 函数指针挪用
(*pQI)(&this->ifoo, riid, ppv); // 第二种函数指针挪用方法
宏界说、展开法则
对付宏,一直有一种雾里看花的感受,好像很随意,怎么来都行,好比:
#define AddRef(pif) \
(IUNK_VTABLE_OF(pif)->AddRef((IUnknown *)(pif)))
宏界说应该是可以嵌套的,即宏界说的“内容”中还可以包括(嵌套)宏,如本例,“IUNK_VTABLE_OF”就是嵌套宏。在展开的时候,将嵌套的宏也一并展开(替换成界说的内容),直到不再有宏为止。
那么就有两个疑问:
1.假如被嵌套的宏包括(直接或间接)界说的宏,那么展开就没完没了,死轮回了。
2.假如界说的内容中有跟界说的宏同名的字符串(好比上面的例子IUNK_VTABLE_OF),那么怎么区分这同名的东东是嵌套的宏(需要展开),照旧一般的字符串(不需要展开)?
函数挪用类型约定、main函数挪用类型。
一开始把几个文件汇总到项目里时,编译通不外,错误提示大抵意思是,不能把一种挪用类型的函数指针转换成另一种挪用类型的函数指针。厥后把挪用类型改为 /Gz(__stdcall),编译为(Compile As)改为/TC(Compile As C Code)就好了。
想来是对付。c文件,编译器缺省利用的是__cdecl,而IFoo中的接口宏界说在win32下展开成了__stdcall,所以呈现了抵牾。而利用/Gz强制未声明挪用类型的函数利用__stdcall,实现就与声明一致了。
(size_t)&(((s *)0)->m)
c++措施员也许都知道,会见解点“0”处的成员是一大忌,会造成GP.然而,取地点“0”处的成员的地点,却是个正当的操纵。固然地点“0”处并没有什么内容,可是,假如在地点0处存放一个内容,那么该内容中的成员也是有地点的。本例中正是巧妙地操作这种要领,从接口地点计较得出实现该接口的实例地点,进而会见实例的内部变量。