《DirectShow开发指南》学习笔记_2

COM编程基础

       DirectX采用了COM标准。而DirectShow是一套完全基于COM的应用系统。要想深入学习DirectShow,掌握一些COM编程的基础知识是必不可少的。DirectShow应用程序实际上是一种COM组件的客户程序,只是COM组件的“使用”问题。这些问题包括如何创建COM组件、如何得到组件对象上的接口以及调用接口方法、如何管理组件对象(即需要熟悉COM的引用计数机制)等。下面的代码是最一般的步骤。

CoInitialize(NULL);   //COM库初始化
//Do something
//...
IUnknown *pUnk = NULL;
IObject *pObject = NULL;
//创建组件对象
HRESULT hr = CoCreateInstance(CLSID_Object, CLSCTX_INPROC_SERVER, NULL, IID_IUnknown,(void**)&pUnk);
if (SUCCEEDED(hr))
{
	//查询得到组件对象上的接口
	hr = pUnk->QueryInterface(IID_IObject, (voidI**)&pObject);
	if (SUCCEEDED(hr))
	{
		//调用接口的方法
		pObject->SomeMethod();
		pObject->Release();
	}
	pUnk->Release();
}
//...
CoUninitialize();   //释放COM库使用的资源

      Filter本身是一种COM组件,开发Filter牵涉到了COM组件的“实现”问题。COM本身只是一种规范,而不是实现。当使用C++来实现时,COM组件就是一个C++类,而接口都是纯虚类。可以用如下的C++代码来简单描述一个COM组件。

class Ifunction
{
public:
	virtual Method1(...) = 0;
	virtual Method2(...) = 0;
	//...
};

class MyObject : public Ifunction
{
public:
	virtual Method1(...){...}
	virtual Method2(...){...}
	//...
};

       其中,Ifunction就是我们常说的接口,而MyObject就是COM组件。COM规范规定,任何组件或接口都必须从IUnknown接口中继承而来。IUnknown定义了3个重要函数,分别是QueryInterface、AddRef和Release。其中,QueryInterface负责组件对象上的接口查询,AddRef用于增加引用计数,Release用于减少引用计数。引用计数是COM种的一个非常重要的概念,它很好地解决了组件对象的生命周期问题,即COM组件到底在什么时候被销毁,以及由谁来销毁的问题。

       COM组件一般会采用一个“自销毁”的策略。可以看到很多IUnknown::Release函数的实现:当引用计数器值为0时,组件对象就会调用delete this指令。

       除了IUnknown接口外,还有另外一个重要的接口,即IClassFactory。COM组件实际上是一个C++类,对于组件的外部使用者来说,这个类名一般不可知。COM规范规定,每个组件都必须实现一个与之相对应的类工厂(Class  Factory)。类工厂也是一个COM组件,它实现了IClassFactory接口。在IClassFactory的接口函数CreateInstance中,才能使用new操作生成一个COM组件类对象实例。

       COM组件有3种类型:进程内组件、本地进程组件和远程组件。Filter一般是一种进程内组件,以DLL(动态链接库)的形式提供服务。接下来看一下怎么来实现一个COM组件,如图所示是COM组件的创建过程。

下面的代码是CoCreateInstance函数实现的伪代码。

CoCreateInstance(...)
{
	//Do something
	//...
	IClassFactory *pClassFactory = NULL;
	CoGetClassObject(CLSID_Object, CLSCTX_INPROC_SERVER, NULL, IID_IClassFactory, (void**)&pClassFactory);
	pClassFactory->CreateInstance(NULL, IID_IUnknown, (void**)&pUnk);
	pClassFactory->Release();
	//...
}

      每个COM组件都使用一个GUID来唯一标识。当创建一个COM组件时,总是有首先通过这个GUID(上例中为CLSID_Object)调用CoGetClassObject来获得创建这个组件对象的类工厂。然后调用类工厂的接口方法IClassFactory::CreateInstance,就能真正地创建CLSID_Object标识的组件对象了。

         下面再给出一段CoGetClassObject函数以及其他相关函数实现的伪代码。

CoGetClassObject(...)
{
	//通过查询注册表CLSID_Object得知组件DLL文件路径
	//装入DLL库(调用LoadLibrary)
	//使用函数GetProcAddress(..)得到DLL中函数DllGetClassObject的函数指针
	//掉用DllGetClassObject得到类工厂对象指针
}
DllGetClassObject(...)
{
	//...
	//创建类工厂对象
	CFactory* pFactory = new CFactory;
	//查询得到IClassFactory指针
	pFactory->QueryInterface(IID_IClassFactory, (void**)&pClassFactory);
	pFactory->Release();
	//...
}
CFactory::CreateInstance(...)
{
	//...
	//创建CLSID_Object对应的组件对象
	CObject *pObject = new CObject;
	pObject->QueryInterface(IID_IUnknown, (void**)&pUnk);
	pObject->Release();
	//...
}

其中,DllGetClassObject是在COM组件DLL中必须实现的一个(导出)函数,它的作用是根据指定的组件GUID创建相应的类工厂对象,并返回这个类工厂的IClassFactory接口;CreateInstance是IClassFactory接口的一个接口方法,负责最终创建组件对象实例;CObject就是我们的COM组件类,它实现了COM框架以外的真正的组件功能。

一个典型的自注册COM组件DLL所必须的5个(导出)函数如下:

DllMain:DLL的入口函数(DirectShow实现的是DllEntryPoint);

DllGetClassObject:用于获得类工厂指针;

DllCanUnloadNow:系统空闲时会调用这个函数,以确定是否可以卸载DLL;

DllRegisterServer:将COM组件注册到注册表中;

DllUnRegisterServer:删除注册表中COM组件的注册信息。

提示:其实,SDK基类源代码中已经实现了Filter中的COM基本特性。这样,在SDK基础上开发Filter就没有那么费劲了。

猜你喜欢

转载自blog.csdn.net/Small_SaltedFish/article/details/81735504