1 / 16

十六 ATL

十六 ATL. ATL 对 COM 客户端的支持 使用 ATL 开发 COM 组件. 1 ATL 对 COM 客户端的支持. ATL 对 COM 客户的支持主要体现在智能指针上。 回顾: 当没有对 COM 对象进行引用计数时,对象的每个接口指针必须密切配合以保持内存的正确性。 比如: void f(void){ IFastString *pfs=0; IPersistentObject *ppo=0; pfs=CreateFastString("asdf"); if(pfs) {

lydie
Download Presentation

十六 ATL

An Image/Link below is provided (as is) to download presentation Download Policy: Content on the Website is provided to you AS IS for your information and personal use and may not be sold / licensed / shared on other websites without getting consent from its author. Content is provided to you AS IS for your information and personal use only. Download presentation by click this link. While downloading, if for some reason you are not able to download a presentation, the publisher may have deleted the file from their server. During download, if you can't get a presentation, the file might be deleted by the publisher.

E N D

Presentation Transcript


  1. 十六 ATL • ATL对COM客户端的支持 • 使用ATL开发COM组件

  2. 1 ATL对COM客户端的支持 • ATL对COM客户的支持主要体现在智能指针上。 • 回顾: 当没有对COM对象进行引用计数时,对象的每个接口指针必须密切配合以保持内存的正确性。 比如: void f(void){ IFastString *pfs=0; IPersistentObject *ppo=0; pfs=CreateFastString("asdf"); if(pfs) { ppo=(IPersistentObject*) pfs->Dynamic_Cast("IPersistentObject"); if(!ppo) pfs->Delete();//转换不成功,使用pfs来删除对象。 else{ ppo->Save("c:\\myfile"); ppo->Delete();} //转换成功,使用ppo来删除对象。 } }

  3. 实现了引用计数后, 每个对象维护一个引用计数,当接口指针被复制的时候,计数增加;接口指针被销毁的时候,计数减少。 1。当接口指针被复制时,调用DuplicatePointer 即AddRef 2。当接口指针不再有用时调用DestroyPointer 即Release void f(void){ IFastString *pfs=0; IPersistentObject *ppo=0; pfs=CreateFastString("asdf"); if(pfs) { ppo=(IPersistentObject*) pfs->Dynamic_Cast("IPersistentObject"); if(ppo) { ppo->Save("c:\\myfile"); ppo->DestroyPointer();} //每个指针各自负责自己。 pfs->DestroyPointer();//每个指针各自负责自己。 } } 或者使用正式的表达方式:

  4. void GetLottaPointers(LPUNKNOWN pUnk) { HRESULT hr; LPPERSIST pPersist; LPDISPATCH pDispatch; LPDATAOBJECT pDataObject; hr = pUnk->QueryInterface(IID_IPersist, (LPVOID *)&pPersist); //引用计数加1 if(SUCCEEDED(hr)) { hr = pUnk->QueryInterface(IID_IDispatch, (LPVOID *) &pDispatch); //引用计数加1 if(SUCCEEDED(hr)) { hr = pUnk->QueryInterface(IID_IDataObject, (LPVOID *) &pDataObject); //引用计数加1 if(SUCCEEDED(hr)) { DoIt(pPersist, pDispatch, pDataObject); //同时使用三个指针 pDataObject->Release(); } //释放引用 pDispatch->Release(); } //释放引用 pPersist->Release(); } //释放引用 pUnk指向的对象的引用计数在退出此函数时保持不变。对象检测到引用为零时,会删除自己。 } 比较没有引用计数的情况,有了很大的进步。但是仍然要小心。

  5. CComPtr • template <class T> class CComPtr { public: typedef T _PtrClass; T* p;//数据成员 CComPtr() {p=NULL;} //缺省构造函数把内部接口指针初始化为NULL CComPtr(T* lp) { if ((p = lp) != NULL) p->AddRef(); }//构造函数 引用计数加1 CComPtr(const CComPtr<T>& lp) { if ((p = lp.p) != NULL) p->AddRef(); } //构造函数 引用计数加1 /* CComPtr对象可以通过一个适当类型的接口指针进行初始化,可以使用IFoo或者另一个CComPtr<IFoo> 对象来初始化。另外两个构造函数分别完成这个任务。如果指定的值不是NULL,则构造函数调用内部接口指针的AddRef 比如CComPtr<IUnkown> punk ; CComPtr<ISomeInterface> psome */ T* operator=(T* lp){return (T*)AtlComPtrAssign( (IUnknown**)&p, lp);} T* operator=(const CComPtr<T>& lp) { return (T*)AtlComPtrAssign((IUnknown**)&p, lp.p); }

  6. /* 重载了=操作符进行赋值操作,在赋值操作时: 1当前接口指针非NULL时,Release 2源接口指针非NULL时,AddRef 3把源接口指针保存为当前接口指针*/ 这里使用了AtlComPtrAssign函数。 IUnkown* AtlComPtrAssign(IUnknown**pp,IUnkown *lp) { if(*pp) (*pp)->Release(); // 当前指针要指向新的对象,原对象引用要减1 if(lp) lp->AddRef(); //源指针要被复制,引用计数要加1。 *pp=lp; return lp;}...... }

  7. 在智能指针的支持下,对象的引用计数处理起来更加方便:在智能指针的支持下,对象的引用计数处理起来更加方便: void GetLottaPointers(LPUNKNOWN pUnk){ HRESULT hr; CComPtr<IUnknown> persist; CComPtr<IUnknown> dispatch; CComPtr<IUnknown> dataobject; hr = pUnk->QueryInterface(IID_IPersist, (LPVOID *)&persist); //引用计数不变 if(FAILED(hr)) return; hr = pUnk->QueryInterface(IID_IDispatch, (LPVOID *) &dispatch); //引用计数不变 if(FAILED(hr)) return; hr = pUnk->QueryInterface(IID_IDataObject, (LPVOID *) &dataobject); //引用计数不变 if(FAILED(hr)) return; DoIt(pPersist, pDispatch, pDataObject); } 实际上,在整个处理过程中,对象的应用没有任何变化!作为局部变量的persist等指针除了函数体以后也自动被清除。真正做到想用就用,不用就丢。无需任何处理。但是COM对象的引用计数仍然在被正确无误地维持着!

  8. 事实上ATL提供了另一个智能指针CComQIPtr对QueryInterface函数进行了封装,使得客户使用COM接口更为方便:事实上ATL提供了另一个智能指针CComQIPtr对QueryInterface函数进行了封装,使得客户使用COM接口更为方便: • template <class T, const IID* piid = &__uuidof(T)> class CComQIPtr { public: T* p; typedef T _PtrClass; CComQIPtr() {p=NULL;} //默认构造函数 CComQIPtr(T* lp) { //类型未定的构造函数 if ((p = lp) != NULL) p->AddRef(); //引用计数加1 } CComQIPtr(const CComQIPtr<T,piid>& lp) {//拷贝构造函数 if ((p = lp.p) != NULL) p->AddRef(); //引用计数加1 } CComQIPtr(IUnknown* lp) { //指向IUnkown接口的指针 p=NULL; if (lp != NULL) lp->QueryInterface(*piid, (void **)&p); //由IUnkown接口进行查询。 } ......

  9. T* operator=(T* lp){ return (T*)AtlComPtrAssign((IUnknown**)&p, lp); } T* operator=(const CComQIPtr<T,piid>& lp) { return (T*)AtlComPtrAssign((IUnknown**)&p, lp.p); } T* operator=(IUnknown* lp) { return (T*)AtlComQIPtrAssign((IUnknown**)&p, lp, *piid); } ...... } CComQIPtr的第二个模板参数是一个指向接口IID的指针。它有四个构造函数,并且对“=”进行了三次重载。对IUnkown指针的赋值会调用 AtlComQIPtrAssign函数: _(IUnknown*) AtlComQIPtrAssign(IUnknown** pp, IUnknown* lp, REFIID riid) { IUnknown* pTemp = *pp; *pp = NULL; if (lp != NULL) lp->QueryInterface(riid, (void**)pp); if (pTemp) pTemp->Release(); //注意这里要释放以前的引用! return *pp; }

  10. 在CComQIPtr的支持下,客户的使用变得极为简单:在CComQIPtr的支持下,客户的使用变得极为简单: • void GetLottaPointers(IUnkown* pUnk){ CComQIPtr<IPersist, &IID_IPersist> persist; CComQIPtr<IDispatch, &IID_IDispatch> dispatch; CComPtr<IDataObject, &IID_IDataObject> dataobject; dispatch = pUnk; persist = pUnk; dataobject = pUnk; DoIt(persist, dispatch, dataobject); } 或者, • void GetLottaPointers(IUnkown* pUnk){ CComQIPtr<IPersist, &IID_IPersist> persist(pUnk); CComQIPtr<IDispatch, &IID_IDispatch> dispatch (pUnk); CComPtr<IDataObject, &IID_IDataObject> dataobject (pUnk); DoIt(persist, dispatch, dataobject); } 所有繁琐的操作都不见了。

  11. 2 使用ATL开发COM组件 • C语言是产生汇编语言代码的框架。 • ATL是产生C++/COM代码的框架。 使用ATL COM AppWizard进行COM组件开发。 1。 使用ATL COM AppWizard创建一个工程。 可以选择Exe Dll 或 service的方式。这里选择dll, 2。当选择Dll时,还可以选择是否 Allow Merging Of Proxy/Stub Code 当Dll在远程时,客户有两种方式加载它,加载到客户进程或通过代理存根。如果通过代理存根,组件实现者还要提供代理存根dll。(如何开发?)选中以上选项可以把代理存根dll和组件对象连在一起。 另外还可以选择支持MFC和MTS。这里我们都不选。 进行完以上操作后,IDE生成了一个CComModule _Module 的实例。它类似于MFC应用程序的CWinApp类。同时 IDE会给此DLL自动生成 DllGetClassObject, DllCanUnloadNow, DllRegisterServer, 和DllUnregisterServer.引出函数。(它们的作用?) 3。Insert-》New Atl Object-》Simple Object-》myobj 当在short name中输入myobj时,IDE为我们选定了: CoCLass的名字为myobj,它实现的接口为Imyobj,ProgID为 Myatl.myobj。同时IDE为我们生成了类Cmyobj以实现COM对象,用两个文件myobj.h和myobj.cpp 来保存代码。 Attribute页为我们选定了Apartment线程模型,双接口,以及聚合的支持。 以上选项都可以人为地修改。

  12. class ATL_NO_VTABLE Cmyobj : public CComObjectRootEx<CComSingleThreadModel>, public CComCoClass<Cmyobj, &CLSID_myobj>, public IDispatchImpl<Imyobj, &IID_Imyobj, &LIBID_MYATLLib> {...... 模板类CComObjectRootEx实现了IUnkown的成员方法。QueryInterface、AddRef、和Release。模板类型参数指定了COM对象所运行的线程模型。根据不同的线程模型, CComObjectRootEx模板类对IUnkown成员的实现方法不一样。 CComCoClass模板类为对象定义了缺省的类厂,并且支持聚合模型。 ATL_NO_VTABLE是一个宏 #define ATL_NO_VTABLE __declspec(novtable) 。 ATL使用继承的方式来实现接口。COM组件的内存中包含为每个类所实现的每个接口的虚表。这是采用继承方式(即虚表的方式)必须付出的代价。ATL使用__declspec(novtable)指示编译器不要在构造函数中初始化对象的虚表。(因此不要在它的构造函数中调用虚函数) 问题是,如果没有虚表,那么,客户得到接口的指针如何能调用到它的具体实现呢?答案是客户实际上没有实例化类Cmyobj ,而是实例化了类Cmyobj的一个派生类CComObject<Cmyobj>.CComObject也是一个模板类,它使用Cmyobje作为它的模板参数。而CComObject<Cmyobj>的虚表是被正常创建了的。

  13. ATL支持双重接口的开发。(在MFC开发双重接口很困难) IDispatchImpl模板类用于此目的。其模板参数包括双重接口,接口的IID,接口的类型库的GUID。然后在接口映射表中加上双重接口和IDispatch接口的入口: BEGIN_COM_MAP(Cmyobj) COM_INTERFACE_ENTRY(Imyobj) COM_INTERFACE_ENTRY(IDispatch) END_COM_MAP() 这些宏以类似于MFC的方式建立了一张COM接口映射表。表项中记录了每一个接口IID所对应的虚表和this指针的偏移。这张映射表使得接口指针能够找到它所指的对象的this指针,从而访问其数据成员。 在接口上添加方法: 使用classview添加接口方法。 IDE会为我们在IDL文件上添加方法,在cpp文件上添加C++代码。 [id(1), helpstring("method mymethod")] HRESULT mymethod(); STDMETHODIMP Cmyobj::mymethod() { // TODO: Add your implementation code here return S_OK; }

  14. 也可以添加属性。属性有设置属性和读取属性两种,也可以同时支持。本质上与方法没有两样。一般我们要在实现类上声明一个成员变量来代表这个属性。属性的读取和设置操作都以这个成员变量为目的。也可以添加属性。属性有设置属性和读取属性两种,也可以同时支持。本质上与方法没有两样。一般我们要在实现类上声明一个成员变量来代表这个属性。属性的读取和设置操作都以这个成员变量为目的。 [propget, id(2), helpstring("property myprop")] HRESULT myprop([out, retval] short *pVal); [propput, id(2), helpstring("property myprop")] HRESULT myprop([in] short newVal); STDMETHODIMP Cmyobj::get_myprop(short *pVal) { return S_OK; } STDMETHODIMP Cmyobj::put_myprop(short newVal) { return S_OK; }

  15. 添加新的接口 • 1。在idl文件中添加: [ object, uuid(37F7EFD9-F74C-4c72-BF51-8811E4A9A22D), helpstring("Imyobj2 Interface"), pointer_default(unique) ] interface Imyobj2 : IUnknown {}; coclass myobj { [default] interface Imyobj; interface Imyobj2; }; //uuid 是用guidgen产生的。 2.在类的实现文件中加上: class ATL_NO_VTABLE Cmyobj : public CComObjectRootEx<CComSingleThreadModel>, public CComCoClass<Cmyobj, &CLSID_myobj>, public IDispatchImpl<Imyobj, &IID_Imyobj, &LIBID_MYATLLib>, public Imyobj2 3.在接口映射表中加上入口: BEGIN_COM_MAP(Cmyobj) COM_INTERFACE_ENTRY(Imyobj) COM_INTERFACE_ENTRY(IDispatch) COM_INTERFACE_ENTRY(Imyobj2) END_COM_MAP() 在IDE中编译后,midl会更新myatl.h myatl_i.cpp等文件。同时classview等也会通步更新。

  16. 如果要添加新的双接口: • 1。在idl文件中添加: [ object,dual uuid(F1CC2E60-BF57-4084-9278-03B4C3489649), helpstring("Imyobj2 Interface"), pointer_default(unique) ] interface Imyobj2 : IDiaptch {}; coclass myobj { [default] interface Imyobj; interface Imyobj2; }; //uuid 是用guidgen产生的。 2.在类的实现文件中加上: class ATL_NO_VTABLE Cmyobj : public CComObjectRootEx<CComSingleThreadModel>, public CComCoClass<Cmyobj, &CLSID_myobj>, public IDispatchImpl<Imyobj, &IID_Imyobj, &LIBID_MYATLLib>, public IDispatchImpl<Imyobj2, &IID_Imyobj2, &LIBID_MYATLLib>, 3.在接口映射表中加上入口: BEGIN_COM_MAP(Cmyobj) COM_INTERFACE_ENTRY(Imyobj) COM_INTERFACE_ENTRY(IDispatch) COM_INTERFACE_ENTRY2(IDispatch,Imyobj2) END_COM_MAP() //当客户请求Idispatch接口时,此宏使得客户获得正确的版本。

More Related