Chinaunix首页 | 论坛 | 博客
  • 博客访问: 149708
  • 博文数量: 21
  • 博客积分: 1400
  • 博客等级: 上尉
  • 技术积分: 355
  • 用 户 组: 普通用户
  • 注册时间: 2007-12-12 17:44
文章分类

全部博文(21)

文章存档

2011年(1)

2010年(5)

2009年(8)

2008年(7)

我的朋友

分类:

2009-01-19 15:54:21

o. 一个IClassFactory对象
 现在我们需要看一下如何得到IExample对象,最后我们要写一些代码来实现它。微软已经设计一个标准的方法。这需要在我们的DLL文件里包含第二个COM对象--IClassFactory,它有一些特殊的函数,且定义了自己的GUID,通过IID_IClassFactory引用。
 IClassFactory的VTable有5个特殊函数,QueryInterface,AddRef,Release,CreateInstance和LockServer。注意到了IClassFactory有它自己的QueryInterface,AddRef和Release函数,就像我们的IExample对象一样。(这里为了避免名字冲突,我们将IClassFactory的函数加上class前缀,classQueryInterface)。
 真正重要的函数是CreateInstance。当程序无论何时需要我们创建一个IExample对象,并初始化返回它的时候,就必须调用我们的IClassFactory的CreateInstance函数。实际上,如果需要IExample多个对象,则可以调用CreateInstance多次。这就是程序如何得到我们的IExample对象,但是你可能会问“程序如何得到IClassFactory对象呢?”,我们稍候会解释,现在我们简单的实现IClassFactory的五个函数,生成其虚函数表。
 生成需函数表很容易,不同于IExample的虚函数表,我们没有必要定义我们自己的IClassFactory的虚函数表。微软已经为我们定义了IClassFactoryVtbl结构。我们需要作的就是声明我们的VTable并且填充我们自己实现的5个函数。让我们创建一个静态的VTable,并命名为IClassFactory_Vtbl:
 static const IClassFactoryVtbl IClassFactory_Vtbl = {classQueryInterface,
   classAddRef,
   classRelease,
   classCreateInstance,
   classLockServer);
 同样的,创建一个IClassFactory对象也很容易,因为微软已经定义了此结构。我们仅仅需要一个对象,让我们声明一个静态的IClassFactory对象并命名为MyClassFactoryObj,并用上面的VTable来初始化:
 static IClassFactory MyIClassFactoryObj = {&IClassFactory_Vtbl};
 现在我们需要实现这5个函数。我们的classAddRef和ClassRelease是没有意义的,因为我们没有真正分配IClassFactory的空间(我们只是简单的声明为静态对象),也不需要释放空间。所以classAddRef总是返回1,表明总有一个IClassFactory对象。classRelease也一样,我们不需要任何引用计数。
 ULONG STDMETHODCALLTYPE classAddRef(IClassFactory *this)
 {
  return (1);
 }
 
 ULONG STDMETHODCALLTYPE classRlease(IClassFactory *this)
 {
  return (1);
 }
 现在来看看QueryInterface函数,它需要检查传入的GUID是否是一个IUnknown的GUID或者一个IClassFactory的GUID。我们作同样的事情如同在IExample的QueryInterface函数中一样。
 HRESULT STDMETHODCALLTYPE classQueryInterface(IClassFactory *this,
                          REFIID factoryGuid, void **ppv)
 {
    // Check if the GUID matches an IClassFactory or IUnknown GUID.
    if (!IsEqualIID(factoryGuid, &IID_IUnknown) &&
        !IsEqualIID(factoryGuid, &IID_IClassFactory))
    {
       // It doesn't. Clear his handle, and return E_NOINTERFACE.
       *ppv = 0;
       return(E_NOINTERFACE);
    }
 
    // It's a match!
 
    // First, we fill in his handle with the same object pointer he passed us.
    // That's our IClassFactory (MyIClassFactoryObj) he obtained from us.
    *ppv = this;
 
    // Call our IClassFactory's AddRef, passing the IClassFactory.
    this->lpVtbl->AddRef(this);
 
    // Let him know he indeed has an IClassFactory.
    return(NOERROR);
 }
 IClassFactory的LockServer函数现在只是一个桩函数:
 HRESULT STDMETHODCALLTYPE classLockServer(IClassFactory *this, BOOL flock)
 {
    return(NOERROR);
 }
 CreateInstance被定义成:
 HRESULT STDMETHODCALLTYPE classCreateInstance(IClassFactory *,
              IUnknown *, REFIID, void **);
 通常的,CreateInstance的第一个参数是指向IClassFactory对象(MyIClassFactoryObj)的指针。
 当我们使用聚合的时候才需要使用第二个参数。这里我们不涉及它。如果它非空,那么说明希望我们支持聚合,这里我们不这么做,而直接返回错误。
 第三个参数是IExample的VTable的GUID(如果希望我们分配,初始化,返回一个IExample对象)
 第四个参数是我们返回我们创建的IExample对象句柄。
 让我们来看一下CreateInstance函数(被命名为classCreateInstance):
 HRESULT STDMETHODCALLTYPE classCreateInstance(IClassFactory *this,
         IUnknown *punkOuter, REFIID vTableGuid, void **ppv)
 {
    HRESULT          hr;
    struct IExample *thisobj;
 
    // Assume an error by clearing caller's handle.
    *ppv = 0;
 
    // We don't support aggregation in IExample.
    if (punkOuter)
       hr = CLASS_E_NOAGGREGATION;
    else
    {
       // Create our IExample object, and initialize it.
       if (!(thisobj = GlobalAlloc(GMEM_FIXED,
                       sizeof(struct IExample))))
          hr = E_OUTOFMEMORY;
       else
       {
          // Store IExample's VTable. We declared it
          // as a static variable IExample_Vtbl.
          thisobj->lpVtbl = &IExample_Vtbl;
 
          // Increment reference count so we
          // can call Release() below and it will
          // deallocate only if there
          // is an error with QueryInterface().
          thisobj->count = 1;
 
          // Fill in the caller's handle
          // with a pointer to the IExample we just
          // allocated above. We'll let IExample's
          // QueryInterface do that, because
          // it also checks the GUID the caller
          // passed, and also increments the
          // reference count (to 2) if all goes well.
          hr = IExample_Vtbl.QueryInterface(thisobj, vTableGuid, ppv);
 
          // Decrement reference count.
          // NOTE: If there was an error in QueryInterface()
          // then Release() will be decrementing
          // the count back to 0 and will free the
          // IExample for us. One error that may
          // occur is that the caller is asking for
          // some sort of object that we don't
          // support (ie, it's a GUID we don't recognize).
          IExample_Vtbl.Release(thisobj);
       }
    }
 
    return(hr);
 }
o. 打包成dll
 为了让其他程序可以得到我们的IClassFactory(并且调用CreateInstance函数得到IExample对象),我们会将上面的代码打包成一个DLL文件。这篇文档不想说如何创建一个DLL文件,如果你不熟悉的话,那么你应该首先阅读一下相关方面的资料。
 我们已经完成了IExample和IClassFactory的所有代码,我们现在需要做的就是变成DLL代码。
 还有许多要做,微软已经规定了我们必须在DLL中添加DllGetClassObject函数,以及它的参数,该怎么做,及返回值。一个程序通过调用DllGetClassObject来得到我们IClassFactory对象的指针(实际上,后面我们会看到,程序会调用一个名为CoGetClassObject的OLE函数,那个函数会调用我们的DllGetClassObject.)所以,程序如何得到IClassFactory对象--通过调用DllGetClassObject。我们的DllGetClassObject函数必须完成这个工作,它这样子被定义:
 HRESULT PASCAL DllGetClassObject(REFCLSID objGuid,
     REFIID factoryGuid, void **factoryHandle);
 第一个参数是IExample的GUID(而不是VTable的GUID)。我们需要检查此来确保调用者明确的调用DLL的DllGetClassObject函数。注意到每个COM的DLL都有一个DllGetClassObject函数,所以我们需要GUID来从其他COM的DLL中区分出DllGetClassObject函数。
 第二个参数是IClassFactory的GUID。
 第三个参数是调用者希望我们返回一个IClassFactory对象指针(如果传递了正确的IExample的GUID)
 HRESULT PASCAL DllGetClassObject(REFCLSID objGuid,
        REFIID factoryGuid, void **factoryHandle)
 {
    HRESULT  hr;
 
    // Check that the caller is passing
    // our IExample GUID. That's the COM
    // object our DLL implements.
    if (IsEqualCLSID(objGuid, &CLSID_IExample))
    {
       // Fill in the caller's handle
       // with a pointer to our IClassFactory object.
       // We'll let our IClassFactory's
       // QueryInterface do that, because it also
       // checks the IClassFactory GUID and does other book-keeping.
       hr = classQueryInterface(&MyIClassFactoryObj,
                           factoryGuid, factoryHandle);
    }
    else
    {
       // We don't understand this GUID.
       // It's obviously not for our DLL.
       // Let the caller know this by
       // clearing his handle and returning
       // CLASS_E_CLASSNOTAVAILABLE.
       *factoryHandle = 0;
       hr = CLASS_E_CLASSNOTAVAILABLE;
    }
 
    return(hr);
 }
 大多数的工作已经完成了,还有一件事情。程序不会加载我们的DLL,而是操作系统代替程序加载当程序调用CoGetDllClassObject(CoGetClassObject加载我们的DLL,通过LoadLibrary函数加载,GetProcAddress函数得到DllGetClassObject函数的地址,然后调用它)。不幸的是,微软并没有设计出程序如何告知操作系统已经使用完了我们DLL并且可以卸载它(FreeLibrary)的方法。所以我们必须告诉操作系统让它知道什么时候可以安全的卸载DLL。我们必须提供一个名为DllCanUnloadNow函数,当它返回S_OK的时候就可以安全的卸载我们的DLL,否则返回S_FALSE。
 我们如何知道什么时候是可以安全卸载的呢?
 我们还需要引用计数器。每当我们分配一个对象的空间,我们就要增加引用计数,每次程序调用Release函数,我们释放对象,减少引用计数。只有当计数为0时,我们才告诉OS我们的DLL可以被安全卸载,因为我们可以确信没有其他程序在使用我们的任何对象。所以我们声明一个静态的OutstandingObjects来维护这个引用计数(当DLL被加载的时候,这个值为0)。
 我们在哪里增加此引用计数呢?在我们的IClassFactory的CreateInstance函数中,当我们调用GlobalAlloc分配空间并所有事情都顺利地完成后。我们在那个函数中添加一行,在Release的后面:
 static DWORD OutstandingObjects = 0;
 
 HRESULT STDMETHODCALLTYPE classCreateInstance(IClassFactory *this,
         IUnknown *punkOuter, REFIID vTableGuid, void **ppv)
 {
    ...
 
          IExampleVtbl.Release(thisobj);
 
          // Increment our count of outstanding objects if all
          // went well.
          if (!hr) InterlockedIncrement(&OutstandingObjects);
       }
    }
 
    return(hr);
 }
 哪里最合适减少引用计数呢?在我们的IExample的Release函数中,在GlobalFree函数后面:
 InterlockedDecrement(&OutstandingObjects);
 微软规定应该提供一种方法允许程序在内存中锁住我们的DLL。为了那个目的,我们调用IClassFactory的LockServer函数,如果我们希望增加一个锁,则传递参数1,传递0则意味着减少DLL的锁。我们需要第二个静态的DWORD引用计数变量LockCount(DLL加载时初始化为0):
 static DWORD LockCount = 0;
 
 HRESULT STDMETHODCALLTYPE
         classLockServer(IClassFactory *this, BOOL flock)
 {
    if (flock) InterlockedIncrement(&LockCount);
    else InterlockedDecrement(&LockCount);
 
    return(NOERROR);
 }
 现在我们可以实现DllCanUnloadNow函数了:
 HRESULT PASCAL DllCanUnloadNow(void)
 {
    // If someone has retrieved pointers to any of our objects, and
    // not yet Release()'ed them, then we return S_FALSE to indicate
    // not to unload this DLL. Also, if someone has us locked, return
    // S_FALSE
    return((OutstandingObjects | LockCount) ? S_FALSE : S_OK);
 }
o 我们的C++/C包含文件
 早先我们提过,为了让程序使用我们的IExample动态链接库文件,我们需要提供IExample和IExample的VTable的GUID。我们把GUID宏放在.H文件里,我们可以给别人使用,也可以给自己的DLL使用。在这个.H文件里,我们还需要包含IExampleVtbl和IExample的结构,这样程序员可以通过IExample来调用我们的函数。
 到现在为止我们是这么定义的:
 typedef HRESULT STDMETHODCALLTYPE QueryInterfacePtr(IExample *, REFIID, void **);
 typedef ULONG STDMETHODCALLTYPE AddRefPtr(IExample *);
 typedef ULONG STDMETHODCALLTYPE ReleasePtr(IExample *);
 typedef HRESULT STDMETHODCALLTYPE SetStringPtr(IExample *, char *);
 typedef HRESULT STDMETHODCALLTYPE GetStringPtr(IExample *, char *, long);
 
 typedef struct {
    QueryInterfacePtr  *QueryInterface;
    AddRefPtr          *AddRef;
    ReleasePtr         *Release;
    SetStringPtr       *SetString;
    GetStringPtr       *GetString;
 } IExampleVtbl;
 
 typedef struct {
    IExampleVtbl *lpVtbl;
    DWORD         count;
    char          buffer[80];
 } IExample;
 这样子定义有个问题,我们不希望让其他程序知道count和buffer成员,我们希望隐藏它们。外部程序应该不能直接访问我们结构的数据成员。它们只需要知道lpVtbl来调用我们的函数即可,我们希望我们的IExample是这么定义的:
 typedef struct {
    IExampleVtbl *lpVtbl;
 } IExample;
 另外,尽管typedef可以使函数定义变得简单,但是如果函数过多的话,会显得有些冗长还可能容易犯错。
 最后的问题是上面的定义是C定义,它使得C++程序要使用我们的COM对象变得不容易。毕竟尽管我们用C实现的IExample,但是IExample是一个C++类。对C++程序来说定义一个C++类比C结构要容易得多。
 代替上述的定义,微软提供了一个宏使得我们可以定义我们的VTable和对象对C和C++都支持,而且隐藏了其他数据成员。要使用此宏,首先必须要定义对象的符号INTERFACE,在此之前我们必须undef此符号避免编译警告。接着,我们使用DECLARE_INTERFACE_宏,在宏内,我们列举了IExample的函数,它看起来应该是这样:
 #undef  INTERFACE
 #define INTERFACE   IExample
 DECLARE_INTERFACE_ (INTERFACE, IUnknown)
 {
    STDMETHOD  (QueryInterface)  (THIS_ REFIID, void **) PURE;
    STDMETHOD_ (ULONG, AddRef)   (THIS) PURE;
    STDMETHOD_ (ULONG, Release)  (THIS) PURE;
    STDMETHOD  (SetString)       (THIS_ char *) PURE;
    STDMETHOD  (GetString)       (THIS_ char *, DWORD) PURE;
 };
 看上去可能有些奇怪。
 当定义一个函数,STDMETHOD表示函数返回HRESULT。我们的QueryInterface,SetString和GetString都返回HRESULT。AddRef和Release不是,他们返回ULONG,这就是为什么我们用STDMETHOD_(有个下划线)。接着我们把函数名放在括号中,如果函数不返回HRESULT,我们需要在函数名前指出其返回类型,然后逗号。函数名之后是()内的是参数。THIS表示我们对象的指针(IExample),如果函数只有此一个参数,就只需要在()内加入THIS,如同AddRef和Release函数一样。其他函数有额外的参数,我们必须使用THIS_(有个下划线)。我们列出余下的参数,注意到了,在THIS_和其他参数之间没有逗号,但是其他参数之间有逗号。最后我们加上PURE和分号。
 虽然宏很奇怪,但是它是定义COM对象的方法为了让C编译器和C++编译器都可以识别。
 “但是IExample结构的定义去哪里了?”。这个宏很奇怪,它可以让C编译器自动产生IExample结构的定义,它仅仅包含一个lpVtbl成员。通过这种方法定义我们的VTable,我们自动的得到合适的IExample结构。
 把GUID添加到此.H文件中,我们创建IExample.h文件。
 但是你知道IExample还有2个数据成员。所以我们打算定义一个IExample变种在我们的DLL文件中,我们定义其为MyRealIExample,它是IExample的真正定义:
 typedef struct {
    IExampleVtbl *lpVtbl;
    DWORD         count;
    char          buffer[80];
 } MyRealIExample;
 在IClassFactory的CreateInstance函数中,我们分配MyRealIExample结构:
 if (!(thisobj = GlobalAlloc(GMEM_FIXED, sizeof(struct MyRealIExample))))
 程序不需要知道我们实际给它的对象还多了额外的数据,毕竟,这些结构都含有相同的lpVtbl指针。现在我们的DLL函数可以通过将IExample转换为MyRealIExample类型而得到额外的数据成员。
o 定义文件(DEF)
 我们需要DEF文件来导出DllCanUnloadNow和DllGetClassObject函数。微软编译器希望他们被定义为PRIVATE:
 LIBRARY IExample
 EXPORTS
 DllCanUnloadNow   PRIVATE
 DllGetClassObject PRIVATE
o 安装DLL,注册对象
 我们已经完成了生成IExample.dll的所有事情,我们可以开始编译IExample.dll了。
 这不是我们的最后步骤,在其他程序使用我们的IExample之前,我们需要作两件事情:
 1. 安装DLL文件使得程序运行时可以被找到
 2. 注册我们的DLL为COM对象
 我们需要创建一个安装程序使得可以将IExample.dll拷贝至一个合适的地方。例如,我们会在Program Files下创建一个IExample目录,然后拷贝DLL到此目录下(当然了,我们的安装程序应该做版本检查,如果有我们DLL的早期版本已经被安装,我们不会覆盖)
 接着我们要注册此DLL,这包括创建几个注册表健值。
 首先我们在HKEY_LOCAL_MACHINE\Software\Classes\CLSID下创建一个键值,名称必须为IExample的GUID,且必须为text格式。
 在GUID键值下,创建字符串InprocServer32,默认值为DLL的安装全路径。添加ThreadingModel字符串,如果我们没有必要约束程序必须以单线程的方式调用我们的DLL函数,则我们可以指定其值为“both”。在我们的IExample函数中没有使用全局数据结构,所以我们是线程安全的。
 在运行安装程序之后,IExample.dll被注册为COM组件,其他程序可以使用它了。
阅读(1594) | 评论(1) | 转发(0) |
给主人留下些什么吧!~~

chinaunix网友2009-07-31 11:59:07

非常不错,希望楼主能够继续更新。原文的3-8部分我在codeproject上也看到了,本人英文不好读着太费劲。