---------------------------------------------------
cd的作用到底是什么?
接口的实现与客户用分d来吗Q?/p>
不尽然。用CoCreateInstanceQ客户可以完全不必知道类厂的存在Q而创建组Ӟ获取lg实现的接口ƈ使用?/p>
即COM库可以完全抛开cd的概念,而是提供一个这L函数原型Q?br>CoCreateObject(REFID rclsid,...,REFID riid,void **ppItf);
用户在调用的时候可以对riid提供IID_Unknown或者特定于该对象的一个接口,直接获取该对象的IUnknown或特定的接口指针?/p>
可以看到Q这正是CoCreateInstance所作的事情?/p>
1 cd提供了间接创建类对象的方式:用户可以先获取ƈ持有cd接口指针Q通过该指针所指向的类厂接口创建类对象。适用于需要创建多个(或重复创建)cd象的地方Q减了每次都要定位对象库ƈ把对象库装入内存的开销?br>2 cd提供了保证组件库留在内存不被卸蝲出去的另一U方法:cd接口函数LockServer。组件库l护一个库范围计数器,只有该计数器?Ӟlg库才允许自己被卸载出内存。(与此相对Q引用计数是cd象范围的Q通过该类实现的各个接口来l护。如果一个类对象的引用计数达?Q那么该对象占有的内存就被释放,该对象上的接口指针也不再有效。)
除了调用LockServer锁定lg库以外,当创建的lg个数大于0Ӟlg库也不能被卸载。也可以_调用一ơLockServer()的作用相当于创徏了一个组件?/p>
-----------------------------------------------------------------------
客户一侧:
1 使用一个接口需要知道哪些信息?
备选:
接口IID
cd象(cdQCLSIDQ或ProgIDQ?br>接口函数原型Q参C敎ͼcdQ返回|
实现接口lg的线E模型(q程内、进E外、远E)Q?br>cd库typelib信息Q?/p>
服务一侧:
2 实现一个组件和接口以供客户调用Q需要提供哪些东西?
备选:
所有客户用组件和接口所需的内?br>额外的还有:
--------------------------------------------------------------------
为dlld.def文g与直接在需要导出的函数定义处指定_declspec( dllexport )有区别吗Q如果有是什么区别?
我发现在outdll.c中这h定:
__declspec( dllexport ) HRESULT DllGetClassObject (REFCLSID rclsid, REFIID riid, void **ppv)
会生编译错误:
1>------ Build started: Project: outside, Configuration: Debug Win32 ------
1>Compiling...
1>outdll.c
1>d:\outside-cf\outside\outdll.c(19) : error C2375: 'DllGetClassObject' : redefinition; different linkage
1> c:\program files\microsoft visual studio 8\vc\platformsdk\include\objbase.h(833) : see declaration of 'DllGetClassObject'
1>Build log was saved at "file://d:\outside-cf\outside\Debug\BuildLog.htm"
1>outside - 1 error(s), 0 warning(s)
========== Build: 0 succeeded, 1 failed, 1 up-to-date, 0 skipped ==========
c2375的解释意思是出错的函C用的链接指示W与之前声明的不同?br>Compiler Error C2375
'function' : redefinition; different linkage
The function is already declared with a different linkage specifier.
objbase.h中声明了DllGetClassObject()函数Q?br>STDAPI DllGetClassObject(IN REFCLSID rclsid, IN REFIID riid, OUT LPVOID FAR* ppv);
而?def文g没有问题?/p>
-----------------------------------------------------------------------------
初次执行l果Q?/p>
问题是L一个分配的内存没有释放Q?/p>
Ҏ打印出来的内存地址可以判断Q应该是先创建的cd对象的内存没有释放?br>查代码,main()中ƈ没有忘记调用Release(pCF)释放cd对象。打印Release(pCF)的返回|发现?Q即在类厂接口指针上调用了一ơReleaseQ那么,I竟是哪里少的呢Q?/p>
main()函数中有关类厂对象引用计数的地方是CoGetClassObject和ReleaseQCreateInstance跟类厂自q引用计数无关Q,q是一对增加引用计数和减少引用计数的对应操作,所?main()中应该没有问题?/p>
那么Q就只有创徏cd对象的时候了。下面看一下类厂对象是如何创徏的?br>首先Qmain调用CoGetClassObjectQ该函数p用dll中的DllGetClassObject。由于是W一ơ调用(不考虑其他客户使用该dll的情况)Q程序执行到CreateClassFactory(...),该函数执行完后,cd对象的引用计数是1?/p>
׃创徏成功Q因此l向下执行到QueryInterface,此时Q类厂对象的引用计数变成?。然后,DllGetClassObjectq回Qcom库函数CoGetClassObject也应该返回。注意,此时的类厂对象引用计数已l是2了!
因此Q问题就出在q里。main调用一ơCoGetClassObject后,cd对象的引用计数是2Q而不是我惛_中的1。于是,后面调用一ơRelease也就当然无法释放掉类场对象了?/p>
扑ֈ了原因,Ҏ很Ҏ了。这里我觉得需要把DllGetClassObject作如下修改:
修改后在执行Q内存都正常释放了?/p>
-------------------------------------------------------------------------------------------
CreateClassFactory代码说明
可以看到Q两行代码的效果是对引用计数?及减1Q这两行代码执行后,对引用计数的影响互相抉|Q等于没有改变引用计数。那么,把这两行同时注释掉,是不是可以呢Q?br>我的回答是:在本例中可以。因两行代码之间的QueryInterfaceL可以执行成功的(因ؓ是用IDD_ClassFactory来调用该函数的)。所以,即便把这两行代码同时注释掉,CreateClassFactory执行l束后,cd对象的引用计C增了1Q以后调用Release可以释放掉cd对象占用的内存?br>但是Q如果CFQueryInterface的代码编写中除了错误Q比如,像这样写Q?br>
那么Q这两行代码之间的QueryInterface׃执行出错Q那么类厂对象占用的内存永q没有机会释放了?br>也就是说QAddRef和Release虽然在作用上对引用计数来说相互抵消,但Release函数提供了释攑֯象内存的ZQ当引用计数?ӞQ如果不成对的调用他们,也就失去了管理对象内存(释放对象占用的内存)的机会?/p>
---------------------------------------------------------------------------
lg库outside文g说明Q?br> IFoo.h IFoo接口声明
outside.c lg对象、IFoo接口实现
cf.c cd对象、IClassFactory接口实现
outdll.c lg库导出函数实?br> outside.def lg库模块定义文Ӟ导出函数声明
outside.reg lg库注册文?/p>
----------------------------------------------------------------------------
源码Q?outside-cf
把该文中实现的代码整理汇d一个项目中。目前只是实现到一个中间阶D,重点在说明COM接口的实现原理,q没有包含类厂的部分。以后还需陆箋dcd{高U功能?/p>
文gl成Q?br>ifoo.h COM接口IFoo,接口ID IID_IFoo 声明文g?br>outside.c COM接口实现。这里实现IFoo的是一个结构体COutside.
util.h 一些宏定义、全局函数、变量声明文件?br>main.c W者ؓ实现目d的文件。提供main函数、内存管理函数Alloc,Free的实玎ͼ装Cq行库函数malloc和free.Q、接口ID定义?/p>
COM接口到底是什么?
COM接口是一个指向虚函数表的指针。通过q个指针可以讉K内存中某处的各个功能块,执行预定义的功能Q完成用Ld。这些功能块以函数的形式存在Q想不出q有其他形式:)Qƈ被调用。它们有一个共同点Q都包含一个指针参敎ͼ指向q些功能要操作的数据地址。在C++中,q个地址是对象的首地址Q也是cL员函C隐含的this指针。在C函数中ƈ没有q种现成的便利,因此代码实现中在接口定义时仍使用了接口指针(HRESULT (__stdcall * QueryInterface) (IFoo * This, const IID * const, void **)Q,而在接口函数实现时根据结构体布局l构Q从q个接口指针推算得到对象实例指针?/p>
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接口Q指向的虚函数表Q的头三个函数必LIUnknown接口的函敎ͼQueryInterface,AddRef和Release。在C++中,UCؓ从IUnknown接口l承?br>对于调用QueryInterface响应查询IID_IUnknwon得到的接口指针|同一个对象实现的所有接口必ȝ同。这是判断两个COM对象是否是同一个对象的标准?/p>
宏定?#8220;#define IUNK_VTABLE_OF(x) ((IUnknownVtbl *)((x)->lpVtbl))“说明
在预处理输出文gmain.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接口中的IFooVtblcd的指针拿出来Q?x)->lpVtbl)Q,q强制{换((IUnknownVtbl *)Q成IUnknownVtbl?br>“强制转换”的结果是什么呢Q是怎么做到的呢Q?br>很明显,l果是得到的指针不再是IFooVtbl *cdQ而是变成了IUnknownVtbl *cd。至于做法,pȝ应该记录每一个变量、表辑ּ的类型。当q行强制cd转换Ӟ(临时圎ͼ修改其类型ؓ转换到的cd?br>同理QQueryInterface, AddRef, Release宏定义中?IUnknown *)也是q种用法?/p>
可以看到Q宏“IUNK_VTABLE_OF“的作用是供宏QueryInterface,宏AddRefQ宏Release引用Q把IFooVtbl *cd转换为IUnknownVtbl *cdQ最l达到调用IUnknownVtbl中定义的三个QueryInterface,AddRef,Release函数?/p>
那么Q这U大费周章的目的是什么呢Qؓ什么不以IFooVtbl中三个函数的定义形式Q不通过强制转换来{换成必须的类型)Q直接调用IFooVtbl中定义的函数呢?虽然强制转换在参数gq不会造成改变Q最l调用的也是IFooVtbl定义的函敎ͼFooQueryInterface,FooAddRef,FooReleaseQ?/p>
Z么一定要通过IUnknown接口指针调用q三个函数呢Q修改QueryInterface宏定义如下:
#define QueryInterface(pif, iid, pintf) \
(((pif)->lpVtbl)->QueryInterface(pif, iid, (void **)(pintf)))
即通过IFoo接口指针来调用由IUnknown引入的函敎ͼ有什么不对的地方吗?
试验表明Q将QueryInterface宏定义如下也可以~译通过Q执行v来也没有出现M异常?br>#define QueryInterface(pif, iid, pintf) \
(((pif)->lpVtbl)->QueryInterface(pif, iid, (void **)(pintf)))
对于IUnknown接口的三个函敎ͼ调用时传递的参数是IUnknown *cdQ见QueryInterface, AddRef, Release宏定义)Q而函数定义中QFooQueryInterface, FooAddRef, FooReleaseQ声明的参数是IFoo *cdQ这U不一致的情况是怎么出现的?q种不一致不会有问题吗?
q种不一致的产生是由于从不同的角度看待引L。如果从IUnknown接口来看Q那么接口函C的第一个参数类型就是IUnknown *;如果从IFoo来看Q那么第一个参数的cd是IFoo *?/p>
q种不一致性只是针对于~译器对于类型的~译要求有意义的Q在接口实现及用时Q传递给lpVtbl->QueryInterface, lpVtbl->AddRef,lpVtbl->Release的第一个参数在g都是相同的,都是实现该接口的内存地址Q在本例中是COutside对象的首地址Q?/p>
一些语法现象回?/p>
函数指针变量定义、赋值及调用?br>HRESULT (__stdcall * pQI) (IFoo * This, const IID * const, void **) ;
定义一个函数指针变量pQI,该变量指?#8220;q回HRESULT,?个参数分别ؓcdIFoo *,const IID * const, void **”的函数?/p>
typedef HRESULT (__stdcall * QIType) (IFoo * This, const IID * const, void **) ;
定义一个函数指针类型,该类型的指针指向“q回HRESULT,?个参数分别ؓcdIFoo *,const IID * const, void **”的函数?/p>
HRESULT __stdcall QueryInterface(IFoo * This, const IID * const, void **) ;//函数声明CZ
pQI = 0; // 函数指针赋|0表示不指向Q何函数?br>pQI = QueryInterface; // 函数指针赋|pQI指向QueryInterface?br>pQI = &QueryInterface; // 与上面等仗?/p>
QueryInterface(&this->ifoo, riid, ppv); // 使用函数名直接调?br>pQI(&this->ifoo, riid, ppv); // 函数指针调用
(*pQI)(&this->ifoo, riid, ppv); // W二U函数指针调用方?/p>
宏定义、展开规则
对于宏,一直有一U雾里看q感觉Q似乎很随意Q怎么来都行,比如Q?br>#define AddRef(pif) \
(IUNK_VTABLE_OF(pif)->AddRef((IUnknown *)(pif)))
宏定义应该是可以嵌套的,卛_定义?#8220;内容“中还可以包含Q嵌套)宏,如本例,“IUNK_VTABLE_OF”是嵌套宏。在展开的时候,嵌套的宏也一q展开Q替换成定义的内容)Q直C再有宏ؓ止?br>那么有两个疑问Q?br>1。如果被嵌套的宏包含Q直接或间接Q定义的宏,那么展开没完没了,d@环了?br>2。如果定义的内容中有跟定义的宏同名的字符Ԍ比如上面的例子IUNK_VTABLE_OFQ,那么怎么区分q同名的东东是嵌套的宏(需要展开Q,q是一般的字符Ԍ不需要展开Q?
函数调用规范U定、main函数调用规范?/p>
一开始把几个文g汇d目里时Q编译通不q,错误提示大致意思是Q不能把一U调用规范的函数指针转换成另一U调用规范的函数指针。后来把调用规范改ؓ /Gz(__stdcall),~译为(Compile AsQ改?TC(Compile As C Code)好了?/p>
x是对?c文gQ编译器~省使用的是__cdeclQ而IFoo中的接口宏定义在win32下展开成了__stdcallQ所以出C矛盾。而?Gz强制未声明调用规范的函数使用__stdcallQ实现就与声明一致了?/p>
(size_t)&(((s *)0)->m)
c++E序员也讔R知道Q访问地址“0”处的成员是一大忌Q会造成GP。然而,取地址“0”处的成员的地址Q却是个合法的操作。虽然地址“0”处ƈ没有什么内容,但是Q如果在地址0处存放一个内容,那么该内容中的成员也是有地址的。本例中正是巧妙地利用这U方法,从接口地址计算得出实现该接口的实例地址Q进而访问实例的内部变量?br>
------------------------------------------------------------------------------------
2009q???br>附上源码Q?a href="http://www.shnenglu.com/Files/gracelee/outside.zip">/Files/gracelee/outside.zip
代码执行l果Q?br>
The
#ifndef xx #define xx ... #endif
method is to make sure that a header file isn't included more than once from the same c file.
You can not - and normally don't want to - stop multiple c files from including the same header file.
A header file is included because:
1) You have specifically added a line #include "xx" or #include <xx> in the source file. Don't do that unless you want the file to be included :)
2) You are including one header file, that it it's turn (one or more steps away) includes another header file. But a header file should only contain a recursive #include if it really needs that other file for some declarations. Hence, you need to include it.
What does this mean?
If the header file must be seen by multiple source files, you can't use it to allocate global variables, since the linker would then complain about multiple sets of global variables with the same name. This can be solved with the following:
//globals.h #ifndef _GLOBALS_H #define _GLOBALS_H #if defined MAIN #define EXTERN #else #define EXTERN extern #endif ... EXTERN int my_global_variable; #endif // _GLOBALS_H
// main.c
#define MAIN
#include "globals.h"
...
// misc.c #include "globals.h" ...
In this case, only the inclusion in main.c will result in an "allocation" of global variables, because the #define EXTERN will be empty. All other source files that includes "globals.h" will just see the type information for the global variables.
׃定义了操作符重蝲CDerived::operator long() 和CBase::operator long()Q?9行得以编译通过。同理,定义了CBase::operator char()Q?1行可以编译?br>
执行l果为:
CBase constructor()
CDerived constructor()
CDerived::operator long()
CBase::operator long()
lTmp=0
CBase::operator char()
cTmp=a
CDerived destructor()
CBase destructor()
q里涉及到的概念主要有:
1 cL员操作符重蝲Q得用户定义类型{换ؓ内徏cd成ؓ可能。对于用户定义类型之间的转换Q还可以通过构造函数的方式q行Q?br>2 自动cd转换。自动类型{换发生的情况有以下几U:
函数调用时传递的实参cd与函数声明中指定的参数类型不匚w
函数q回的对象类型与函数声明中指定的q回cd不匹?br>表达式中操作数的cd不一_q正是上面例子中的情况)