1. 何为com
微软提出了C O M(Component Object Model, 中文也可以译作"组件对象模型")
COM组件是完全与语言无关的,开发后,可以供其它开发语言使用。
2.如何调用com
进程内com其实是一个Dll, 这个dll与普通dll有些区别,就是可以通过regsvr32之类的程序进行注册,注册过程其实是将com的clsid写入注册表,
并给出dll的物理路径,以便调用程序加载dll,下面是调用方法(VC):
CoInitialize(NULL); //初始化com库
ISimpleMsgBox* pIMsgBox;
HRESULT hr;
hr = CoCreateInstance ( __uuidof(CSimpleMsgBoxImpl), NULL, CLSCTX_INPROC_SERVER,
__uuidof(ISimpleMsgBox), (void**) &pIMsgBox );
if ( FAILED(hr) ) { //com调用失败
AfxMessageBox("error.");
return;
}
pIMsgBox->DoSimpleMsgBox ( NULL, _bstr_t("Hello my sdk com!") ); //对com中方法的调用
pIMsgBox->Release();
CoUninitialize(); //关闭com库
可以看到调用com很简单,先将com库初始化,再利用CoCreateInstance 调用即可
__uuidof(CSimpleMsgBoxImpl) 是指com库的clsid
__uuidof(ISimpleMsgBox) 是指要请求的接口的iid
(void**) &pIMsgBox 得到com对象实例指针
其中clsid, iid都是UUID的重定义,可以根据com在注册表中的注册信息,直接调用uuid
3。com分类
COM组件有三种,进程内、本地、远程。对于后两者情况必须调度接口指针及函数参数。
进程内:它可以是进程内的,即和调用者在同一个进程内,
本地:也可以和调用者在同一个机器上但在不同的进程内,
远程:还可以根本就和调用者在两台机器上
com是一个dll, 自己不能独立运行。要运行起来必须通过父进程。
我们先讨论进程内com
另外说一下com服务器,com客户端
com服务器:即com程序本身
com客户端:调用com程序的程序
com原理
可以简单总结如下:
a.如果com支持自注册,要有输出自注册相关的函数(向注册表中写注册信息)
b.通过调用DllGetClassObject得到类工厂(这个函数作为dll的输出)
c.从类工厂中返回接口实例
当然具体过程没这么简单,下面具体介绍一下。
com其实就是一个Dll, 只是这个dll遵循了Com的规范,一个典型的自注册的COM DLL所必有的四个函数:
DllGetClassObject:用于获得类厂指针
DllRegisterServer:注册一些必要的信息到注册表中
DllUnregisterServer:卸载注册信息
DllCanUnloadNow:系统空闲时会调用这个函数,以确定是否可以卸载DLL
(1)。在用regsvr32注册com时,会调用DllRegisterServer将clsid等信息写入注册表.
(2)。CoCreateInstance()函数调用时会做以下操作:
1、客户端程序调用CoCreateInstance(),传递组件对象类的CLSID以及所要接口的IID。
2、COM库在HKEY_CLASSES_ROOT/CLSID.键值下查找服务器的CLSID键值,这个键值包含服务器的注册信息。
3、COM库读取服务器DLL的全路径并将DLL加载到客户端的进程空间。
4、COM库调用在服务器中DllGetClassObject()函数为所请求的组件对象类请求类工厂。
5、服务器创建一个类工厂并将它从DllGetClassObject()返回。
6、COM库在类工厂中调用CreateInstance()方法创建客户端程序请求的COM对象。
7、CreateInstance()返回一个接口指针到客户端程序。
可以看到com具体实现的调用是通过类工厂的。而每一个Com的实现都要继承自IUnknown接口,并实现接口中的方法
class CUnknownImpl : public IUnknown
{
public:
// 构造函数和析构器
CUnknownImpl();
virtual ~CUnknownImpl();
// IUnknown 方法
ULONG AddRef();
ULONG Release)();
HRESULT QueryInterface( REFIID riid, void** ppv );
protected:
UINT m_uRefCount; // 对象的引用计数
};
QueryInterface()简称QI(),由客户端程序调用这个函数从COM对象请求不同的接口。
下面说一下CoCreateInstance调用后的各步骤实现的说明:
1、客户端程序调用CoCreateInstance(),传递组件对象类的CLSID以及所要接口的IID。
其中CLSID是com注册时写入注册表HKEY_CLASSES_ROOT//CLSID//下的GUID
这个值会在dll输出函数DllGetClassObject中验证
因为一个类可以实现多个接口,IID是要请求接口的GUID,在实现类的QueryInterface函数中将验证。
可以参考下面的代码直接利用GUID串来生成UUID
//uuid的创建
void CAboutDlg::OnUuid()
{
unsigned char* _clsid;
unsigned char* _iid;
_clsid = (unsigned char*)new char(128);
_iid = (unsigned char*)new char(128);
memcpy(_clsid, "7193E4CD-89AF-412F-930E-C131F9981CAC", sizeof("7193E4CD-89AF-412F-930E-C131F9981CAC"));
memcpy(_iid, "BF239963-D427-4910-92A6-92A52F070382", sizeof("BF239963-D427-4910-92A6-92A52F070382"));
UUID CLSID_first_alt;
UUID IID_Ifirst_alt;
UuidFromStringA(_clsid, &CLSID_first_alt);
UuidFromStringA(_iid, &IID_Ifirst_alt);
HRESULT hr;
Ifirst_alt *IFirstATL = NULL;
hr = CoInitialize(0);
if(SUCCEEDED(hr))
{
hr = CoCreateInstance( CLSID_first_alt, NULL, CLSCTX_INPROC_SERVER,
IID_Ifirst_alt, (void**) &IFirstATL);
// 如果成功,则调用fun 方法,否则显示相应的出错信息
if(SUCCEEDED(hr))
{
IFirstATL->fun();
IFirstATL->Release();
}
else
{
AfxMessageBox("CoCreateInstance Failed.");
}
}
RpcStringFreeA(&_clsid);
RpcStringFreeA(&_iid);
// 释放 COM
CoUninitialize();
}
2、COM库在HKEY_CLASSES_ROOT/CLSID.键值下查找服务器的CLSID键值,这个键值包含服务器的注册信息。
3、COM库读取服务器DLL的全路径并将DLL加载到客户端的进程空间。
2,3步做完后,com库已经将com组件加载入内存
4、COM库调用在服务器中DllGetClassObject()函数为所请求的组件对象类请求类工厂。
STDAPI DllGetClassObject ( REFCLSID rclsid, REFIID riid, void** ppv )
{
HRESULT hrRet;
CSimpleMsgBoxClassFactory* pFactory;
if ( !IsEqualGUID ( rclsid, __uuidof(CSimpleMsgBoxImpl) ))
return CLASS_E_CLASSNOTAVAILABLE;
// Check that ppv really points to a void*.
if ( IsBadWritePtr ( ppv, sizeof(void*) ))
return E_POINTER;
*ppv = NULL;
// Construct a new class factory object.
pFactory = new CSimpleMsgBoxClassFactory;
if ( NULL == pFactory )
return E_OUTOFMEMORY;
// AddRef() the factory since we're using it.
pFactory->AddRef();
// QI() the factory for the interface the client wants.
hrRet = pFactory->QueryInterface ( riid, ppv );
// We're done with the factory, so Release() it.
pFactory->Release();
return hrRet;
}
DllGetClassObject是dll的输出函数之一,用来得到类工厂,从而创建对象。可以看到这个函数中对传入的clsid进行了检查。
5、服务器创建一个类工厂并将它从DllGetClassObject()返回。
6、COM库在类工厂中调用CreateInstance()方法创建客户端程序请求的COM对象。
STDMETHODIMP CSimpleMsgBoxClassFactory::CreateInstance ( IUnknown* pUnkOuter,
REFIID riid,
void** ppv )
{
HRESULT hrRet;
CSimpleMsgBoxImpl* pMsgbox;
// We don't support aggregation, so pUnkOuter must be NULL.
if ( NULL != pUnkOuter )
return CLASS_E_NOAGGREGATION;
// Check that ppv really points to a void*.
if ( IsBadWritePtr ( ppv, sizeof(void*) ))
return E_POINTER;
*ppv = NULL;
// Create a new COM object!
pMsgbox = new CSimpleMsgBoxImpl;
if ( NULL == pMsgbox )
return E_OUTOFMEMORY;
// QI the object for the interface the client is requesting.
hrRet = pMsgbox->QueryInterface ( riid, ppv );
// If the QI failed, delete the COM object since the client isn't able
// to use it (the client doesn't have any interface pointers on the object).
if ( FAILED(hrRet) )
delete pMsgbox;
return hrRet;
}
可以看到pMsgbox = new CSimpleMsgBoxImpl;这一句创建了对象,
然后再通过hrRet = pMsgbox->QueryInterface ( riid, ppv );对iid进行验证,并得到对象指针
例子程序在机器的D:/Test/vc/com
这是通过sdk研究com的原理。但应用中一般使用Alt来简化开发。
参考资料: