COM编程入门
好文章,来自【福优学苑@音视频+流媒体】
1. COM组件介绍
COM组件是什么?COM是一种规范,而不是实现。但是当使用C++来实现时,COM组件就是一个C++类,而COM接口就是继承至IUnknown的纯虚类,COM组件就是实现相应COM接口的C++类。
COM规范规定,任何组件或接口都必须从IUnknown接口中继承而来。IUnknown定义了3个重要函数,分别是QueryInterface、AddRef和Release。其中,QueryInterface负责组件对象上的接口查询,AddRef用于增加引用计数,Release用于减少引用计数。引用计数是COM中的一个非常重要的概念,它很好地解决了组件对象地生命周期问题,即COM组件何时被销毁,以及谁来销毁地问题。
除了IUnknown接口外,还有另外一个重要地接口,即IClassFactory。COM组件实际上是一个C++类,对于组件地外部使用者来说,这个类名一般不可知,那么如何创建这个类地实例?由谁来创建?COM规范规定,每个组件都必须实现一个与之对应的类工厂(Class Factory)。类工厂也是一个COM组件,它实现了IClassFactory接口。在IClassFactory的接口函数CreateInstance中,才能使用new操作生成一个COM组件类对象实例。
COM组件有3种类型:进程内组件(CLSCTX_INPROC_SERVER)、本地进程组件(CLSCTX_LOCAL_SERVER)和远程组件(CLSCTX_REMOTE_SERVER)。
2. COM组件开发
实现一个COM组件,需要完成以下工作:
COM组件接口
COM组件实现类
COM组件创建工厂
COM组件注册与取消注册
本文以一个例子作为说明,COM组件提供了一个SayHello的接口函数,将“Hello COM”打印输出。
2.1 创建COM组件接口
COM组件接口是一个继承IUnknown的纯虚函数:
// IComTest.h #pragma once /* COM 组件接口 */ #include <Unknwn.h> // interface id,COM组件接口唯一标识 static const WCHAR* IID_IComTestStr = L"{213D1B15-9BBA-414A-BAB6-CA5B6CEF0006}"; static const GUID IID_IComTest = { 0x213D1B15, 0x9BBA, 0x414A, { 0xBA, 0xB6, 0xCA, 0x5B, 0x6C, 0xEF, 0x00, 0x06 } }; class IComTest :public IUnknown { public: virtual int _stdcall SayHello() = 0; };
2.2 创建COM组件实现类
COM组件类是一个实现了相应COM组件接口的C++类,注意:一个COM组件可以同时实现多个COM接口。
// ComTest.h #pragma once #include "IComTest.h" // class id,COM组件唯一标识 static const WCHAR* CLSID_CComTestStr = L"{4046FA83-57F0-4475-9381-8818BFC50DDF}"; static const GUID CLSID_CComTest = { 0x4046FA83, 0x57F0, 0x4475, { 0x93, 0x81, 0x88, 0x18, 0xBF, 0xC5, 0x0D, 0xDF } }; class CComTest :public IComTest { public: CComTest(); ~CComTest(); // 实现IUnknown接口 // 查找接口 // riid : 输入参数,接口id // ppvObject : 输出参数,返回相应的接口 virtual HRESULT _stdcall QueryInterface(const IID &riid, void ** ppvObject); // 增加引用计数 virtual ULONG _stdcall AddRef(); // 减少引用计数 virtual ULONG _stdcall Release(); virtual int _stdcall SayHello(); protected: // 引用计数 ULONG m_RefCount; // 全局创建对象个数 static ULONG g_ObjNum; };
// ComTest.cpp #include "ComTest.h" #include <stdio.h> ULONG CComTest::g_ObjNum = 0; CComTest::CComTest() { m_RefCount = 0; g_ObjNum++; } CComTest::~CComTest() { g_ObjNum--; } HRESULT _stdcall CComTest::QueryInterface(const IID &riid, void **ppvObject) { // 通过接口id判断返回的接口类型 if (IID_IUnknown == riid){ *ppvObject = this; ((IUnknown*)(*ppvObject))->AddRef(); } else if (IID_IComTest == riid){ *ppvObject = (IComTest*)this; ((IComTest*)(*ppvObject))->AddRef(); } else{ *ppvObject = NULL; return E_NOINTERFACE; } return S_OK; } ULONG _stdcall CComTest::AddRef() { m_RefCount++; return m_RefCount; } ULONG _stdcall CComTest::Release() { m_RefCount--; if (0 == m_RefCount){ delete this; return 0; } return m_RefCount; } int _stdcall CComTest::SayHello() { printf("hello COM\r\n"); return 666; }
2.3 COM组件创建工厂
对于组件地外部使用者来说,这个COM组件的类名一般不可知,那么如何创建这个类地实例?由谁来创建?COM规范规定,每个组件都必须实现一个与之对应的类工厂(Class Factory)。类工厂也是一个COM组件,它实现了IClassFactory接口。在IClassFactory的接口函数CreateInstance中,才能使用new操作生成一个COM组件类对象实例。
// ComTestFactory.h #pragma once #include <Unknwn.h> class CComTestFactory : public IClassFactory { public: CComTestFactory(); ~CComTestFactory(); // 实现IUnknown接口 virtual HRESULT _stdcall QueryInterface(const IID& riid, void** ppvObject); virtual ULONG _stdcall AddRef(); virtual ULONG _stdcall Release(); // 实现IClassFactory接口 virtual HRESULT _stdcall CreateInstance(IUnknown *pUnkOuter, const IID& riid, void **ppvObject); virtual HRESULT _stdcall LockServer(BOOL fLock); protected: ULONG m_RefCount; static ULONG g_ObjNum; }; // ComTestFactory.cpp #include "ComTestFactory.h" #include "ComTest.h" ULONG CComTestFactory::g_ObjNum = 0; CComTestFactory::CComTestFactory() { m_RefCount = 0; g_ObjNum++; } CComTestFactory::~CComTestFactory() { g_ObjNum--; } // 查询指定接口 HRESULT _stdcall CComTestFactory::QueryInterface(const IID &riid, void **ppvObject) { if (IID_IUnknown == riid){ *ppvObject = (IUnknown*)this; ((IUnknown*)(*ppvObject))->AddRef(); } else if (IID_IClassFactory == riid){ *ppvObject = (IClassFactory*)this; ((IClassFactory*)(*ppvObject))->AddRef(); } else{ *ppvObject = NULL; return E_NOINTERFACE; } return S_OK; } ULONG _stdcall CComTestFactory::AddRef() { m_RefCount++; return m_RefCount; } ULONG _stdcall CComTestFactory::Release() { m_RefCount--; if (0 == m_RefCount){ delete this; return 0; } return m_RefCount; } // 创建COM对象,并返回指定接口 HRESULT _stdcall CComTestFactory::CreateInstance(IUnknown *pUnkOuter, const IID &riid, void **ppvObject) { if (NULL != pUnkOuter){ return CLASS_E_NOAGGREGATION; } HRESULT hr = E_OUTOFMEMORY; //ComClass::Init(); CComTest* pObj = new CComTest(); if (NULL == pObj){ return hr; } hr = pObj->QueryInterface(riid, ppvObject); if (S_OK != hr){ delete pObj; } return hr; } HRESULT _stdcall CComTestFactory::LockServer(BOOL fLock) { return NOERROR; }
2.4 COM组件的注册
COM组件需要使用regsvr32工具注册到系统才能被调用,然而COM组件是如何被regsvr32注册的?一个典型的自注册COM组件需要提供4个必需的导出函数:
DllGetClassObject:用于获得类工厂指针
DllCanUnloadNow:系统空闲时会调用这个函数,以确定是否可以卸载COM组件
DllRegisterServer:将COM组件注册到注册表中
DllUnregisterServer:删除注册表中的COM组件的注册信息
DLL还有一个可选的入口函数DllMain,可用于初始化和释放全局变量
DllMain:DLL的入口函数,在LoadLibrary和FreeLibrary时都会调用
// ComTestExport.h
#include <windows.h>
extern "C" HRESULT _stdcall DllRegisterServer();
extern "C" HRESULT _stdcall DllUnregisterServer();
extern "C" HRESULT _stdcall DllCanUnloadNow();
extern "C" HRESULT _stdcall DllGetClassObject(__in REFCLSID rclsid, __in REFIID riid, LPVOID FAR* ppv);
3. COM组件使用
COM组件的使用包括:
如何创建COM组件
如何得到组件对象上的接口以及如何调用接口方法
如何管理组件对象(需熟悉COM的引用计数机制)
下面的代码是最一般的步骤:
CoInitialize(NULL);// COM库初始化 // ... IUnknow *pUnk = NULL; IObject *pObj = NULL; // 创建组件对象,CLSID_XXX为COM组件类的GUID(class id),返回默认IID_IUnknown接口 HRESULT hr = CoCreateInstance(CLSID_XXX,NULL,CLSCTX_INPROC_SERVER,NULL,IID_IUnknown,(void **)&pUnk); if(S_OK == hr) { // 获取接口,IID_XXX为组件接口的GUID(interface id) hr = pUnk->QueryInterface(IID_XXX,(void **)&pObj); if(S_OK == hr) { // 调用接口方法 pObj->DoXXX(); } // 释放组件对象 pUnk->Release(); } //... // 释放COM库 CoUninitialize(); 下面我们编写一个客户端,调用之前写的COM组件服务: #include "IComTest.h" #include "ComTest.h" #include <stdio.h> int main() { // 初始化COM库 CoInitialize(NULL); IComTest *pComTest = NULL; HRESULT hResult; // 创建进程内COM组件,返回指定接口 hResult = CoCreateInstance(CLSID_CComTest, NULL, CLSCTX_INPROC_SERVER, IID_IComTest, (void **)&pComTest); if (S_OK == hResult) { // 调用接口方法 printf("%d\r\n", pComTest->SayHello()); // 释放组件 pComTest->Release(); } // 释放COM库 CoUninitialize(); return 0; } 上面的例子和一般步骤不一致,少了QueryInterface,是因为默认返回的就是指定的接口,下面按一般步骤再实现一次: #include "IComTest.h" #include "ComTest.h" #include <stdio.h> int main() { // 初始化COM库 CoInitialize(NULL); IUnknown *pUnk = NULL; IComTest *pComTest = NULL; HRESULT hResult; // 创建COM组件,返回默认接口 hResult = CoCreateInstance(CLSID_CComTest, NULL, CLSCTX_INPROC_SERVER, IID_IUnknown, (void **)&pUnk); if (S_OK == hResult) { // 查询接口 hResult = pUnk->QueryInterface(IID_IComTest, (void **)&pComTest); if (S_OK == hResult) { // 调用接口方法 printf("%d\r\n", pComTest->SayHello()); } // 释放组件 pComTest->Release(); } // 释放COM库 CoUninitialize(); return 0; }
好文章,来自【福优学苑@音视频+流媒体】
***【在线视频教程】***