COM技术7

COM组件的创建过程中,类厂(Class Factory)起到了关键作用,封装了组件实例化的细节。客户端通过CoCreateInstance调用CoGetClassObject,后者查找注册表找到DLL位置,接着调用DllGetClassObject创建CFactory实例。CFactory的CreateInstance方法用于生成组件实例。CoGetClassObject提供了更多灵活性,例如创建多个实例或使用不同接口。CoCreateInstance通常是默认选择,但在特定情况下,如需要IClassFactory以外的接口或提高效率时,会使用CoGetClassObject。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

 

     回忆一下COM技术5中我们创建组件的方法,客户以DLL的名称做为参数.装载此DLL并调用其中所输出的函数CreateInstance.这个函数可以建立一个组件的实例并给客户返回一个IUnknown接口的指针.但是组件对我们来说仍是不透明的,我们需要知道实现组件DLL的位置.但是COM组件实际上可以透明地在网络上(或本地)被重新分配位置,而不会影响本地客户程序.所以,由客户端来调用DLL并不是什么好主意.必须有一种更好的办法让组件的实现更透明,更灵活.于是就出现了类厂的概念.这和设计模式中封装变化点的思想一致,哪里有变化,我就封装哪里.类厂封装了组件的产生过程.下面来看看这一系列活动内部运转的详细过程:我在 http://www.cnblogs.com/shipfi/archive/2007/02/13/649196.html 里找到了详细介绍:

 

/*
 1.实现二个接口IX,IY 
 2.实现一个组件CA,实现了IX,IY接口.   
 3.对于这个组件进行注册,把组件的信息加入到注册表中.实现DllRegisterServer和DllUnregisterServer函数.函数具体功能就是把本组件的CLSID,ProgID,DLL的位置放入注册表中.这样程序就可以通过查询注册表来获得组件的位置.
*/


/*
 4.创建本组件类厂的实例
*/

class  CFactory: public  IClassFactory
{
    
virtual HRESULT __stdcall QueryInterface(const IID& iid,void** ppv);
    
virtual ULONG   __stdcall AddRef();
    
virtual ULONG   __stdcall Release();
    
    
virtual HRESULT __stdcall CreateInstance(IUnknown* pUnknownOuter, const IID& iid, void** ppv);
}


/*
 在类厂实例中,主要的功能就是CreateInstance了,这个函数就是创建组件的相应实例.看它的实现:
*/

HRESULT __stdcall CFactory::CreateInstance(IUnknown
*  pUnknownOuter, const  IID &  iid, void **  ppv)
{
    
//...
    CA* pA = new CA;
    
if(pA == NULL)
    
{
        
return E_OUTOFMEMORY;        
    }

    HRESULT hr 
= pA->QueryInterface(iid,ppv);
    
    pA
->Release();
    
return hr;
}

/*
 5.在这个组件的DLL中导出DllGetClassObject函数.这个函数的功能就是创建类厂的实例对象并查询接口.看其实现:
*/

STDAPI DllGetClassObject(
const  CLSID &  clsid, const  IID &  iid, void **  ppv)
{
    
//....
    CFactory* pFactory = new CFactory();
    
    
if(pFactory == NULL)
    
{
        
return E_OUTOFMEMORY;
    }

    
    HRESULT hr 
= pFactory->QueryInterface(iid,ppv);
    pFactory
->Release();
    
return hr;
}





/*
组件的实现差不多就这么多,在客户端怎么调用组件呢?这就需要用到COM函数库了,由COM函数库去查找注册表,调用组件的类厂,创建组件实例,返回接口.如下所示:
*/

{
    
//...
    IUnknown* pUnk = NULL;
    IX
* iX = NULL;
    CoInitialize(NULL);
    CoCreateInstance(CLSID_Component1,CLSCTX_INPROC_SERVER,IID_IUnknown,(
void**)&pUnk);
    pUnk
->QueryInterface(IID_IX,(void**)&iX);
    pUnk
->Release();
    iX
->Fx();
    iX
->Release();
    CoUninitialize();
}


/*
至于客户是通过CoCreateInstance怎么获得组件的类厂,创建组件实例的.下面,清晰的说明了这一切:
*/


// CoCreateInstance内部实现的伪代码: 
CoCreateInstance(....) 

    
//....... 
    IClassFactory *pClassFactory=NULL; 
    CoGetClassObject(CLSID_Object, CLSCTX_INPROC_SERVER, NULL, IID_IClassFactory, (
void **)&pClassFactory); 
    pClassFactory
->CreateInstance(NULL, IID_IUnknown, (void**)&pUnk); 
    pClassFactory
->Release(); 
    
//........ 
}
 
// 这段话的意思就是先得到类厂对象,再通过类厂创建组件从而得到IUnknown指针.

// 继续深入一步,看看CoGetClassObject的内部伪码:
CoGetClassObject(.....) 

    
//通过查注册表CLSID_Object,得知组件DLL的位置,文件名 
    
//装入DLL库 
    
//使用函数GetProcAddress(...)得到DLL库中函数DllGetClassObject的函数指针. 
    
//调用DllGetClassObject 
}
 


// DllGetClassObject是干什么的,它是用来获得类厂对象的.只有先得到类厂才能去创建组件. 
// 下面是DllGetClassObject的伪码:
DllGetClassObject(...) 

    
//...... 
    CFactory* pFactory= new CFactory; //类厂对象 
    pFactory->QueryInterface(IID_IClassFactory, (void**)&pClassFactory); 
    
//查询IClassFactory指针 
    pFactory->Release(); 
    
//...... 
}
 

// CoGetClassObject的流程已经到此为止,现在返回CoCreateInstance,看看CreateInstance的伪码: 
CFactory::CreateInstance(.....) 

    
//......
    CA* pA = new CA; //组件对象 
    CA->QueryInterface(IID_IUnknown, (void**)&pUnk); 
    CA
->Release(); 
}
  

     总结一下上面描述的过程:客户端调用CoCreateInstance,导致调用CoGetClassObject,CoGetClassObject通过查找注册表,得知DLL位置,文件名,然后调用DLL中DllGetClassObject,DllGetClassObject的功能是返回CFactory的实例.返回后,回到CoCreateInstance,通过CFactory的指针,调用pClassFactory->CreaetInstance()创建组件实例.这样就返回了组件实例的指针.

CoCreateInstace  -->  CoGetClassObject  --> DllGetClassObject --> Get CFactory*
                        <-------------------------------------------------------
                        -->  CFactory->CreateInstance(); --> Get IX* 
IX->Fx();

 

    下面插图也很好说明了这个过程:

 

    下面是得到桌面背景的一个例子,可以看到,客户其实只需要调用CoCreateInstance就可以了:

void  TestIActiveDesktop()
{
    WCHAR   wszWallpaper [MAX_PATH]; 
    CString strPath; 
    HRESULT hr; 
    IActiveDesktop
* pIAD; 
    
    
// 1. 初始化COM库(让Windows加载DLLs)。通常是在程序的InitInstance()中调用 
    
// CoInitialize ( NULL )或其它启动代码。MFC程序使用AfxOleInit()。 
    
    CoInitialize ( NULL ); 
    
    
// 2. 使用外壳提供的活动桌面组件对象类创建COM对象。 
    
// 第四个参数通知COM需要什么接口(这里是IActiveDesktop). 
    
    hr 
= CoCreateInstance ( CLSID_ActiveDesktop, 
        NULL, 
        CLSCTX_INPROC_SERVER, 
        IID_IActiveDesktop, 
        (
void**&pIAD ); 
    
    
if ( SUCCEEDED(hr) ) 
    

        
// 3. 如果COM对象被创建成功,则调用这个对象的GetWallpaper() 方法。 
        hr = pIAD->GetWallpaper ( wszWallpaper, MAX_PATH, 0 ); 
        
        
if ( SUCCEEDED(hr) ) 
        

            
// 4. 如果 GetWallpaper() 成功,则输出它返回的文件名字。 
            
// 注意这里使用wcout 来显示Unicode 串wszWallpaper.  wcout 是 
            
// Unicode 专用,功能与cout.相同。 
            wcout << L"Wallpaper path is:    " << wszWallpaper << endl << endl; 
        }
 
        
else 
        

            cout 
<< _T("GetWallpaper() failed."<< endl << endl; 
        }
 
        
        
// 5. 释放接口。 
        pIAD->Release(); 
    }
 
    
else 
    

        cout 
<< _T("CoCreateInstance() failed."<< endl << endl; 
    }
 
    
    
// 6. 收回COM库。MFC 程序不用这一步,它自动完成。 
    CoUninitialize(); 
    
}

 

    也许图形最能说明问题,再发一图: 

    首先是客户,它将调用CoGetClassObject来启动组件的创建过程.其次是COM库,它实现了CoGetClassObject函数.第三个角色是DLL,其中实现了被CoGetClassObject调用的DLLGetClassObject函数.DLLGetClassObject的任务就是创建客户所请求的类厂.当然它创建此类厂的方式完全是由开发人员决定的.
    在创建好类厂之后,客户将使用IClassFactory接口来创建相应的组件,IClassFactory::CreateInstance如何创建组件也是由开发人员决定的.IClassFactory将把这个过程封装起来,以便类厂能够使用关于组件的内部知识来创建它.

    下面的图片,展示了主要元素之间的协作关系:

 

    在例子中是通过CoCreateInstace创建组件的,当然客户还可以调用CoGetClassObject,利用CoGetClassObject会有更大的灵活性.和CoCreateInstace不同的是,CoGetClassObject返回的是创建类厂的接口.
    但一般使用CoCreateInstace就足够了,有两个情况,我们要使用CoGetClassObject,一是如果想用不同于IClassFactory的某个创建接口(比如IClassFactory2)来创建组件,则必须使用CoGetClassObject.第二种情况是,如需创建同一个组件的多个实例,那么使用CoGetClassObject将可以获得更高的效率.因为只需要创建相应的类厂一次,而CoCreateInstace则需为每一个实例分别创建并释放相应的类厂.见下面代码(是得到桌面背景的另一个实现方法):

void  TestIActiveDesktop()
{
    WCHAR   wszWallpaper [MAX_PATH]; 
    CString strPath; 
    HRESULT hr; 
    IActiveDesktop
* pIAD; 
    
    
// 1. 初始化COM库(让Windows加载DLLs)。通常是在程序的InitInstance()中调用 
    
// CoInitialize ( NULL )或其它启动代码。MFC程序使用AfxOleInit()。 
    
    CoInitialize ( NULL ); 
    
    
// 2. 使用外壳提供的活动桌面组件对象类创建COM对象。 
    
// 第四个参数通知COM需要什么接口(这里是IActiveDesktop). 
#if 1    
    IClassFactory
* pIFactory = NULL;
    hr 
=
 CoGetClassObject(CLSID_ActiveDesktop,
        CLSCTX_INPROC_SERVER,
        NULL,
        IID_IClassFactory,    
//lei:这里还可以用其他创建型接口

        (void**)&pIFactory);
    
if
 (SUCCEEDED(hr))
    
{
        hr 
= pIFactory->CreateInstance(NULL,IID_IActiveDesktop,(void **)&
pIAD);
                                          
//lei:这里可以多次调用,获得多个实例.


        pIFactory
->Release();
    }

#else
    hr 
= CoCreateInstance ( CLSID_ActiveDesktop, 
        NULL, 
        CLSCTX_INPROC_SERVER, 
        IID_IActiveDesktop, 
        (
void**&pIAD ); 
#endif    
    
if ( SUCCEEDED(hr) ) 
    

        
// 3. 如果COM对象被创建成功,则调用这个对象的GetWallpaper() 方法。 
        hr = pIAD->GetWallpaper ( wszWallpaper, MAX_PATH, 0 ); 
        
        
if ( SUCCEEDED(hr) ) 
        

            
// 4. 如果 GetWallpaper() 成功,则输出它返回的文件名字。 
            
// 注意这里使用wcout 来显示Unicode 串wszWallpaper.  wcout 是 
            
// Unicode 专用,功能与cout.相同。 
            wcout << L"Wallpaper path is:    " << wszWallpaper << endl << endl; 
        }
 
        
else 
        

            cout 
<< _T("GetWallpaper() failed."<< endl << endl; 
        }
 
        
        
// 5. 释放接口。 
        pIAD->Release(); 
    }
 
    
else 
    

        cout 
<< _T("CoCreateInstance() failed."<< endl << endl; 
    }
 
    
    
// 6. 收回COM库。MFC 程序不用这一步,它自动完成。 
    CoUninitialize(); 
    
}


一 组件基础 1 软件开发的阶段 1.1 结构化编程 采用自顶向下的编程方式,划分模块 和功能的一种编程方式。 1.2 面向对象编程 采用对象的方式,将程序抽象成类, 模拟现实世界,采用继承、多态的方式 设计软件的一种编程方式。 1.3 面向组件编程 将功能和数据封装成二进制代码,采用 搭积木的方式实现软件的一种编程方式。 2 组件和优点 2.1 组件 - 实际是一些可以执行的二进 制程序,它可以给其他的应用程序、操 作系统或其他组件提供功能 2.2 优点 2.2.1 可以方便的提供软件定制机制 2.2.2 可以很灵活的提供功能 2.2.3 可以很方便的实现程序的分布式 开发。 3 组件的标准 - COMComponent Object Model ) 3.1 COM是一种编程规范,不论任何开发语言 要实现组件都必须按照这种规范来实现。 组件和开发语言无关。 这些编程规范定义了组件的操作、接口的 访问等等。 3.2 COM接口 COM接口是组件的核心,从一定程度上 讲"COM接口是组件的一切". COM接口给用户提供了访问组件的方式. 通过COM接口提供的函数,可以使用组件 的功能. 4 COM组件 4.1 COM组件-就是在Windows平台下, 封装在动态库(DLL)或者可执行文件(EXE) 中的一段代码,这些代码是按照COM的 规范实现. 4.2 COM组件的特点 4.2.1 动态链接 4.2.2 与编程语言无关 4.2.3 以二进制方式发布 二 COM接口 1 接口的理解 DLL的接口 - DLL导出的函数 类的接口 - 类的成员函数 COM接口 - 是一个包含了一组函数指针 的数据结构,这些函数是由组件实现的 2 C++的接口实现 2.1 C++实现接口的方式,使用抽象类 定义接口. 2.2 基于抽象类,派生出子类并实现 功能. 2.3 使用 interface 定义接口 interface ClassA { }; 目前VC中,interface其实就是struct 3 接口的动态导出 3.1 DLL的实现 3.1.1 接口的的定义 3.1.2 接口的实现 3.1.3 创建接口的函数 3.2 DLL的使用 3.2.1 加载DLL和获取创建接口的函数 3.2.2 创建接口 3.2.3 使用接口的函数 4 接口的生命期 4.1 问题 在DLL中使用new创建接口后,在用户 程序使用完该接口后,如果使用delete 直接删除,会出现内存异常. 每个模块有自己的内存堆(crtheap) EXE - crtheap DLL - crtheap new/delete/malloc/free默认情况 下都是从自己所在模块内存堆(crtheap) 中分配和施放内存.而各个模块的 这个内存堆是各自独立.所以在DLL中 使用new分配内存,不能在EXE中delete. 4.2 引用计数和AddRef/Release函数 引用计数 - 就是一个整数,作用是 表示接口的使用次数 AddRef - 增加引用计数 +1 Release - 减少引用计数 -1, 如果 当引用计数为0,接口被删除 4.3 使用 4.3.1 创建接口 4.3.2 调用AddRef,增加引用计数 4.3.3 使用接口 4.3.4 调用Release,减少引用计数 4.4 注意 4.4.1 在调用Release之后,接口指针 不能再使用 4.4.2 多线程情况下,接口引用计数 要使用原子锁的方式进行加减 5 接口的查询 5.1 每个接口都具有唯一标识 GUID 5.2 实现接口查询函数 QueryInterface 6 IUnknown 接口 6.1 IUnknown是微软定义的标准接口 我们实现所有接口就是继承这个接口 6.2 IUnknown定义了三个函数 QueryInterface 接口查询函数 AddRef 增加引用计数 Release 减少引用计数 7 接口定义语言 - IDL(Interface Definition Language ) 7.1 IDL和MIDL IDL - 定义接口的一种语言,与开发 语言无关. MIDL.EXE - 可以将IDL语言定义接口, 编译成C++语言的接口定义 7.2 IDL的基础 import "XXXX.idl" [ attribute ] interface A : interface_base { } 7.2.1 Import 导入,相当于C++的 #include 7.2.2 使用"[]"定义区域,属性描述 关键字 1) object - 后续是对象 2) uuid - 定义对象GUID 3) helpstring - 帮助信息 4) version - 版本 5) point_default - 后续对象 中指针的默认使用方式 比如: uniqune - 表示指针可以 为空,但是不能修改 7.2.3 对象定义 1) 父接口是IUnknown接口 2) 在对象内添加函数,函数定义必须 是返回 HRESULT. HRESULT是32位整数,返回函数是否 执行成功,需要使用 SUCCESSED和 FAILED宏来判断返回值.
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值