COM技术7

来源:互联网 发布:如何报考网络大专 编辑:程序博客网 时间:2024/05/21 09:39

 

     回忆一下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(); 
    
}


原创粉丝点击