65.9K
CodeProject 正在变化。 阅读更多。
Home

从零开始的 COM - 第二部分

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.85/5 (41投票s)

2004年4月18日

14分钟阅读

viewsIcon

256479

downloadIcon

5998

关于 COM 库的文章。

引言

第一部分中,我们解释了有关 COM 技术的一些背景信息,并通过一个简单的例子展示了客户端如何通过接口使用组件的功能。在第二部分中,我将指导读者将组件的实现与其客户端分离,以便客户端不再与组件绑定,并能够通过类工厂创建组件。

第二部分 - 打破依赖

提供组件(分发)

软件组件服务器提供了一种更方便重用功能的方式,此外,当多个应用程序同时使用相同功能时,它们可以减少内存开销,因为尽管每个应用程序都会获得自身的数据副本,但它们可以共享代码。通过将组件放入 DLL 中,可以实现一种组件分发形式,DLL 成为组件的服务器,并包含组件所支持接口的实现。

构建组件的服务器(DLL)

在示例中,客户端和组件都在同一个文件中。现在应该将它们分开,客户端将是一个 .exe 文件,它将组件加载到其地址空间以便使用它,而组件将由 DLL 提供服务。客户端需要在获取接口指针之前将 DLL 加载到其进程中并创建组件。如果客户端链接到 DLL 中的 CreateInstance() 函数,则可以通过接口指针访问组件的所有其他函数。因此,解决方案就是从 DLL 导出 CreateInstance() 函数,以便客户端可以在运行时显式地链接到它。DLL 将使用 Microsoft 的命令行工具之一从命令提示符进行构建。

下图显示了用于创建 DLL 的服务器文件。

The Server files

步骤 1

创建一个源文件(Component.cpp),并将组件类的定义和实现放入其中。

第二步

通过在文件末尾添加以下代码段来导出 CreateInstance() 函数。

Client and Server files

步骤 3

链接器应被告知 CreateInstance 函数将被导出,这可以通过使用模块定义文件来完成。模块定义文件是一个扩展名为“def”的文件,其中包含有关导出、属性以及链接具有导出(或 DLL)的 .EXE 文件所需其他信息。在 .def 文件中,CreateInstance 函数的导出序号被选择为 1。下面的部分显示了此文件的内容。

;component.def 
; Component module-definition file
; LIBRARY Component.dll 
DESCRIPTION 'Components windows dynamik library' 
EXPORTS ; Explicit exports can go here 
CreateInstance @1 PRIVATE

步骤 4

接口标识符和接口定义对于客户端和组件都应该是已知的,并且可以将它们放入两个独立的文件中,这两个文件在客户端和组件之间共享。创建另一个源文件(GUID.cpp),其中可以包含接口 ID。

//
// GUID.cpp - Interface ID
//
#include "objbase.h"
extern "C" 
{

extern const IID IID_IComponent = 
{ 0x853b4626, 0x393a, 0x44df, //Data1,Data2,Data3
{ 0xb1, 0x3e, 0x64, 0xca, 0xbe, 0x53, 0x5d, 0xbf } };  //Data4

    // The extern is required to allocate memory for C++ constants.
}

步骤 5

创建一个头文件(interface.h),其中包含以下内容:

//
// Interface.h
//
interface IComponent : IUnknown
{
    virtual void __stdcall Print(const char* msg) = 0 ;
} ;


// Forward references for GUID
extern "C"
{
    extern const IID IID_IComponent ;

}

步骤 6

创建一个“make”文件,其中包含以下内容用于创建 DLL 的选项:

#Makefile 

################################################################################
# Compiler options:
# /c     compile without linking
# CL     cl.exe is a 32-bit tool that controls the Microsoft C 
#        and C++ compilers and linker. 
#        The compilers produce Common Object File Format
#        (COFF) object (.obj) files. 
#        The linker produces executable (.exe) files
#        or dynamic-link libraries (DLLs).
#
##################################
# Linker options:
#
# /DEF     Passes a module-definition (.def) file to the linker
# /DEBUG   Creates debugging information
# /DLL     Builds a DLL


CPP_FLAGS=/c /MTd /Zi /Od /D_DEBUG
EXE_LINK_FLAGS=/DEBUG
DLL_LINK_FLAGS=/DLL /DEBUG

LIBS=UUID.lib

#############################################
# Targets:
# CodeProject is just a pseudotarget 
#
CodeProject :  component

component : Component.dll 


#########################################
# Shared source files:
#

GUID.obj : GUID.cpp 
    Cl $(CPP_FLAGS) GUID.cpp

##########################################
# Component source files:
#

Component.obj : Component.cpp Interface.h
    Cl $(CPP_FLAGS) Component.cpp


########################################
# Link component:
#

Component.dll : Component.obj    GUID.obj Component.def 
    link $(DLL_LINK_FLAGS) Component.obj GUID.obj $(LIBS) /DEF:Component.def

步骤 7

使用 Microsoft Program Maintenance Utility (NMAKE.EXE) 从命令行构建 DLL。此程序是一个工具,可以根据描述文件中的命令构建项目。

  1. 打开命令窗口(单击“开始”,选择“运行”菜单项,然后在对话框中输入 cmd)。
  2. 在命令提示符下,更改到包含服务器文件的目录。
  3. 在命令提示符下,输入:nmake /f makefile

      NMAKE 工具将在同一文件夹中创建 DLL。

      The Output window

    构建客户端

    下图显示了用于构建客户端的文件。客户端将在 Visual C++ 开发环境中构建。

    The Client files

    步骤 1

    使用 AppWizard,创建一个简单的 Win32 控制台应用程序,并选择一个空项目。

    第二步

    创建一个新的源文件(Create.cpp),并创建一个接受 DLL 名称作为参数的函数,加载“DLL”,然后调用导出的 CreateInstance() 函数。函数的返回值将是 CreateInstance() 函数的返回值,这是一个 IUnknown 接口指针。为了显式链接到“DLL”,该函数调用 GetProcAddress 函数来获取导出函数的地址。GetProcAddress 函数接受两个参数。第一个参数是“DLL”模块的句柄,第二个参数是“DLL”的名称。通过调用 LoadLibrary 函数,可以获得模块句柄。

    // Create.cpp
     #include "iostream.h"
     #include "unknwn.h"//IUnknown definition file.
     #include "Create.h"
    
    typedef IUnknown* (*CREATEFUNCPTR)();
     //////////////////////////////////////////
     IUnknown* CallCreateInstance(char* dllname) 
    { 
     //-----------------------------------------------------------------//
     // Load dynamic link library into client's process.
     //Loadlibrary maps a DLL module and return a handle 
     //that can be used in GetProcAddress 
     //to get the address of a DLL function 
     //-----------------------------------------------------------------// 
        HMODULE hm = ::LoadLibrary(dllname);
        if (hm ==NULL)
            return NULL; 
        // Get  the address of CreateInstance function. 
        CREATEFUNCPTR Function = 
            (CREATEFUNCPTR)::GetProcAddress(hm, "CreateInstance"); 
        if (Function == NULL) 
            return NULL; 
        return Function();
     }

    步骤 3

    创建一个新的头文件(Create.h),其中包含以下内容:

    // Create.h
    IUnknown* CallCreateInstance(char* dllname) ;

    步骤 4

    创建一个新的头文件(interface.h),其中包含以下内容:

    //
    // Interface.h
    //
    interface IComponent : IUnknown
    {
        virtual void __stdcall Print(const char* msg) = 0 ;
    } ;
    
    
    // Forward references for GUID
    extern "C"
    {
        extern const IID IID_IComponent ;
    
    }

    步骤 5

    创建另一个源文件(GUID.cpp),其中可以包含接口 ID。

    // GUID.cpp - Interface ID
    #include "objbase.h"
    extern "C" 
    {
    extern const IID IID_IComponent = 
    { 0x853b4626, 0x393a, 0x44df, //Data1,Data2,Data3
    { 0xb1, 0x3e, 0x64, 0xca, 0xbe, 0x53, 0x5d, 0xbf } };  //Data4
    // The extern is required to allocate memory for C++ constants.
    }

    步骤 6

    创建一个源文件(Client.cpp)并实现 main 函数。调用第 2 步中创建的函数,以实例化组件并使用其方法。

    //--------//
    // Client
    //--------//
    int main()
    {
        HRESULT hr ;
        
        // Get the name of the component to use.
        char dllname[20];
        cout << "Enter the filename of component's server [component.dll]:";
        cin  >> dllname;
        ...
    
        // calling the CreateInstance function in the
        // DLL in order to create the component.
        TRACE("Getting an IUnknown interface pointer...") ;
        IUnknown* pIUnknown = CallCreateInstance(dllname) ;
       
       ...
       
        IComponent* pIComponent ;
        hr = pIUnknown->QueryInterface(IID_IComponent, (void**)&pIComponent);
        
        if (SUCCEEDED(hr))
        {
           ...
            pIComponent->Print("COM from scratch.") ; 
            //using the component's functionality
    
            pIComponent->Release() ;
           ...
        }
       ...
        
        return 0 ;
    }

    步骤 7

    将组件服务器(Component.dll)放在客户端的同一目录下。现在,客户端能够将其 DLL 加载到其地址空间,并通过 LoadLibraryGetProcAddress 函数获取 CreateInstance 函数的地址。构建并运行客户端程序。

    下面的屏幕截图显示了客户端应用程序,在加载 DLL 并调用组件的 Print 方法后。

    The output window

    结论:通过服务器分发 COM 组件,使客户端可以轻松地重用组件的功能。

    在不重新构建客户端的情况下扩展组件功能

    COM 组件的一个优点是可以在不重建的情况下轻松扩展应用程序的功能。只要接口不变,客户端应用程序就可以继续使用该组件,即使其功能已通过对方法的新更改进行了扩展。为了展示 COM 组件的这一优势,最好通过一个简单的例子来考察客户端应用程序的重建问题。在下面,一个 DLL 被链接到一个客户端应用程序,您可能会注意到,每当对 DLL 进行更改时(例如,通过向 DLL 中的类添加新成员变量并修改成员函数),如果未重新构建,客户端应用程序将无法运行。

    步骤 1:创建 DLL

    1. 使用 AppWizard,创建一个新的项目,类型为 Win32 动态链接库。

      AppWizard

    2. 在向导的第二步中,选择“一个简单的 DLL 项目”并单击“完成”。

      Step 1

    3. 创建一个新的头文件,并定义一个包含成员变量和可以从 DLL 导出的成员函数的类。
      //myclass.h
      
      class CMyclass
       {
       long m_cRef; 
       public:
       _declspec(dllexport) void Print(const char*msg);
      };
    4. 创建一个源文件(myclass.cpp)并实现成员函数。

      Implementation of Print method

    5. 构建 DLL。

    步骤 2:创建客户端并加载 DLL

    1. 创建一个新的空项目,类型为 Win32 控制台应用程序。
    2. 创建一个新的源文件以加载和测试 DLL(client.cpp)。
    3. 包含包含 DLL 中类定义的头文件。
      //client.cpp
      
      #include"iostream.h"
      #include"..\DLL\myclass.h"
      
      /////////////////////////////////
      void  main()
      { 
       CMyclass  classObj; 
       classObj.Print("COM from scratch.");
      }
    4. 将 DLL 项目中的 DLL.lib 文件添加到客户端项目中(项目 -> 添加到项目 -> 文件,然后选择库文件(.lib)作为文件类型)。

      Implementation of Print method

    5. 将 DLL 复制到客户端可执行文件的同一文件夹中。如果构建并运行客户端应用程序,屏幕上将显示“COM from scratch.”,并且 DLL 将没有问题地加载。

    步骤 3:查看重建问题

    1. 返回 CMyclass 类的实现,并添加一个新的成员变量。
      //myclass.h
      class CMyclass
      {
      long m_cRef;
      int m_i; // a new member variable
      public:
              _declspec(dllexport) void Print(const char* msg);
      };
    2. 修改 Print 成员函数的实现。

      Reimplementation of Print method

    3. 重新构建 DLL 并将其复制到客户端可执行文件的同一文件夹中。
    4. 在不重建的情况下执行新版本的 DLL 的客户端应用程序,如果您运行客户端应用程序,您将遇到问题,而重建客户端应用程序可以解决此问题。客户端应用程序的重建是一个大问题,因为它需要源代码。现在,如果您对第一部分示例中的组件类进行相同的更改,并重建 DLL 并将其与客户端应用程序(而不重建它)进行测试,您会注意到客户端运行正常,尽管组件类中添加了一个新的成员变量并且 Print 方法的实现已更改。由于 COM 组件中的方法调用是通过接口间接进行的,因此如果方法被修改,将不会有问题。

    结论:COM 在不重建的情况下扩展应用程序的功能。

    改进示例

    在示例中,尽管客户端和组件已分离,但客户端与组件的实现密切相关,并且应该知道 DLL 的名称,更改 DLL 的名称会影响客户端。一种改进方法是将组件从一个 DLL 移动到另一个 DLL 或其他目录。解决方案是将 CallCreateInstance 函数替换为名为 CoCreateInstance 的 COM 库函数。COM 运行时库是 Windows 操作系统的一个组成部分,它为客户端提供查找和实例化 COM 对象的方法。COM 类对象可以通过 CLSID(全局唯一标识符)来标识,这些标识符用于定位和创建对象的实例。一旦获得 CLSID,客户端应用程序就会将 CLSID 提交给 COM 运行时库来加载 COM 对象并检索接口指针。使用 CLSID 和注册表,CoCreateInstance 会定位指定的对象,创建该对象的实例,并返回指向该对象的接口指针。为了使用 CoCreateInstance 创建对象,必须将该对象注册到系统中。

    CoCreateInstance

    COM 库包含此函数。创建组件最简单的方法是使用 CoCreateInstance 函数。CoCreateInstance 在创建组件时使用类工厂。它接受一个 CLSID,创建相应组件的实例,并返回该组件实例的接口。CoCreateInstance 接受 4 个 in 参数和 1 个 out 参数(IUnknown*)。通过将 IID 传递给 CoCreateInstance,客户端在创建组件后无需调用 QueryInterface

    CoCreateInstance 的参数

    • 第一个参数是对象的 CLSID。
    • 第二个参数用于将对象聚合为另一个对象的一部分。
    • 第三个参数指定对象的执行上下文。
    • 第四个参数是要请求的接口的 IID(接口 ID)。
    • 最后一个参数是一个 out 参数,是指向所创建对象的接口指针。

    组件注册

    可以使用 CoCreateInstance 创建的对象也必须在系统中注册。注册将 CLSID 映射到对象所在的自动化组件文件(.dll.exe)。如果客户端希望在运行时获取 CLSID,则必须有一种方法可以动态地定位和加载可访问对象的 CLSID。此外,COM 库必须有一些系统范围内的方法可以将给定的 CLSID(无论客户端如何获取它)与实现该类的服务器代码相关联。换句话说,COM 库需要一些 CLSID 到服务器的持久化映射,它使用这些映射来实现其定位器服务。Microsoft Windows 上的 COM 实现使用 Windows 系统注册表作为此类信息的存储。在该注册表中,有一个名为“CLSID”的根键,服务器负责在该键下创建指向其模块的条目。通常,这些条目是在安装时由应用程序的设置代码创建的,但也可以根据需要动态创建。当服务器安装在 Windows 下时,安装程序将为主机服务器支持的每个类在 CLSID 下创建一个子键,使用 CLSID 的标准字符串表示形式作为键名。因此,CLSID 的主要条目是 CLSID 键下的子键,该子键是包含在花括号中的十六进制数字表示的 CLSID。我们还可能希望将 CLSID 与所谓的编程标识符或 ProgID 相关联,它实际上标识了同一个类。ProgID 是一个不带空格的文本字符串,可以代替 CLSID 字符串使用。标准的 ProgID 格式为 <Vendor>.<Component>.<Version>,例如 Codeproject.Cmpnt1.1。此格式相当独特,如果每个人都遵循它,通常不会发生冲突。还有一个“VersionIndependentProgID”,其格式与 ProgID 相同,但不包含版本号。ProgID 和 VersionIndependentProgID 都可以注册,并在根键下方以人类可读的名称作为值。VersionIndependentProgID 映射到 ProgID,ProgID 映射到 CLSID。要创建注册表条目,您可以编写代码,或者创建一个 REG 文件并简单地运行它以将其条目与注册表合并。下图显示了 Demo Application 中 Component1 的注册表条目。

    Component's CLSID

    类工厂

    CoCreateInstance 函数不直接创建 COM 组件。相反,它创建一个称为类工厂的组件,然后由类工厂创建所需的组件。类工厂是一个创建其他组件的组件。特定的类工厂仅创建对应于单个、特定 CLSID 的组件。客户端使用类工厂支持的接口来控制类工厂如何创建每个组件。用于创建组件的标准接口是 IClassFactory 接口。IClassFactory 与其他 COM 接口一样,派生自 IUnknown 接口并具有两个方法:

    • CreateInstance,它创建一个指定 CLSID 的未初始化对象。
    • LockServer,它将对象的服务器锁定在内存中,从而可以更快地创建新对象。

    下面定义了一个类工厂,用于创建示例中的 COM 组件。

    ///////////////////////////////////////////////////////////
    //
    // Class factory
    //
    class CFactory : public IClassFactory
    {
    public:
        // IUnknown
        virtual HRESULT __stdcall QueryInterface(const IID& iid,void** ppv) ;
        virtual ULONG   __stdcall AddRef() ;
        virtual ULONG   __stdcall Release() ;
    
        // IClassFactory
        virtual HRESULT __stdcall CreateInstance(IUnknown* pUnkOuter,
                                                 const IID& iid,
                                                 void** ppv) ;
        virtual HRESULT __stdcall LockServer(BOOL bLock) ; 
    
        // Constructor
        CFactory() : m_cRef(1) {}
       // Destructor
        ~CFactory() {}
    
    private:
        long m_cRef ;
    } ;
    
    
    //
    // Class factory IUnknown implementation
    //////////////////////////////////////////////////////////////////////
    HRESULT __stdcall CFactory::QueryInterface(const IID& iid,LPVOID* ppv)
    {
        if ((iid == IID_IUnknown) || (iid == IID_IClassFactory))
            *ppv = static_cast<IClassFactory*>(this) ; 
        else
        {
            *ppv = NULL ;
            return E_NOINTERFACE ;
        }
        reinterpret_cast<IUnknown*>(*ppv)->AddRef() ;
        return S_OK ;
    }
    
    ///////////////////////////////////
    ULONG __stdcall CFactory::AddRef()
    {
        return ::InterlockedIncrement(&m_cRef) ;
    }
    
    
    ////////////////////////////////////
    ULONG __stdcall CFactory::Release() 
    {
    
        if (::InterlockedDecrement(&m_cRef) == 0)
        {
            delete this ;
            return 0 ;
        }
        return m_cRef ;
    }
    
    
    //
    // IClassFactory implementation
    ///////////////////////////////////////////////////////////////
    HRESULT __stdcall CFactory::CreateInstance(IUnknown* pUnkOuter, 
                                              const IID& iid,void** ppv) 
    {
    
        HRESULT hr;
        if (pUnkOuter != NULL)
        {
            return CLASS_E_NOAGGREGATION ;
        }
        CComponent* pComponent = new CComponent ;
        if (pComponent == NULL)
        {
    
            return E_OUTOFMEMORY ;
        }
    
        // Get the requested interface.
        hr = pComponent->QueryInterface(iid,(void**) ppv) ;
    
      
    if(FAILED(hr))
        pComponent->Release() ;
    
        return hr ;   
    }
    //-----------------------------------------------------------------------//
    // LockServer
    // Called by the client of a class object to keep a server open in memory,
    // allowing instances to be created more quickly.
    //-----------------------------------------------------------------------//
    ///////////////////////////////////////////////////
    HRESULT __stdcall CFactory::LockServer(BOOL bLock) 
    {
        return S_OK ;
    }

    在深入探讨更多细节之前,最好先概述一下通过 COM 库创建组件。

    1. 客户端调用 CoCreateInstance,该函数在 COM 库中实现。
    2. CoCreateInstance 是使用 CoGetClassObject 函数实现的。
    3. CoGetClassObject 调用 DllGetClassObject,该函数在 DLL 服务器中实现,其工作是为组件创建类工厂。
    4. DllGetClassObject 查询类工厂的 IClassFactory 接口,该接口将返回给 CoCreateInstance 函数。
    5. CoCreateInstance 使用 IClassFactory 接口调用其 CreateInstance 方法。
    6. IClassFactory::CreateInstance(...) 使用 new 操作符创建组件,并查询组件的接口。
    7. 获取组件的接口后,CoCreateInstance 释放类工厂并将接口指针返回给客户端。
    8. 客户端使用接口指针来调用组件的 Print 方法并使用其功能。

    下图说明了这些步骤。

    Creation of the Component vis COM Library

    因此,为了改进示例,我们需要:

    1. 实现 CFactory 方法。
    2. 在组件服务器或 DLL 中实现 DllGetClassObject,而不是 CreateInstance 函数。
    3. 编写必要的代码(或使用注册文件)以便在 Windows 注册表系统中注册组件。

    此外,在 Visual C++ 开发环境中制作 DLL 会更容易。下面将实现这些步骤。

    步骤 1

    • 使用 AppWizard,为 DLL 创建一个新项目(名称为“Component”),并选择 MFC AppWizard (DLL)。请注意,DLL 现在将驻留在与客户端不同的目录(C:\CodeProject)中。

      AppWizard

    • 在第一步中,选择“MFC Extension DLL”并单击“Finish”按钮。

      Step 1 in AppWizard

    • 打开 Component.cpp 文件,并用以下部分替换其内容。
      // Component.cpp : Defines the initialization routines for the DLL.
      //
      
      #include "stdafx.h"
      #include <afxdllx.h>
      #include "interface.h"
      #include <objbase.h>
      #include "iostream.h"
      #ifdef _DEBUG
      #define new DEBUG_NEW
      #undef THIS_FILE
      static char THIS_FILE[] = __FILE__;
      #endif
      //
      // Component.cpp
      
      
      /////////////////////////////////////////////
       BOOL APIENTRY DllMain(HINSTANCE InsModule, 
                            DWORD dwReason, 
                            void* lpReserved)
      {
      
          return TRUE;
      }
    • 将组件类和类工厂的定义及其实现复制粘贴到源文件 Component.cpp 中,并忽略 CreateInstance 函数的导出,因为类工厂将创建组件。

    步骤 2:获取类工厂 - DllGetClassObject

    当类上下文为 DLL 时,CoGetClassObject 函数将调用 DllGetClassObject 函数,如前所述,其作用是为组件创建类工厂。在 Component.cpp 文件中实现此函数。

    /////////////////////////////////////////////////////////////////////////
    STDAPI DllGetClassObject(const CLSID& clsid, const IID& iid, void** ppv)
    
    {
    
     if (clsid != CLSID_Component)
     return CLASS_E_CLASSNOTAVAILABLE;
     // Create class factory.
     CFactory* pFactory = new CFactory ;
     if (pFactory == NULL)
     return E_OUTOFMEMORY;
     // Get requested interface.
     HRESULT hr = pFactory->QueryInterface(iid, ppv); 
     pFactory->Release(); 
     return hr;
    
    }

    编译并构建 DLL(Component.dll)。

    步骤 3:注册

    使用 GUIDGEN.EXE 为 Component 类创建一个 CLSID。

    {49BF12F1-5041-48da-9B44-AA2FAA63AEFB}
      static const GUID CLSID_Component = 
      { 0x49bf12f1, 0x5041, 0x48da, 
           { 0x9b, 0x44, 0xaa, 0x2f, 0xaa, 0x63, 0xae, 0xfb } };

    创建一个扩展名为“.reg”的文件(component.reg),以便为组件创建注册表条目(使用 CLSID)。

    REGEDIT
     HKEY_CLASSES_ROOT\Codeproject.Component.1 = 
                Codeproject Component Version 1.0
     HKEY_CLASSES_ROOT\Codeproject.Component.1\CLSID = 
                {49BF12F1-5041-48da-9B44-AA2FAA63AEFB}
     HKEY_CLASSES_ROOT\Codeproject.Component = Codeproject Component 
     HKEY_CLASSES_ROOT\Codeproject.Component\CurVer = Codeproject.Component.1
     HKEY_CLASSES_ROOT\CLSID\{49BF12F1-5041-48da-9B44-AA2FAA63AEFB} = 
                Codeproject Component 1.0
     HKEY_CLASSES_ROOT\CLSID\{49BF12F1-5041-48da-9B44-AA2FAA63AEFB}\InprocServer32 = 
                c:\codeproject\component.dll
     HKEY_CLASSES_ROOT\CLSID\{49BF12F1-5041-48da-9B44-AA2FAA63AEFB}\ProgID = 
                Codeproject.Component.1
     HKEY_CLASSES_ROOT\CLSID\{49BF12F1-5041-48da-9B44-AA2FAA63AEFB}\
                VersionIndependentProgID = Codeproject.Component

    通过单击注册表文件来激活它。运行注册表文件后,条目将存储在 Windows 注册表系统中。下图显示了这些条目。

    Registry entries for the Component

    就是这样,依赖关系现在已完全打破。用客户端进行检查。

    客户端

    现在,尽管组件服务器(component.dll)位于 C:\codeproject 目录中,客户端仍然可以通过类工厂和 COM 库轻松加载它并使用其功能,这就是 COM 组件通常由其客户端创建和使用的方式。下面显示了客户端如何通过 COM 库使用组件。

    //-----------//
    // Client
    //-----------//
    void main()
    
    {
        HRESULT hr;
        IUnknown* pIUnknown;
        IComponent* pIComponent;
        IClassFactory* pIClassFactory;
    
        ::CoInitialize(NULL);
        /*
        //Once the CoCreateInstance is called, the component 
        //will be created and the client can not 
        //control it, that's why CoCreateInstance is inflexible 
        //and the solution is to call CoGetClassObject function
        hr = ::CoCreateInstance(CLSID_Component,NULL,
                CLSCTX_INPROC_SERVER,IID_IUnknown,(void**)&pIUnknown) ; 
        if (SUCCEEDED(hr))
            {
                hr=pIUnknown->QueryInterface(IID_IComponent,(void**)&pIComponent);
                if(SUCCEEDED(hr))
                   pIComponent->Print("COM from scratch.");
            }
    
         */ 
        //-------------------------------//
        // improvement of the client code
        //------------------------------//
        // By calling the CoGetClassObject function, the client can control
        // creation of the component
        hr=CoGetClassObject(CLSID_Component,CLSCTX_INPROC_SERVER, 
                     NULL,IID_IClassFactory,(void**)&pIClassFactory);
        if (SUCCEEDED(hr))
        {
            hr=pIClassFactory->CreateInstance(NULL,
                    IID_IComponent,(void**)&pIComponent);
            if(SUCCEEDED(hr))
               pIComponent->Print("COM from scratch.");
        }
    
        ::CoUninitialize ();
    
    }

    第三部分将在下一篇文章中介绍。

    © . All rights reserved.