介绍了怎么样用动态链接库去实现COM,但组件对我们来说仍是不透明的,我们需要知道实现组件DLL的位置,必须自己来加载组件的CreateInstance函数来获得组件的指针.在书中第一篇就曾经提到过:COM组件可以透明地在网络上(或本地)被重新分配位置,而不会影响本地客户程序.所以,由客户端来调用DLL并不是什么好主意.必须有一种更好的办法让组件的实现更透明,更灵活!
于是,就引入了类厂的概念.什么是类厂,类厂也是一个接口,它的职责是帮我们创造组件的对象.并返回给客户程序一个接口的指针.每个组件都必须有一个与之相关的类厂,这个类厂知道怎么样创建组件.当客户请求一个组件对象的实例时,实际上这个请求交给了类厂,由类厂创建组件实例,然后把实例指针交给客户程序。这么说有点难明白.先看一个伪实例.
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怎么获得组件的类厂,创建组件实例的.下面摘录的一篇文章很清晰的说明了这一切:
-------------------------------------------------------------------------------------
这部分我们将构造一个创建COM组件的最小框架结构,然后看一看其内部处理流程是怎样的
COM组件的运行机制,即COM是怎么跑起来的。
IUnknown *pUnk=NULL;
IObject *pObject=NULL;
CoInitialize(NULL);
CoCreateInstance(CLSID_Object, CLSCTX_INPROC_SERVER, NULL, IID_IUnknown, (void**)&pUnk);
pUnk->QueryInterface(IID_IOjbect, (void**)&pObject);
pUnk->Release();
pObject->Func();
pObject->Release();
CoUninitialize();
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的位置、文件名(这是在这里
//传入CLSID_Object的目的)
//装入DLL库 ,如果是远程则由存根代理来实现,以下操作均在另一个进程 //或远程机上实现
//使用函数GetProcAddress(...)得到DLL库中函数DllGetClassObject的函数指针。
//调用DllGetClassObject
}
DllGetClassObject是干什么的,它是用来获得类厂对象的。只有先得到类厂才能去创建组件.
下面是DllGetClassObject的伪码:
DllGetClassObject(...)
{ //CLSID_Object的传入存储在类工厂实例中,指示当前类工厂所要生产的组件类 //实例
......
CFactory* pFactory= new CFactory; //类厂对象
pFactory->QueryInterface(IID_IClassFactory, (void**)&pClassFactory);
//查询IClassFactory指针
pFactory->Release();
......
}
CoGetClassObject的流程已经到此为止,现在返回CoCreateInstance,看看CreateInstance的伪码:
CFactory::CreateInstance(.....)
{ //类工厂实例已经知道它所要生产的类实例CLSID_Object
...........
CObject *pObject = new CObject; //组件对象
pObject->QueryInterface(IID_IUnknown, (void**)&pUnk);
pObject->Release();
...........
}
需要结合工厂模式理解工厂的作用;
1 #pragma once
2 #include "ocidl.h"
3
4 class COPCClassFactory :
5 public IClassFactory
6 {
7 public:
8 COPCClassFactory(void);
9 ~COPCClassFactory(void);
10
11 //IUnknown interfaces
12 STDMETHODIMP QueryInterface( REFIID iid, LPVOID* ppInterface);
13 STDMETHODIMP_(ULONG) AddRef( void);
14 STDMETHODIMP_(ULONG) Release( void);
15
16 //IClassFactory interfaces
17 STDMETHODIMP CreateInstance( LPUNKNOWN pUnkOuter, REFIID riid, LPVOID* ppvObject);
18 STDMETHODIMP LockServer( BOOL fLock);
19
20 private:
21 ULONG m_lRefCount;
22 };
1 // QueryInterface()
2 // Implementation of the standard IUnknown QueryInterface() function for the sample OPC class factory.
3
4 STDMETHODIMP COPCClassFactory::QueryInterface( REFIID iid, LPVOID* ppInterface)
5 {
6 if ( ppInterface == NULL)
7 return E_INVALIDARG;
8
9 if ( iid == IID_IUnknown || iid == IID_IClassFactory)
10 {
11 *ppInterface = this;
12 }
13 else
14 *ppInterface = NULL;
15
16 if( *ppInterface )
17 {
18 AddRef();
19 return S_OK;
20 }
21
22 return E_NOINTERFACE;
23 }
1 // AddRef()
2 // Implementaion of the standard IUnknown AddRef() function for the sample OPC class factory.
3
4 STDMETHODIMP_(ULONG) COPCClassFactory::AddRef( void)
5 {
6 return ++m_lRefCount;
7 }
1 // Release()
2 // Implementation of the standard IUnknown Release() function for the sample OPC class factory.
3
4 STDMETHODIMP_(ULONG) COPCClassFactory::Release( void)
5 {
6 ULONG currentCount = --m_lRefCount;
7 if ( currentCount == 0)
8 delete this;
9 return currentCount;
10 }
1 // CreateInstance()
2 // This function creates an instance of the OPC sample server.
3 STDMETHODIMP COPCClassFactory::CreateInstance( LPUNKNOWN pUnkOuter, REFIID riid, LPVOID* ppvObject)
4 {
5 COPCServer* pServer = new COPCServer;
6 if ( pServer == NULL)
7 {
8 TRACE( "OPCClassFactory::CreateInstance() - Not enough memory to create OPC Server object, returning E_OUTOFMEMORY/n" );
9 return E_OUTOFMEMORY;
10 }
11 //get requested interface
12 HRESULT hr = pServer->QueryInterface( riid, ppvObject);
13 if ( FAILED( hr))
14 {
15 delete pServer;
16 }
17 else
18 {
19 g_ServerMgr.AddServerToList(pServer);
20 }
21 return hr;
22 }
23
1 // LockServer()
2 // This is a standard implementation of the IClassFactory::LockServer() function that either
3 // adds or removes a lock on the server.
4 // This is very useful if our server is an inproc process.
5 // The count determines whether the dll can be unloaded or not.
6 STDMETHODIMP COPCClassFactory::LockServer( BOOL fLock)
7 {
8 if ( fLock)
9 g_dwLockCount++;
10 else
11 g_dwLockCount--;
12
13 return S_OK;
14 }