COM(组件对象模型)简单介绍
2008-09-03 03:22
239 查看
什么是COM?
简单地说,COM提供了一种在不同的应用程序和语言之间共享二进制代码的规范。COM定义了软件组件互相通讯的方式。它是一种二进制和网络标准,允许任意两个组件互相通讯,而不管它们是在什么计算机上运行(只要计算机是相连的),也不管计算机运行的是什么操作系统(只要该操作系统支持COM),也不管该组件是用什么语言编写的。COM还提供了位置透明性:当使用COM组件时,该组件是进程内Dll、本地exe还是位于其他机器上的组件,都无所谓。
为什么需要COM?
虽然可能有很多历史的原因,但是,我们仅从二进制代码共享的角度来看看,为什么要引入COM。
Windows可以使用DLLs在二进制级别共享代码,如kernel32.dll、user32.dll等。但是,这些dll都采用C接口,它们仅能被C语言或者能够理解C调用规范的语言使用。这从语言层次上限定了二进制代码的共享范围。
MFC有一种被称为MFC扩展DLL的共享机制。但是你只能在MFC应用程序中使用它们。
COM利用定义一个二进制规范来解决这些问题。使用COM编写的二进制模块必须匹配一些特定的数据结构。COM规范同样也定义COM对象在内存中应该如何被组织。这份二进制代码同样也不能依赖任何语言层面的特征。
COM对象的结构使用与C++虚函数相同的结构。所以很多COM使用C++编写,但是严格来说COM与编写该COM的语言是不想关的,因为最后的二进制代码可以被任何语言使用。
COM并不是Win32相关的,理论上,COM可以被移植到任何操作系统上,但是现实中COM依然只是Windows世界的东西。
COM基本术语
1.interface:一个接口可以看做是一组称为方法的函数。接口名以"I"打头,如"IShellLink",在C++中,接口即是一个只含有纯虚函数的抽象基类。
2.coclass:component object class的简称。coclass用来实现接口。一个COM对象是coclass在内存中的一个实例(instance)。注意COM"class"跟C++"class"不是一个东西,虽然COM class 的实现通常都是C++ class。
3.COM Server:含有一个或多个coclass的二进制模块(DLL或者EXE)。
4.Registration:向Windows注册,登记COM Server的位置和入口。Unregistration是一个相反的过程:从Windows中移除掉这个登记。
5.GUID:globally unique identifier。一个128位的数。COM使用语言无关的GUID作为标识。每一个interface和coclass都有一个GUID,因为GUID是全世界范围内唯一的。所以避免了名字冲突。有时候GUID也被称为UUID(universally unique identifier)。一个coclass的class ID称为CLSID;一个interface的interfaceID称为IID。
6.HRESULT:一个整数类型的值,COM使用它来返回成功或者错误码。可以使用Windows错误码查询来获取HRESULT所代表的字符含义。
基接口(base interface):IUnknown
每一个COM接口都要从IUnknown接口继承。IUnknown这个名字的含义是:如果你拥有一个指向IUnknown接口的COM对象指针,那个你无从知道它真正指向的对象是什么,因为COM中的每一个对象都实现了这个接口。
这个接口有三个方法:AddRef、Release和QueryInterface。其中QueryInterface用于在获取某个COM的一个接口指针后,查询该COM的其他接口指针。
使用COM
1.创建一个COM对象:C++中使用new操作符或者直接在栈上创建;创建COM对象则要调用COM library中的一个API(CoCreateInstance)。
2.删除COM对象:C++中,使用delete操作符或者在栈上的对象超出作用域以后会自动析构。但是在COM中,所有的对象拥有它们自己的引用计数(reference count)。调用者必须在使用完COM对象以后通知它(减少引用计数)。COM对象在引用计数到达0时,自动从内存中释放自己。
创建COM对象的函数原型如下:
参数说明:
当调用CoCreateInstance函数时,它查询注册表中的CLSID,读取COM server的位置,将其加载到内存中并创建函数所请求的coclass的一个实例。需要注意的是,在使用函数CoCreateInstance之前,确保你的程序已经初始化了COM library,并且需要在程序退出时卸载它,分别调用CoInitialize和CoUninitialize函数。
-----------------COM object create sample-------------------------------------------------------
HRESULT hr;
IShellLink* pISL;
hr = CoCreateInstance(CLSID_ShellLink,
NULL,
CLSCTX_INPROC_SERVER,
IID_IShellLink,
(void*)&pISL);
if (SUCCEEDED(hr))
{
pISL->Release();//just release.
}
else
{
}
---------------------------------------------------------------------------------------------------------
COM中的字符串操作
只要注意到COM中的字符串都是Unicode的,就应该没有问题的。习惯于使用Unicode并在与COM的交互中使用Unicode,就不会有任何问题。当使用ANSI时,需要自己处理Unicode与ANSI之间的转换。除了常规的转换方法外,ATL提供了几个宏处理这种转换:W2A()、OLE2A()、OLE2CA()等。
一个完整的使用COM的样例代码通常如下,代码从参考文章中copy的:
------------------------------------------------------------------------------------------------------------------------
------------------------------------------------------------------------------------------------------------------------
编写COM
下面看看一个最简单的COM server,进程内server(in-process server),需要如何编写。
要想能用COM lib来使用该COM server,首先必须做两件事情:
1.必须在注册表
前面说道每一个接口都必须实现IUnknown接口,因为IUnknown接口包含了COM对象的两个基本特征:引用计数和接口查询。在写coclass时,也必须实现该接口。下面是一个简单的coclass:
-----------------------------------------------------------------------------------------------------------------------------
-----------------------------------------------------------------------------------------------------------------------------
在每一次实现一个coclass时,同时也需要写一个被称为class factory配对的类,该类用于创建这个coclass的实例。需要这个工厂类的原因是为了语言无关:COM本身不创建COM对象,因为那样的话就不是语言和实现无关的了。
当COM lib调用DllGetClassObject时,它传递COM客户端请求的CLSID,server创建代表这个CLSID的类工厂。工厂类(class factory)本身也是一个coclass,它实现了IClassFactory。当DllGetClassObject成功返回时,它返回给COM lib一个指向IClassFactory的指针,然后COM lib使用IClassFactory的方法来创建COM对象。
IClassFactory像下面这样:
参考文章
http://www.codeproject.com/KB/COM/comintro.aspx#upd0722b
http://www.codeproject.com/KB/COM/comintro2.aspx
简单地说,COM提供了一种在不同的应用程序和语言之间共享二进制代码的规范。COM定义了软件组件互相通讯的方式。它是一种二进制和网络标准,允许任意两个组件互相通讯,而不管它们是在什么计算机上运行(只要计算机是相连的),也不管计算机运行的是什么操作系统(只要该操作系统支持COM),也不管该组件是用什么语言编写的。COM还提供了位置透明性:当使用COM组件时,该组件是进程内Dll、本地exe还是位于其他机器上的组件,都无所谓。
为什么需要COM?
虽然可能有很多历史的原因,但是,我们仅从二进制代码共享的角度来看看,为什么要引入COM。
Windows可以使用DLLs在二进制级别共享代码,如kernel32.dll、user32.dll等。但是,这些dll都采用C接口,它们仅能被C语言或者能够理解C调用规范的语言使用。这从语言层次上限定了二进制代码的共享范围。
MFC有一种被称为MFC扩展DLL的共享机制。但是你只能在MFC应用程序中使用它们。
COM利用定义一个二进制规范来解决这些问题。使用COM编写的二进制模块必须匹配一些特定的数据结构。COM规范同样也定义COM对象在内存中应该如何被组织。这份二进制代码同样也不能依赖任何语言层面的特征。
COM对象的结构使用与C++虚函数相同的结构。所以很多COM使用C++编写,但是严格来说COM与编写该COM的语言是不想关的,因为最后的二进制代码可以被任何语言使用。
COM并不是Win32相关的,理论上,COM可以被移植到任何操作系统上,但是现实中COM依然只是Windows世界的东西。
COM基本术语
1.interface:一个接口可以看做是一组称为方法的函数。接口名以"I"打头,如"IShellLink",在C++中,接口即是一个只含有纯虚函数的抽象基类。
2.coclass:component object class的简称。coclass用来实现接口。一个COM对象是coclass在内存中的一个实例(instance)。注意COM"class"跟C++"class"不是一个东西,虽然COM class 的实现通常都是C++ class。
3.COM Server:含有一个或多个coclass的二进制模块(DLL或者EXE)。
4.Registration:向Windows注册,登记COM Server的位置和入口。Unregistration是一个相反的过程:从Windows中移除掉这个登记。
5.GUID:globally unique identifier。一个128位的数。COM使用语言无关的GUID作为标识。每一个interface和coclass都有一个GUID,因为GUID是全世界范围内唯一的。所以避免了名字冲突。有时候GUID也被称为UUID(universally unique identifier)。一个coclass的class ID称为CLSID;一个interface的interfaceID称为IID。
6.HRESULT:一个整数类型的值,COM使用它来返回成功或者错误码。可以使用Windows错误码查询来获取HRESULT所代表的字符含义。
基接口(base interface):IUnknown
每一个COM接口都要从IUnknown接口继承。IUnknown这个名字的含义是:如果你拥有一个指向IUnknown接口的COM对象指针,那个你无从知道它真正指向的对象是什么,因为COM中的每一个对象都实现了这个接口。
这个接口有三个方法:AddRef、Release和QueryInterface。其中QueryInterface用于在获取某个COM的一个接口指针后,查询该COM的其他接口指针。
HRESULT IUnknown::QueryInterface ( REFIID iid, void** ppv );
使用COM
1.创建一个COM对象:C++中使用new操作符或者直接在栈上创建;创建COM对象则要调用COM library中的一个API(CoCreateInstance)。
2.删除COM对象:C++中,使用delete操作符或者在栈上的对象超出作用域以后会自动析构。但是在COM中,所有的对象拥有它们自己的引用计数(reference count)。调用者必须在使用完COM对象以后通知它(减少引用计数)。COM对象在引用计数到达0时,自动从内存中释放自己。
创建COM对象的函数原型如下:
HRESULT CoCreateInstance ( REFCLSID rclsid, LPUNKNOWN pUnkOuter, DWORD dwClsContext, REFIID riid, LPVOID* ppv );
参数说明:
rclsidcoclass的CLSID。例如,你可以传递
CLSID_ShellLink来创建一个用于创建快捷方式的COM对象。
pUnkOuter只在使用聚合COM对象的时候有用。如果不是用,则传递NULL。
dwClsContext指示我们希望使用什么类型的COM Server。这里我们使用最简单的一种Server,an in-process DLL,传递
CLSCTX_INPROC_SERVER。
riid我们要使用的接口的IID。例如,传递
IID_IShellLink来得到
IShellLinkinterface的指针。
ppv所请求的接口的指针地址。
当调用CoCreateInstance函数时,它查询注册表中的CLSID,读取COM server的位置,将其加载到内存中并创建函数所请求的coclass的一个实例。需要注意的是,在使用函数CoCreateInstance之前,确保你的程序已经初始化了COM library,并且需要在程序退出时卸载它,分别调用CoInitialize和CoUninitialize函数。
-----------------COM object create sample-------------------------------------------------------
HRESULT hr;
IShellLink* pISL;
hr = CoCreateInstance(CLSID_ShellLink,
NULL,
CLSCTX_INPROC_SERVER,
IID_IShellLink,
(void*)&pISL);
if (SUCCEEDED(hr))
{
pISL->Release();//just release.
}
else
{
}
---------------------------------------------------------------------------------------------------------
COM中的字符串操作
只要注意到COM中的字符串都是Unicode的,就应该没有问题的。习惯于使用Unicode并在与COM的交互中使用Unicode,就不会有任何问题。当使用ANSI时,需要自己处理Unicode与ANSI之间的转换。除了常规的转换方法外,ATL提供了几个宏处理这种转换:W2A()、OLE2A()、OLE2CA()等。
一个完整的使用COM的样例代码通常如下,代码从参考文章中copy的:
------------------------------------------------------------------------------------------------------------------------
CString sWallpaper = wszWallpaper; // Convert the wallpaper path to ANSI IShellLink* pISL; IPersistFile* pIPF; // 1. Initialize the COM library (make Windows load the DLLs). Normally you would // call this in your InitInstance() or other startup code. In MFC apps, use // AfxOleInit() instead. CoInitialize ( NULL ); 2. Create a COM object, using the Shell Link coclass provided by the shell. // The 4th parameter tells COM what interface we want (IShellLink). hr = CoCreateInstance ( CLSID_ShellLink, NULL, CLSCTX_INPROC_SERVER, IID_IShellLink, (void**) &pISL ); if ( SUCCEEDED(hr) ) { // 3. Set the path of the shortcut's target (the wallpaper file). hr = pISL->SetPath ( sWallpaper ); if ( SUCCEEDED(hr) ) { // 4. Get a second interface (IPersistFile) from the COM object. hr = pISL->QueryInterface ( IID_IPersistFile, (void**) &pIPF ); if ( SUCCEEDED(hr) ) { // 5. Call the Save() method to save the shortcut to a file. The // first parameter is a Unicode string. hr = pIPF->Save ( L"C://wallpaper.lnk", FALSE ); // 6a. Release the IPersistFile interface. pIPF->Release(); } } // 6b. Release the IShellLink interface. pISL->Release(); } // Printing of error messages omitted here. // 7. Uninit the COM library. In MFC apps, this is not necessary since MFC // does it for us. CoUninitialize();
------------------------------------------------------------------------------------------------------------------------
编写COM
下面看看一个最简单的COM server,进程内server(in-process server),需要如何编写。
要想能用COM lib来使用该COM server,首先必须做两件事情:
1.必须在注册表
HKEY_CLASSES_ROOT/CLSID下注册自己;
2.必须导出一个称为DllGetClassObject的函数。
这是这个COM的最低配置。需要在该注册表项下创建一个以该GUID为名的键,该键下有包含一系列的键值,如COM的位置以及它的线程模型等。
一般情况下,COM还需要导出下面三个函数:
DllCanUnloadNow(): COM lib调用此函数以判断该server是否可以被卸载;
DllRegisterServer(): 由安装工具调用,像RegSvr32一样让server能够注册自身。
DllUnregisterServer(): 由卸载工具调用,用于卸载COM。
前面说道每一个接口都必须实现IUnknown接口,因为IUnknown接口包含了COM对象的两个基本特征:引用计数和接口查询。在写coclass时,也必须实现该接口。下面是一个简单的coclass:
-----------------------------------------------------------------------------------------------------------------------------
class CUnknownImpl : public IUnknown { public: // Construction and destruction CUnknownImpl(); virtual ~CUnknownImpl(); // IUnknown methods ULONG AddRef(); ULONG Release)(); HRESULT QueryInterface( REFIID riid, void** ppv ); protected: UINT m_uRefCount; // object's reference count };
HRESULT CUnknownImpl::QueryInterface ( REFIID riid, void** ppv ) { HRESULT hrRet = S_OK; // Standard QI() initialization - set *ppv to NULL. *ppv = NULL; // If the client is requesting an interface we support, set *ppv. if ( IsEqualIID ( riid, IID_IUnknown )) { *ppv = (IUnknown*) this; } else { // We don't support the interface the client is asking for. hrRet = E_NOINTERFACE; } // If we're returning an interface pointer, AddRef() it. if ( S_OK == hrRet ) { ((IUnknown*) *ppv)->AddRef(); } return hrRet; }
-----------------------------------------------------------------------------------------------------------------------------
在每一次实现一个coclass时,同时也需要写一个被称为class factory配对的类,该类用于创建这个coclass的实例。需要这个工厂类的原因是为了语言无关:COM本身不创建COM对象,因为那样的话就不是语言和实现无关的了。
当COM lib调用DllGetClassObject时,它传递COM客户端请求的CLSID,server创建代表这个CLSID的类工厂。工厂类(class factory)本身也是一个coclass,它实现了IClassFactory。当DllGetClassObject成功返回时,它返回给COM lib一个指向IClassFactory的指针,然后COM lib使用IClassFactory的方法来创建COM对象。
IClassFactory像下面这样:
struct IClassFactory : public IUnknown { HRESULT CreateInstance( IUnknown* pUnkOuter, REFIID riid, void** ppvObject ); HRESULT LockServer( BOOL fLock ); };
在这之后我们需要用COM实现自己的功能,首先定义一个接口,接口里包含了我们自己的功能定义(记住,它必须实现IUnknown接口),例如:
struct ISimpleMsgBox : public IUnknown { // IUnknown methods ULONG AddRef(); ULONG Release(); HRESULT QueryInterface( REFIID riid, void** ppv ); // ISimpleMsgBox methods HRESULT DoSimpleMsgBox( HWND hwndParent, BSTR bsMessageText ); }; struct __declspec(uuid("{7D51904D-1645-4a8c-BDE0-0F4A44FC38C4}")) ISimpleMsgBox;
注意到最后一行,__declspec表示将一个GUID分配给ISimpleMsgBox,然后我们可以使用__uuidof操作符来获取这个GUID,__declspec和__uuidof都是微软的C++扩展
(Microsoft C++ extensions)。
然后,你需要一个类来实现这个接口:
class CSimpleMsgBoxImpl : public ISimpleMsgBox { public: CSimpleMsgBoxImpl(); virtual ~CSimpleMsgBoxImpl(); // IUnknown methods ULONG AddRef(); ULONG Release(); HRESULT QueryInterface( REFIID riid, void** ppv ); // ISimpleMsgBox methods HRESULT DoSimpleMsgBox( HWND hwndParent, BSTR bsMessageText ); protected: ULONG m_uRefCount; }; class __declspec(uuid("{7D51904E-1645-4a8c-BDE0-0F4A44FC38C4}")) CSimpleMsgBoxImpl;
其他细节:与COM有关的宏
STDMETHOD():包含了virtual关键字和HRESULT返回类型以及__stdcall调用约定。STDMETHOD_()与之相同,但是允许指定非HRESULT的返回值。
PURE:与C++中的"=0"效果相同,将一个函数声明为纯虚函数。
STDMETHODIMP:对应于STDMETHOD()的实现,相应地,STDMETHODIMP_()与STDMETHOD_()对应。
STDAPI:包含了返回类型和调用约定,由于STDAPI的扩展方式,使用了此宏以后不能再使用[code]__declspec(dllexport)。可以使用".DEF"文件完成类似功能。
还有一些实现上的问题,就不多讲了,千言万语,都在代码里面:)。
本文完全基于下面两篇英文文章,作了一个简单的理解和翻译,代码也请到下面的链接地址里下载。
参考文章
http://www.codeproject.com/KB/COM/comintro.aspx#upd0722b
http://www.codeproject.com/KB/COM/comintro2.aspx
相关文章推荐
- COM(组件对象模型)简单介绍
- COM(组件对象模型)简单介绍
- [转-来自啊泰]简单介绍一下水晶报表的推与拉两种模式 [http://www.cnblogs.com/babyt/archive/2005/04/12/135849.html]
- ImageMagick简单介绍 http://www.mcuos.com/---wzc0066
- 使MS Word COM将Word转化成pdf的简单介绍
- 简单介绍一下Overstock.com中国官网
- 8,关于串行化的介绍及简单实现(原帖地址:http://www.vckbase.com/document/viewdoc/?id=918)
- Druid数据库连接池简单使用介绍
- Jni使用基础(一)之简单介绍说明
- JSR 303 - Bean Validation 简单介绍及用法
- 深入浅出JMS(二)--ActiveMQ简单介绍以及安装
- 四大常见XML类型的生成和解析方法详解(介绍+优缺点对比+示例+说明)==> 简单实用
- 深入浅出JMS(二)--ActiveMQ简单介绍以及安装
- Android中Intent简单介绍
- Rsync 使用简单介绍之Shell
- Elasticlunr.js 简单介绍
- Masonry框架的简单使用和介绍
- LinearLayout具体解释一:LinearLayout的简单介绍
- 资料:Duilib中各个类的简单介绍
- Android简单介绍