1 题外话
这几天没有什么太多的任务,回顾一下DirectShow的东西,发现MSDN上有一篇文章不错,翻译一下,顺便提高一下英文。
题目:How to Create aDirectShow Filter DLL
出处:http://msdn.microsoft.com/en-us/library/dd389096(v=VS.85).aspx
2 翻译内容
本文描述如何在微软的DirectShow中实现一个动态链接库(DLL)的组件。本文是How to ImplementIUnknown的继续,其描述了如何让你的组件通过继承CUnknown基类来实现一个IUnknown接口。
本文包括以下几个章节。
· 类工厂和工厂模板(ClassFactories and Factory Templates)
· 工厂模板数组(FactoryTemplate Array)
· 动态链接库函数(DLLFunctions)
注册一个DirectShow filter需要以下附件的步骤,这些步骤并没有在本文中描述。关于注册filter可以参考How to RegisterDirectShow Filters。
2.1 类工厂和工厂模板
在客户端创建一个COM对象实例之前,它会调用CoGetClassObject函数创建一个对象的类工厂。然后客户端再调用类工厂的IClassFactory::CreateInstance方法。实际上是这个类工厂创建了这个组件并返回客户端请求的接口的指针。(CoCreateInsatance函数合并了这些步骤,在它的内部调用了上面的那些过程。)
下面的图示说明了方法调用的过程。
CoGetClassObject调用定义在DLL中的DllGetClassObject函数。DllGetClassObject创建一个类工厂并且返回一个类工厂接口的指针。DirectShow已经为你实现了DllGetClassObject,但是这个方法以一种指定的方式依赖于你的代码。为了理解它是如何工作的,你必须理解DirectShow如何实现类工厂的。
一个类工厂就是用来创建其他COM对象的COM对象。一个类工厂创建一种类型的对象。在DirectShow中,每个类工厂就是一个类似于CClassFactory的类。类工厂通过其他的一个类来实现,CFactoryTemplate,也叫工厂模板。每一个类工厂持有一个工厂模板的指针。工厂模板包含一个特定组件的一下信息,例如组件标识符(CLSID),一个创建组件的函数指针。
DLL中声明一个全局的工厂模板数组,一个数组对应一个组件。当DllGetClassObject创建一个新的类工厂时,它依据CLSID查找模板数组。假设找到匹配的一个元素,它创建一个持有对应模板指针的类工厂。当客户端调用IClassFactory::CreateInstance,类工厂调用定义在模板中的实例化函数创建出相应的组件。
下面的示意图说明了方法的调用过程。
你从这种架构中得到的好处是,针对于你的组件你只需要做很少的事情,例如实现实例化函数,而不必实现整个的类工厂。
2.2 工厂模板数组
工厂模板包含下面这些公有成员变量:
const WCHAR * m_Name; // Name
const CLSID * m_ClsID; // CLSID
LPFNNewCOMObject m_lpfnNew; // Function to create an instance
// of the component
LPFNInitRoutine m_lpfnInit; // Initialization function (optional)
const AMOVIESETUP_FILTER * m_pAMovieSetup_Filter; // Set-upinformation (for filters)
其中两个函数指针,m_lpfunNew和m_lpfnInit定义为下面的形式:
typedef CUnknown *(CALLBACK *LPFNNewCOMObject)(LPUNKNOWNpUnkOuter, HRESULT *phr);
typedef void (CALLBACK *LPFNInitRoutine)(BOOL bLoading, constCLSID *rclsid);
第一个是组件实例化的函数。第二个是可选的初始化函数。如果你提供一个初始化的函数,它将从内部在DLL的入口点(entry-point)被调用。(关于DLL的入口函数将在本文的后面进行讨论。)
假设你已经创建了一个包含继承至CUnknown的CMyCompone的DLL。在你的DLL中你必须提供下面的一些元素:
· 初始化函数,返回一个新的CMyComponet实例的公有方法。
· 一个命名为g_Templates的全局的工厂模板数组。这个数组包含CMyComponet的工厂模板。
· 一个指定数组大小的全局变量g_cTemplates。
下面的例子说明如何声明这些元素:
// Public method that returns a new instance.
CUnknown * WINAPI CMyComponent::CreateInstance(LPUNKNOWN pUnk,HRESULT *pHr)
{
CMyComponent *pNewObject= new CMyComponent(NAME("My Component"), pUnk, pHr );
if (pNewObject == NULL){
*pHr =E_OUTOFMEMORY;
}
return pNewObject;
}
CFactoryTemplate g_Templates[1] =
{
{
L"My Component", // Name
&CLSID_MyComponent, // CLSID
CMyComponent::CreateInstance, //Method to create an instance of MyComponent
NULL, // Initializationfunction
NULL // Set-up information (forfilters)
}
};
int g_cTemplates = sizeof(g_Templates) /sizeof(g_Templates[0]);
CreateInstance方法调用类的构造函数返回一个新实例的指针。参数pUnk是IUnknown接口的指针,你可以把这个参数传递给类的构造函数。参数pHr指向一个HRESULT。构造函数给pHr赋一个适当值,但是如果构造失败,需要把这个值赋为E_OUTOFMEMORY。
NAME宏在Debug版本将生成一个字符串,但是在最终的Release版本被设置为NULL。在这个例子中被用来在Debug log给组件一个名字,但是在Release版中不占用内存。
CreateInstance方法可以是任何名字,因为在类工厂中引用的是工厂模板中的函数指针。然而,g_Templates和g_cTemplates是全局的变量,为了使其在类工厂中被找到必须使用这个名字。
2.3 动态链接库函数
为了使其被注册,卸载和被载入内存一个DLL必须实现下面的函数:
· DllMain: DLL的入口点。DllMain是一个系统保留的函数名。DirectShow的实现使用DllEntryPoint。可以参考PlatformSDK获取更多的信息。
· DllGetClassObject: 创建一个类工厂实例。在前面的段落介绍过。
· DllCanUnloadNow: 询问这个DLL是否可以被安全卸载。
· DllRegisterServer: 为DLL创建注册表。
· DllUnRegisterServer: 移除DLL的注册表项。
在这些当中,前三个已经被DirectShow实现了。如果你的工厂模板通过m_lpfnInit成员变量提供一个初始化函数,这个初始化化函数将在DLL的如果点从内部调用。了解更多的系统什么使用调用DLL入口点函数,参考DllMain。
你必须实现DllRegisterServer和DllUnregisterServer,但是DirectShow提供了一个已经做了一些必要工作的函数AMovieDllRegisterServer2。你的组件可以简单的调用这个函数,如下面的例子所示:
STDAPI DllRegisterServer()
{
return AMovieDllRegisterServer2(TRUE );
}
STDAPI DllUnregisterServer()
{
returnAMovieDllRegisterServer2( FALSE );
}
然而,在DllRegisterServer和DllUnregisterServer的内部,你可以定制一下所需的注册过程。如果你的DLL包含一个filter,你也许需要一些附件的工作。更多的信息可以参考How to Register DirectShow Filters。
在你的模块定义文件(.def)中,要暴露出所有接入点所需的函数,下面是一个.def文件的例子:
EXPORTS
DllGetClassObjectPRIVATE
DllCanUnloadNow PRIVATE
DllRegisterServerPRIVATE
DllUnregisterServerPRIVATE
你可以使用Regsvr32.exe工具注册DLL。