关闭

ATL初探(COM对象的创建过程)

1904人阅读 评论(0) 收藏 举报

在这里不说COM对象的创建过程,而只记录一下我发现谜底的过程. 本人Blog的所有原创文章,由于本人的水平非常有限,本人Blog里的所有原创文章,大多只是本人的经验,都只是为自己而写,作为记录以备后查,如有人因此而受到误导,本人全不负责.

下面的是文章正文,可能要有较好的C++基础才能看得明白,也只有受过ATL困惑和正在受到困惑的人才能看得爽,否则味同嚼蜡:

有个问题曾经困惑我,为什么我的ATL工程里,我的组件并没有实现QueryInterface等纯虚函数,却能够生成组件对象呢,答案是:ATL已经把这些可以有规律的代码帮助你自动生成了, 接口就是一组纯虚函数的集合, 生成的组件对象所属类一定都是已经实现了接口的,只是ATL帮我们做了些体力活而已.
 关于"接口是一组纯虚函数的集合"这个概念是很正确的,且不说自定义接口继承而来的AddRef等,就算我们自己添加的自定义函数,也会被ATL打下"纯虚"的烙印, 这一点可以印证:如为自定义接口IMyInterface添加方法AddLong, 可以在工程头文件MyProject.h中看到这么些代码段:
 IMyInterface:public IUnknown
    {
    public:
        virtual HRESULT STDMETHODCALLTYPE AddLong( ...) = 0;      //这里,ATL并未请示我们就把新增的函数搞成纯虚了.
    };
 这里,虽然后来我手动实现了这个纯虚函数,但仍然无法改变IMyInterface成为抽象类的命运. 但后来为什么生成了IMyInterface的对象呢? 回答只能说是被ATL装封了,我们看到的只是表面现象,不是事实.那事实是什么呢?在下面:


#ifndef A
#define A
#else
 cout<<"aaa"<<endl;       //这些与一般的if/else还是有区别的,这里,如果不执行#else,则两条输出语句都不会被执行,就好像
 cout<<"bbb"<<endl;  //在一般的if/else上加了个{}一样. 之所以写在这里,是因为在ATL的工程头文件里出现了这种用法.
#endif 

下面是工程头文件中的代码摘要:
#ifndef ....
typedef interface IInterface1 IInterface1;    //IInterface1 是接口,对应着下面的Interface1 类. 接口定义为interface
#endif           //至于为什么要来两个就不知道了,去掉一个也没报错,纯C++中来还两个报错呢.
---------------------
#ifdef __cplusplus
typedef class Interface1 Interface1;
#else          //这里,根据情况定义类为class还是struct,但要注意,上面的interface肯定是struct的
typedef struct Interface1 Interface1;      //因此接口在ATL中肯定是用struct形式实现的.
#endif
--------------------下面的一段至为关键:
#if defined(__cplusplus)&&!defined(CINTERFACE)

    IInterface1 : public IUnknown //C++风格接口,不要被它的样子蒙蔽,这个IInterface1看上去是class,其实是个struct.
    {     //不信的话可以去掉下面这个public,COM接口指针仍然能够正常调用AddLong方法而不会报错
    public:    //说调用了私有成员.
        virtual HRESULT STDMETHODCALLTYPE AddLong( long num1,long num2,long __RPC_FAR *plong) = 0;
    };                                   //STDMETHODCALLTYPE 就是STDCALL
   
#else  /* C style interface */C风格接口

    typedef struct IInterface1Vtbl //严重注意,这里不是接口,还是接口虚函数表!!!
    {
       ... QueryInterface(...);
       ... AddRef (...);
       ... Release(...);
       ... AddLong(...);          //自定义函数应该靠后.这里罗列出了所有的函数,后面就不用再说继承自IUnknown了.
    } IInterface1Vtbl;

    interface IInterface1   //这里的C风格接口的真正定义,它的内含就是一个虚函数表的指针. 上面那个#else一直管到
    {        //#endif之前.
        CONST_VTBL struct IInterface1Vtbl __RPC_FAR *lpVtbl;
    };
#define IInterface1_QueryInterface(This,riid,ppvObject)  (This)->lpVtbl -> QueryInterface(This,riid,ppvObject)
#define IInterface1_AddRef(This) /*这些#define说明了C风格接口的调用方式*/  (This)->lpVtbl -> AddRef(This)
#define IInterface1_Release(This)    (This)->lpVtbl -> Release(This)
#define IInterface1_AddLong(This,num1,num2,plong)  (This)->lpVtbl -> AddLong(This,num1,num2,plong)

#endif                                 //C风格接口定义至此才结束!!!

 至此,明白了C/C++中COM类(或称对象)与接口的不同,还有什么不明白的呢?仍然是上面说的,ATL对纯虚函数实现的封装.下面我解决最难的问题,抽象类是生成不了对象的,但抽象类可以用作指针,同样可以实现"基类指针指向派生类对象,调用的是派生类对象"的效果, ATL中,实际生成的COM对象,就是我们在ATL代码编辑器中看到的那个类(不是接口哦)的对象,ATL只是封装了QueryInterface,AddRef等方法的实现!!

 COM类与接口的关系?二者是如何组合起来的?
资料收集:
    一,在类的头文件中体现出来了,那里会有多个继承,竟然发现COM类从COM接口派生!!
    二,在工程头文件中,接口的方法都是纯虚型的,但在类的头文件中可以看出,这些类继承了接口,类中重载了这些纯虚函数,这时不再是纯虚函数,那就不是抽象类了,可以生成类的对象即COM对象了!!
    三,工程头文件中的 MyClass与类头文件中的CMyClass是一样的,都是指COM类. 不信可以去工程头文件中在MyClass的前面加个C!!
    结论:  一个类之所以容纳了多个接口,就是通过继承这多个接口,得到它们的成员! 这个生成的COM对象差点就是我们在ATL中看到的类了!!为什么说"差点"呢,因为很多书上都说"COM对象的产生过程是很复杂的",所以我很不确定,但我知道COM对象在ATL中,其实最终都是CComObject模板类的实例对象,MSDN2005的说法是: template<class Base >
class CComObject : public Base
 


 

Parameters
Base
Your class, derived from CComObjectRoot or CComObjectRootEx, as well as from any other interfaces you want to support on the object.

因此就是说,实际的COM对象是我们定义在ATL中的类的派生类的对象.


至此,我终于发现了内含,事实的真相是: ATL的类继承自它的接口,往接口中添加的函数变成纯虚函数之后,虽然接口可以实现它们,但它并未去实现这些函数,都由它们的派生类(COM类来实现),这点可去类的.CPP文件中看到! 至于客户端为什么能够准确地使用某个接口指针调用相应的函数,这就是C++的特性"基类指针(接口指针)指向派生类对象(COM对象)时,调用的是派生类的方法", C++继承时还有一个性质,基类指针指向派生类对象时,只能访问到基类中出现过的成员,最后生成对象时,再派生一次.下面的代码是俺自己写的,有点淋漓尽致的感觉:)自恋一下:

struct IMyInterface           //interface IMyInterface
{         //这段在工程头文件中看到
public:
 virtual void QueryInterface()=0;
 virtual void Add()=0;
};

//////////////////////////////////////////////////////////////////////////

struct IYourInterface         //inteface IYourInterface
{         //这段在工程头文件中看到
public:
 virtual void QueryInterface()=0;
 virtual void Dev()=0;
};

//////////////////////////////////////////////////////////////////////////
class MyClass:public IMyInterface,public IYourInterface  //class MyClass
{        //这段内容在MyClass.h头文件中看到.
 void QueryInterface();
 void Add();
 void Dev();
};
void MyClass::QueryInterface()    //这个函数是ATL封装实现的.
{
 cout<<"QueryInterface of MyClass from IUnknown"<<endl;
}
void MyClass::Add()     //这个是我们自己实现的,在MyClass.cpp中
{
 cout<<"Add of MyClass from IMyInterface"<<endl;
}
void MyClass::Dev()     //这个是我们自己实现的,在MyClass.cpp中
{
 cout<<"Dev of MyClass from IYourInterface"<<endl;
}
//////////////////////////////////////////////////////////////////////////

class CComObject:public MyClass{}; //简单继承,实现完全模拟,用它来生成最终COM对象

//////////////////////////////////////////////////////////////////////////

void main()       //这里是客户端调用COM服务
{
 IMyInterface*pMy=NULL;
 IYourInterface*pYo=NULL;
 CComObject obj;     //生成COM对象

 
 pMy=&obj;      //这里,相当于CoCreateInstance,即要COM对象也要COM接口
 pMy->Add();
 pMy->QueryInterface();

 pYo=&obj;      //这里,相当于CoCreateInstance,即要COM对象也要COM接口
 pYo->Dev();      //不过ATL中已可以通过QueryInterface实现.
 pYo->QueryInterface();
}

 一个类中可有多个接口,不过好像只能直接获得已有的其他类的已有接口,在.IDL文件中并未体现出来,但在类的头文件中体现出来了.
 一个COM组件可有多个类,这些类彼此之间是基本独立的. 我们不能通过一个对象的接口指针去获取(QueryInterface)另一个另一个对象的接口指针,这是因为QueryInterface本身没有使用COM对象作为参数(CLSID_**),QueryInterface得到接口肯定只能是本对象的另一接口,如果要得到另一对象的接口指针并调用其方法,有两种办法: 一,再调用一次CoCreateInstance或与之类似的API; 二,去ATL程序中,在COM类上右键选择"Impliment interface",再选择你想要得到其指针的接口,OK按钮一接下,在COM类就从这个接口派生了.这时重新编译ATL工程就可以了. 去客户端中就可以通过QueryInterface获取新接口的指针了.

0
0

查看评论
* 以上用户言论只代表其个人观点,不代表CSDN网站的观点或立场
    个人资料
    • 访问:60411次
    • 积分:801
    • 等级:
    • 排名:千里之外
    • 原创:18篇
    • 转载:3篇
    • 译文:0篇
    • 评论:23条
    文章分类
    最新评论