AutoCAD 3DMAX C语言 Pro/E UG JAVA编程 PHP编程 Maya动画 Matlab应用 Android
Photoshop Word Excel flash VB编程 VC编程 Coreldraw SolidWorks A Designer Unity3D
 首页 > VC编程

COM编程入门(二)

51自学网 2015-08-30 http://www.wanshiok.com

 

COM服务器注册

COM服务器必须在Windows注册表中正确注册以后才能正常工作。如果你看一下注册表中的HKEY_CLASSES_ROOT/CLSID键,就会发现大把大把子键,它们就是在这个计算机上注册的COM服务器。当某个COM服务器注册后(通常是用DllRegisterServer()进行注册),就会以标准的注册表格式在CLSID键下创建一个键,它名字为服务器的GUID。下面是一个这样的例子:

{067DF822-EAB6-11cf-B56E-00A0244D5087}


大括弧和连字符是必不可少的,字母大小写均可。
这个键的默认值是人可值别的组件对象类名,使用VC所带的OLE/COM对象浏览器可以察看到它们。
在GUID键的子键中还可以存储其它信息。需要创建什么子键依赖于COM服务器的类型以及COM服务器的使用方法。对于本文例子中这个简单的进程内服务器,我们值需要一个子键:InProcServer32。
InProcServer32键包含两个串:这两个串的缺省值是服务器DLL的全路径和线程模型值(ThreadingModel)。线程模型超出了本文所涉及的范围,我们先接受这个概念,这里我们指的是单线程服务器,用的模式为Apartment(即单线程公寓)。

创建COM对象——类工厂

回首看一看客户端的COM,它是如何以自己独立于语言的方式创建和销毁COM对象。客户端调用CoCreateInstance()创建新的COM对象。现在我们来看看它在服务器端是如何工作的。
你每次实现组件对象类的时候,都要写一个旁类负责创建第一个组件对象类的实例。这个旁类就叫这个组件对象类的类工厂(class factory),其唯一目的是创建COM对象。之所以要一个类工厂,是因为语言无关的缘故。COM本身并不创建对象,因为它不是独立于语言的也不是独立于实现的。
当某个客户端想要创建一个COM对象时,COM库就从COM服务器请求类工厂。然后类工厂创建COM对象并将它返回客户端。它们的通讯机制由函数DllGetClassObject()来提供。
术语 “类工厂”和“类对象”实际上是一回事。没有那个单词能精确描述类工厂的作用和义,但正是这个工厂创建了COM对象,而不是COM类所为。将“类工厂”理解成“对象工厂”可能会更有助于理解(实际上MFC就是这样理解的——它的类工厂实现就叫做COleObjectFactory)。但“类工厂”是正式术语,所以本文也这样用。
当COM库调用DllGetClassObject()时,它传递客户端请求的CLSID。服务器负责为所请求的CLSID创建者各类工厂并将它返回。类工厂本身就是一个组件对象类,并且实现IClassFactory接口。如果DllGetClassObject()调用成功,它返回一个IClassFactory指针给COM库,然后COM库用IClassFactory接口方法创建客户端所请求的COM对象实例。
一下是IClassFactory接口:

struct IClassFactory : public IUnknown
{
  HRESULT CreateInstance( IUnknown* pUnkOuter, REFIID riid, void** ppvObject );
  HRESULT LockServer( BOOL fLock );
};

其中,CreateInstance()是创建COM对象的方法。LockServer()在必要时让COM库增加或减少服务器的引用计数。

一个定制接口的例子
这个工程是一个能运行的DLL服务器例子,对象由类工厂创建,此DLL服务器在 CSimpleMsgBoxImpl组件对象类中实现了一个接口:ISimpleMsgBox。

接口定义

我们的新接口是ISimpleMsgBox。所有的接口多必须从IUnknown派生。这个接口只有一个方法:DoSimpleMsgBox()。注意它返回标准类型HRESULT。所有的方法都应该返回HRESULT类型,并且所有返回到调用者的其它数据都应该通过指针参数操作。
struct ISimpleMsgBox : public IUnknown
{
  // IUnknown 方法
  ULONG AddRef();
  ULONG Release();
  HRESULT QueryInterface( REFIID riid, void** ppv );

  // ISimpleMsgBox方法
  HRESULT DoSimpleMsgBox( HWND hwndParent, BSTR bsMessageText );
};

struct __declspec(uuid("{7D51904D-1645-4a8c-BDE0-0F4A44FC38C4}")) ISimpleMsgBox;

有__declspec的一行将一个GUID赋值给ISimpleMsgBox,并且以后可以用__uuidof操作符来获取GUID。这两个东西都是微软的C++的扩展。
DoSimpleMsgBox()的第二个参数是BSTR类型。意思是二进制串——即定长序列位的COM表示。BSTRs主要用于Visual Basic 和 Windows Scripting Host之类的脚本客户端。

接下来这个接口由CSimpleMsgBoxImpl C++类来实现。其定义如下:
class CSimpleMsgBoxImpl : public ISimpleMsgBox 
{
public:
  CSimpleMsgBoxImpl();
  virtual ~CSimpleMsgBoxImpl();

  // IUnknown 方法
  ULONG AddRef();
  ULONG Release();
  HRESULT QueryInterface( REFIID riid, void** ppv );

  // ISimpleMsgBox 方法
  HRESULT DoSimpleMsgBox( HWND hwndParent, BSTR bsMessageText );

protected:
  ULONG m_uRefCount;
};

class __declspec(uuid("{7D51904E-1645-4a8c-BDE0-0F4A44FC38C4}")) CSimpleMsgBoxImpl;

当某一客户端想要创建一个SimpleMsgBox COM对象时,它应该用下面这样的代码:

ISimpleMsgBox* pIMsgBox;
HRESULT hr;

// 组件对象类的CLSID
hr = CoCreateInstance ( __uuidof(CSimpleMsgBoxImpl),              NULL,             // 非聚合
    CLSCTX_INPROC_SERVER, // 进程内服务器
   __uuidof(ISimpleMsgBox), // 所请求接口的IID
  (void**) &pIMsgBox );     // 返回的接口指针的地址

类工厂实现

我们的类工厂SimpleMsgBox是在一个叫做CSimpleMsgBoxClassFactory的C++类中实现的:
class CSimpleMsgBoxClassFactory : public IClassFactory
{
public:
  CSimpleMsgBoxClassFactory();
  virtual ~CSimpleMsgBoxClassFactory();

  // IUnknown方法
  ULONG AddRef();
  ULONG Release();
  HRESULT QueryInterface( REFIID riid, void** ppv );

  // IClassFactory方法
  HRESULT CreateInstance( IUnknown* pUnkOuter, REFIID riid, void** ppv );
  HRESULT LockServer( BOOL fLock );

protected:
  ULONG m_uRefCount;
};

构造函数、析构函数和IUnknown方法都和前面例子中的一样,不同的只有IClassFactory的方法,LockServer(),看起来相当更简单:

HRESULT CSimpleMsgBoxClassFactory::LockServer ( BOOL fLock )
{
  fLock ? g_uDllLockCount++ : g_uDllLockCount--;
  return S_OK;
}

CreateInstance()是重点。我们说过这个方法负责创建新的CSimpleMsgBoxImpl对象。让我们进一步探讨一下它的原型和参数:
HRESULT CSimpleMsgBoxClassFactory::CreateInstance ( IUnknown* pUnkOuter,
                          REFIID  riid,
                          void**  ppv );

第一个参数pUnkOuter只用于聚合的新对象,指向“外部的”COM对象,也就是说,这个“外部”对象将包含此新对象。对象的聚合超出了本文的讨论范围,本文的例子对象也不支持聚合。
riid 和ppv 与在QueryInterface()中的用法一样——它们是客户端所请求的接口IID和存储接口指针的指针缓冲。
下面是CreateInstance()的实现。它从参数的有效性检查和参数的初始化开始。
HRESULT CSimpleMsgBoxClassFactory::CreateInstance ( IUnknown* pUnkOuter,
                          REFIID  riid,
                          void**  ppv )
{
  // 因为不支持聚合,所以这个参数pUnkOuter必须为NULL.
  if ( NULL != pUnkOuter )
    return CLASS_E_NOAGGREGATION;

  //检查指针ppv是不是void*类型
  if ( IsBadWritePtr ( ppv, sizeof(void*) ))
    return E_POINTER;

  *ppv = NULL;

检查完参数的有效性后,就可以创建一个新的对象了。
CSimpleMsgBoxImpl* pMsgbox;

  // 创建一个新的COM对象
  pMsgbox = new CSimpleMsgBoxImpl;

  if ( NULL == pMsgbox )
    return E_OUTOFMEMORY;

 
 
说明
:本教程来源互联网或网友上传或出版商,仅为学习研究或媒体推广,wanshiok.com不保证资料的完整性。

上一篇:MAP原理及其在MFC中的实现  下一篇:Visual C++开发工具与调试技巧整理