COM(组件对象模型)简单介绍

COM(组件对象模型)简单介绍 

什么是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的其他接口指针。

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 );

参数说明:

rclsid
coclass的CLSID。例如,你可以传递 CLSID_ShellLink 来创建一个用于创建快捷方式的COM对象。
pUnkOuter
只在使用聚合COM对象的时候有用。如果不是用,则传递NULL。
dwClsContext
指示我们希望使用什么类型的COM Server。这里我们使用最简单的一种Server,an in-process DLL,传递 CLSCTX_INPROC_SERVER。
riid
我们要使用的接口的IID。例如,传递 IID_IShellLink来得到 IShellLink interface的指针。
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的扩展方式,使用了此宏以后不能再使用__declspec(dllexport)。可以使用".DEF"文件完成类似功能。
    
    还有一些实现上的问题,就不多讲了,千言万语,都在代码里面:)。
    本文完全基于下面两篇英文文章,作了一个简单的理解和翻译,代码也请到下面的链接地址里下载。

    参考文章

    http://www.codeproject.com/KB/COM/comintro.aspx#upd0722b

    http://www.codeproject.com/KB/COM/comintro2.aspx

  •  

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
CruiseYoung提供的带有详细书签的电子书籍目录 http://blog.csdn.net/fksec/article/details/7888251 该资料是《COM技术内幕——微软组件对象模型》一书的随书源代码 COM技术内幕——微软组件对象模型 基本信息 原书名: Inside COM: Microsoft's Component Object Model with Cdrom 原出版社: Microsoft Press 作者: (美)Dale Rogerson 译者: 杨秀章 丛书名: 微软版权图书 出版社:清华大学出版社 ISBN:730203320X 上架时间:2001-10-11 出版日期:1999 年3月 页码:293 版次:1-1 所属分类:计算机 > 软件与程序设计 > COM/DCOM/ATL/COM+ 内容简介    微软公司的组件对象模型(COM)作为一种重要的工具已崭露头角,它是微软迈向分布式计算的基础。不论现在还是将来,它都是定制应用程序的一种强大的方法。并且它是OLE和ActiveX 的基础。COM帮助你理解未来的程序开发技术,而这本书帮助你理解COM。在本书中你将发现:构建优美的COM组件的清晰、简单、实用的规则;COM是如何易学易用,特虽是对那些熟练掌握C++ 的人;循序渐进地介绍COM设计;以代码形式给出的大量实例。    《COM技术内幕》适合于中、高级C++程序员;COM、ActiveX和OLE程序员;对组件设计感兴趣的研究人员;以及那些当COM移植到UNIX、MVS和其他环境时想要使用到COM的程序员。 编辑推荐    微软公司的组件对象模型(COM)作为一种重要的工具已崭露头角,它是微软迈向分布式计算的基础。不论现在还是将来,它都是定制应用程序的一种强大的方法。并且它是OLE和ActiveX 的基础。COM帮助你理解未来的程序开发技术,而这本书帮助你理解COM。在本书中你将发现:构建优美的COM组件的清晰、简单、实用的规则;COM是如何易学易用,特虽是对那些熟练掌握C++ 的人;循序渐进地介绍COM设计;以代码形式给出的大量实例。 目录 封面 -17 扉页 -16 版权 -15 译者前言 -14 目录 -13 引言 -6 第1章 组件 1 1.1 使用组件的优点 2 1.1.1 应用程序的定制 2 1.1.2 组件库 3 1.1.3 分布式组件 3 1.2 对组件的需求 4 1.2.1 动态链接 4 1.2.2 信息封装 5 1.3 COM 6 1.3.1 COM组件是…… 7 1.3.2 COM不是…… 7 1.3.3 COM库 8 1.3.4 COM方法 8 1.3.5 COM超越了用户的需要 8 1.4 本章小结 9 第2章 接口 11 2.1 接口的作用 11 2.1.1 可复用应用程序架构 12 2.1.2 COM接口的其他优点 13 2.2 COM接口的实现 13 2.2.1 编码约定 14 2.2.2 一个完整的例子 15 2.2.3 非接口通信 18 2.2.4 实现细节 18 2.3 接口理论:第二部分 20 2.3.1 接口的不变性 20 2.3.2 多态 20 2.4 接口的背后 21 2.4.1 虚拟函数表 21 2.4.2 vtbl指针及实例数据 23 2.4.3 多重实例 24 2.4.4 不同的类,相同的vtbl 24 2.5 本章小结 26 第3章 QueryInterface函数 27 3.1 接口查询 28 3.1.1 关于IUnknown 28 3.1.2 IUnknown指针的获取 29 3.1.3 关于QueryInterface 29 3.1.4 QueryInterface的使用 30 3.1.5 QueryInterface的实现 31 3.1.6 关于类型转换 32 3.1.7 一个完整的例子 35 3.2 关于QueryInterface的实现规则 40 3.2.1 同一IUnknown 40 3.3.2 客户可以获取曾经得到过的接口 41 3.2.3 可以再次获取已经拥有的接口 41 3.2.4 客户可以从任何接口返回到起始接口 42 3.2.5 若能够从某接口获取某特定接口,则从任意接口都将能够获取此接口 42 3.3 QueryInterface定义了组件 43 3.3.1 接口集 44 3.4 新版本组件的处理 44 3.4.1 何时需要建立一个新版本 46 3.4.2 不同版本接口的命名 46 3.4.3 隐含

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值