为了实现COM的动态链接,必须把组件的实现和客户代码分隔开。
一种处理是把组件放入DLL中。
这样为了支持客户创建组建,组件需要导出一个类似CreateInstance的函数。
- extern "C" IUnknown* CreateInstance()
- {
- IUnknown* pI = static_cast<IX*>(new CA) ;
- pI->AddRef() ;
- return pI ;
- }
- [DEF]
- EXPORTS
- CreateInstance @1 PRIVATE
然后客户需要载入Dll,再调用CreateInstance。如果所有COM都提供同样的CreateInstance接口,这一处理可以变得比较通用:
- typedef IUnknown* (*CREATEFUNCPTR)() ;
- IUnknown* CallCreateInstance(char* name)
- {
- HINSTANCE hComponent = ::LoadLibrary(name) ;
- if (hComponent == NULL)
- {
- cout << "CallCreateInstance:/tError: Cannot load component." << endl ;
- return NULL ;
- }
- CREATEFUNCPTR CreateInstance
- = (CREATEFUNCPTR)::GetProcAddress(hComponent, "CreateInstance") ;
- if (CreateInstance == NULL)
- {
- cout << "CallCreateInstance:/tError: "
- << "Cannot find CreateInstance function."
- << endl ;
- return NULL ;
- }
- return CreateInstance() ;
- }
传入COM的DLL路径名,就可以载入COM组件了。
- int main()
- {
- IUnknown* pIUnknown = CallCreateInstance("com1.dll") ;
- if (pIUnknown != NULL)
- {
- //......
- }
- }
在上面这个例子中有几个问题。
1. 创建COM组件必须要知道对应的dll所在的位置,当dll位置发生移动会创建失败
2. 如果一个dll中有多个COM组件,上面只能支持其中一个组件的创建
为解决这一问题,实际中每个COM组件会有一个CLSID,这其实就是一个GUID,然后需要在注册表中注册。
注册时会在注册表中记录COM组件的GUID和dll路径。
这样客户只要知道COM组件的CLSID,然后在注册表中就可以查询到dll的路径,这样问题1解决了。
在dll导出的CreateInstance函数如果再接受一个CLSID作为参数,通过CLSID创建不同的COM组件,则问题2也解决了
注册信息放在注册表的HKEY_CLASSES_ROOT的CLSID关键字下。
CLSID是一个类似{D01ECD7A-F5FE-4E39-85D9-878B14F487CB}的字串。
这个字串可十分不好记,所以还有一个对应的ProgID。
ProgID格式如下:<Program>.<Component>.<Version>
例如:Visio.Application.1
ProgID直接列在HKEY_CLASSES_ROOT下,每个ProgID对应一个CLSID。
一般客户是知道COM组件的ProgID,通过ProgID查找到CLSID,最后通过找到的CLSID创建COM组件。
CLSIDFromProgID和ProgIDFromCLSID可实现ProgID与CLSID的相互转换
注:COM组件的CLSID是全球唯一的,而ProgID可能重名
为实现COM的注册功能,DLL还需要导出两个函数:
DllRegisterServer 和 DllUnregisterServer。
这样可以通过Regsvr32.exe来注册COM组件。Regsvr32实际上调用了dll中的DllRegisterServer函数