⭐ 欢迎来到虫虫下载站! | 📦 资源下载 📁 资源专辑 ℹ️ 关于我们
⭐ 虫虫下载站

📄 com(2).txt

📁 关于ie控制的编程各种方面的内容和源代码
💻 TXT
📖 第 1 页 / 共 2 页
字号:
COM编程入门 - 第二部分 深入COM服务器 
浏览: 44 次 
下载例子http://www.moreres.com/down/svc031202.zip 
演示程序http://www.moreres.com/down/svc031203.zip 
     本文为刚刚接触COM的程序员提供编程指南,解释COM服务器内幕以及如何用C++编写自己的接口。 
    继上一篇COM编程入门之后,本文将讨论有关COM服务器的内容,解释编写自己的COM接口和COM服务器所需要的步骤和知识,以及详细讨论当COM库对COM服务器进行调用时,COM服务器运行的内部机制。 
    如果你读过上一篇文章。应该很熟悉COM客户端是怎么会事了。本文将讨论COM的另一端——COM服务器。内容包括如何用C++编写一个简单的不涉及类库的COM服务器。深入到创建COM服务器的内部过程,毫无遮掩地研究那些库代码是充分理解COM服务器内部机制的最好方法。 
    本文假设你精通C++并掌握了上一篇文章所讨论的概念和术语。在这一部分将包括如下内容: 
走马观花看COM服务器——描述COM服务器的基本要求。 
服务器生命其管理——描述COM服务器如何控制加载时间。 
实现接口,从IUnknown开始——展示如何用C++类编写一个接口实现并描述IUnknown之方法的目的。 
深入CoCreateInstance()——探究CoCreateInstance()的调用机理。 
COM服务器的注册——描述完成服务器注册所需要的注册表入口。 
创建COM对象——类工厂——描述创建客户端要使用的COM对象的过程。 
一个定制接口的例子——例子代码示范了上述概念。 
一个使用服务器的客户端——举例说明一个简单的客户端应用程序,用它来测试COM服务器。 
其它内容——有关源代码和调试的注释。 
走马观花看COM服务器 
    本文我们将讨论最简单的一种COM服务器,进程内服务器(in-process)。“进程内”意思是服务器被加载到客户端程序的进程空间。进程内服务器都是DLLs,并且与客户端程序同在一台计算机上。 
进程内服务器在被COM库使用之前必须满足两个条件或标准: 
1、 必须正确在注册表的HKEY_CLASSES_ROOT\CLSID 键值下注册。 
2、 必须输出DllGetClassObject()函数。 
这是进程内服务器运行的最小需求。在注册表的HKEY_CLASSES_ROOT\CLSID 键值下必须创建一个键值,用服务器的GUID作为键名字,这个键值必须包含两个键值清单,一是服务器的位置,而是服务器的线程模型。 COM库对DllGetClassObject()函数进行调用是在CoCreateInstance() API中完成的。 
还有三个函数通常也要输出: 
o DllCanUnloadNow():由COM库调用来检查是否服务器被从内存中卸载。 
o DllRegisterServer():由类似RegSvr32的安装实用程序调用来注册服务器。 
o DllUnregisterServer():由卸载实用程序调用来删除由DllRegisterServer()创建的注册表入口。 
另外,只输出正确的函数是不够的——还必须遵循COM规范,这样COM库和客户端程序才能使用服务器。 
服务器生命其管理 
       DLL服务器的一个与众不同的方面是控制它们被加载的时间。“标准的”DLLs被动的并且是在应用程序使用它们时被随机加载/或卸载。从技术上讲,DLL服务器也是被动的,因为不管怎样它们毕尽还是DLL,但COM库提供了一种机制,它允许某个服务器命令COM卸载它。这是通过输出函数DllCanUnloadNow()实现的。这个函数的原型如下: 
         HRESULT DllCanUnloadNow(); 

    当客户应用程序调用COM API CoFreeUnusedLibraries()时,通常出于其空闲处理期间,COM库遍历这个客户端应用已加载所有的DLL服务器并通过调用它的DllCanUnloadNow()函数查询每一个服务器。另一方面,如果某个服务器确定它不再需要驻留内存,它可以返回S_OK让COM将它卸载。 
服务器通过简单的引用计数来确定它是否能被卸载。下面是DllCanUnloadNow()的实现: 
extern UINT g_uDllRefCount;  // 服务器的引用计数 
HRESULT DllCanUnloadNow() 
{ 
    return (g_uDllRefCount > 0) ? S_FALSE : S_OK; 
} 

如何处理引用计数将在下一节涉及到具体代码时讨论。 
实现接口,从IUnknown开始 
    有必要回想一下IUnknown派生的每一个接口。因为IUnknown包含了两个COM对象的基本特性——引用计数和接口查询。当你编写组件对象类时(coclass),还要写一个满足自己需要的IUnknown实现。以实现IUnknown接口的组件对象类为例——下面这个例子可能是你编写的最简单的一个组件对象类。我们将在一个叫做CUnknownImpl的C++类中实现IUnknown。下面是这个类的声明: 
class CUnknownImpl : public IUnknown 
{ 
public: 
    // 构造函数和析构器 
    CUnknownImpl(); 
    virtual ~CUnknownImpl(); 

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

protected: 
    UINT m_uRefCount;  // 对象的引用计数 
}; 

构造器和析构器 
构造器和析构器管理服务器的引用计数: 
CUnknownImpl::CUnknownImpl() 
{ 
    m_uRefCount = 0; 
    g_uDllRefCount++; 
} 

CUnknownImpl::~CUnknownImpl() 
{ 
    g_uDllRefCount--; 
} 

    当创建新的COM对象时,构造器被调用,它增加服务器的引用计数以保持这个服务器驻留内存。同时它还将对象的引用计数初始化为零。当这个COM对象被摧毁时,它减少服务器的引用计数。 
AddRef()和Release() 

这两个方法控制COM对象的生命期。AddRef()很简单: 
ULONG CUnknownImpl::AddRef() 
{ 
    return ++m_uRefCount; 
} 

AddRef()只增加对象的引用计数并返回更新的计数。 
Release()更简单: 
ULONG CUnknownImpl::Release() 
{ 
ULONG uRet = --m_uRefCount; 

    if ( 0 == m_uRefCount )  // 是否释放了最后的引用? 
        delete this; 

    return uRet; 
} 

    除了减少对象的引用计数外,如果没有另外的明确引用,Release()将摧毁对象。Release()也返回更新的引用计数。注意Release()的实现假设COM对象在堆中创建。如果你在全局粘上创建某个对象,当对象试图删除自己时就会出问题。 
    现在应该明白了为什么在客户端应用程序中正确调用AddRef()和 Release()是如此重要!如果在这了做得不对,你使用的对象会被很快摧毁,这样的话在整个服务器中内存会很快溢出导致应用程序下次存取服务器代码时崩溃。 
     如果你编写多线程应用,可能会想到使用++&替代InterlockedIncrement()和InterlockedDecrement()的线程安全问题。++&——用于单线程服务器很保险,因为即使客户端应用是多线程的并从不同的线程中进行方法调用,COM库都会按顺序进行服务器的方法调用。也就是说,一旦一个方法调用开始,所有其它试图调用方法的线程都将阻塞,直到第一个方法返回。COM库本身确保服务器一次不会被一个以上的线程闯入。 

QueryInterface() 

       QueryInterface()简称QI(),由客户端程序调用这个函数从COM对象请求不同的接口。我们在例子代码中因为只实现一个接口,QI()会很容易使用。QI()有两个参数:一个是所请求的接口IID,一个是指针的缓冲大小,如果查询成功,QI()将接口指针地址存储在这个缓冲指针中。 
HRESULT CUnknownImpl::QueryInterface ( REFIID riid, void** ppv ) 
{ 
HRESULT hrRet = S_OK; 

    // 标准QI()初始化 – 置 *ppv 为 NULL. 
    *ppv = NULL; 

    // 如果客户端请求提供的接口,给 *ppv.赋值 
    if ( IsEqualIID ( riid, IID_IUnknown )) 
        { 
        *ppv = (IUnknown*) this; 
        } 
    else 
        { 
        // 不提供客户端请求的接口 
        hrRet = E_NOINTERFACE; 
        } 

    // 如果返回一个接口指针。 调用AddRef()增加引用计数. 
    if ( S_OK == hrRet ) 
        { 
        ((IUnknown*) *ppv)->AddRef(); 
        } 

    return hrRet; 
} 
在QI()中做了三件不同的事情: 
1、初始化传入的指针为NULL[*ppv = NULL;]。 
2、检查riid,确定组件对象类(coclass)实现了客户端所请求接口. 
[if ( IsEqualIID ( riid, IID_IUnknown ))] 
3、如果确实实现勒索请求的接口,则增加COM对象的引用计数。 
[((IUnknown*) *ppv)->AddRef();] 

AddRef()调用很关键。 
    *ppv = (IUnknown*) this; 

    要创建新的COM对象引用,就必须调用这个函数通知COM对象这个新引用成立。在AddRef()调用中的强制转换IUnknown*看起来好像多余,但是在QI()中初始化的*ppv有可能不是IUnknown*类型,所以最好是养成习惯对之进行强行转换。。 
上面我们已经讨论了一些DLL服务器的内部细节,接下来让我们回头看一看当客户端调用CoCreateInstance()时是如何处理服务器的。 

深入CoCreateInstance() 

    在本文的第一部分中,我们见过CoCreateInstance()API,其作用是当客户端请求对象时,用它来创建对象。从客户端的立场看,它是一个黑盒子。只要用正确的参数调用它即可得到一个COM对象。它并没有什么魔法,只是在一个定义良好的过程中加载COM服务器,创建请求的COM对象并返回所要的指针。就这些。 

   下面让我们来浏览一下这个过程。这里要涉及到几个不太熟悉的术语,但不用着急,后面会对它们作详细讨论。 
1、客户端程序调用CoCreateInstance(),传递组件对象类的CLSID以及所要接口的IID。 
2、COM库在HKEY_CLASSES_ROOT\CLSID.键值下查找服务器的CLSID键值,这个键值包含服务器的注册信息。 
3、COM库读取服务器DLL的全路径并将DLL加载到客户端的进程空间。 
4、COM库调用在服务器中DllGetClassObject()函数为所请求的组件对象类请求类工厂。 
5、服务器创建一个类工厂并将它从DllGetClassObject()返回。 
6、COM库在类工厂中调用CreateInstance()方法创建客户端程序请求的COM对象。 
7、CreateInstance()返回一个接口指针到客户端程序。 

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

⌨️ 快捷键说明

复制代码 Ctrl + C
搜索代码 Ctrl + F
全屏模式 F11
切换主题 Ctrl + Shift + D
显示快捷键 ?
增大字号 Ctrl + =
减小字号 Ctrl + -