三. 如何得到Preview Handler的GUID
首先说明一下,IPreviewHandler接口的GUID是8895b1c6-b41f-4c1c-a562-0d564250836f,要找支持的Preview Handler的GUID就要用到它。如何去找呢,在注册表中有四个地方,我这个程序里是四个,不知道还没有别的方式。
第一种方式以.docx后缀为例,注册表路径是:
HKEY_CLASSES_ROOT\.docx\ShellEx\{8895b1c6-b41f-4c1c-a562-0d564250836f},如下图:
其中{8895b1c6-b41f-4c1c-a562-0d564250836f}是IPreviewHandler接口的GUID(下同)。
第二种方式以.html后缀为例,注册表路径是:先从HKEY_CLASSES_ROOT\.html得到其默认值htmlfile.再到HKEY_CLASSES_ROOT\htmlfile\ShellEx\{8895b1c6-b41f-4c1c-a562-0d564250836f}
第三种方式还是以.html后缀为例,注册表路径是HKEY_CLASSES_ROOT\SystemFileAssociations\.html\ShellEx\{8895b1c6-b41f-4c1c-a562-0d564250836f}。
第四种方式以.txt后缀为例:先从HKEY_CLASSES_ROOT\.txt 得到PerceivedType名字的值text,再到这个路径得到GUID。HKEY_CLASSES_ROOT\SystemFileAssociations\text\ShellEx\{8895b1c6-b41f-4c1c-a562-0d564250836f}。
以下代码都在PreviewsManager.cs里面
C#实现读注册表
// The GUID of IPreviewHandler string CLSID = "8895b1c6-b41f-4c1c-a562-0d564250836f"; Guid g = new Guid(CLSID); // This method returns the GUID of supported preview handler associated strExtension public string GetCLSIDFromExtensionShellEx(Guid previewHandlerGuid, string strExtension) { String strCLSID = String.Empty; using (RegistryKey hkPreviewHandler = Registry.ClassesRoot.OpenSubKey( String.Format(@"{0} \ShellEx\{0:B}", strExtension, previewHandlerGuid))) { if (hkPreviewHandler != null) { strCLSID = hkPreviewHandler.GetValue(String.Empty).ToString(); } } return strCLSID; } }
用法如下:
String strGUID = GetCLSIDFromExtensionShellEx(g, ".docx");
注意:返回的字符串可能是null或String.Empty,所以要对其进行判断
if(String.IsNullOrEmpty(strGUID))
{
// Try find GUID using the second mode, if the return value is still String.Empty,
// use the third mode and try again, if no guid is found, try the forth mode...
}
根据GUID创建COM实例
如果找到了GUID,那么就要用这个GUID来创建一个COM实例,相当于C++中的CoCreateInstance函数,如何实现呢?
在C#中有一个类Activator,Activator类包含特定的方法,用以在本地或从远程创建对象类型,或获取对现有远程对象的引用。这个类提供了创建COM对象的方法。创建COM接口部分代码如下:
// Create a COM instance, it is like CoCreateInstance in C++ Guid newCLSID = new Guid(strCLSID); Get the type of the COM interface specified by CLSID(Guid) Type typeofPreveiwHandler = Type.GetTypeFromCLSID(newCLSID, true); Object obj = Activator.CreateInstance(typeofPreveiwHandler);
COM实例我也创建好了,我应该怎么用这个实例来实现文件预览功能,不要着急,让我们分析一下。前面说过,IPreviewHandler是MS提供的一个接口,这个接口里面提供了一些方法,能实现文件预览,那么这些COM实例肯定是实现了IPreviewHandler接口,所以,就应该把刚才创建的COM实例转换成IPreviewHandler对象,用IPreviewHandler对象去调用它的函数,从而实现文件预览功能。
没错,就是这样的,但有一个问题,这些COM实例并没有打开过文件,它是怎么知道文件内容是什么? 问得好,在把COM实例转换成IPreviewHandler接口之前,要对Preview Handler进行初始化,这里就要用到两另外的两个接口IInitializeWithFile和IInitializeWithStream,这两个接口的相关信息请参见MSDN,这里不介绍。其实绝大部分Preview Handler都用IInitializeWithFile这个接口进行初始化。部分代码如下:
// Convert COM instance to IInitializeWithFile or IInitializeWithStream to initialize. IInitializeWithFile fileInit = obj as IInitializeWithFile; IInitializeWithStream streamInit = obj as IInitializeWithStream; bool bSuccess = false; bool isInitalized = false; if (fileInit != null) { fileInit.Initialize(fileName, 0); isInitalized = true; } else if(streamInit != null) { // Open file and read data to stream FileStream stream = new FileStream(fileName, FileMode.Open, FileAccess.Read, FileShare.ReadWrite); COMStream stream = new COMStream(stream); streamInit.Initialize(stream, 0); isInitalized = true; } if(isInitalized) { // Convert obj to IPreviewHandler object IPreviewHandler pHandler = obj as IPreviewHandler; if(pHandler != null) { // TODO: Begin to get preview RECT rect = new RECT(viewRect); pHandler.SetWindow(handler, ref rect); pHandler.SetRect(ref rect); pHandler.DoPreview(); pHandler.Unload(); Marshal.ReleaseComObject(pHandler); } }以上就是得到Preview的部分代码,需要进行说明的,我已经将其标记为红色。下面对其进行一些解释(顺序从上到下):
1,obj:这个对象就是COM实例,它会在后面用到。
2,COMStream:这是一个自己写的类,它继承了IStream,IDispose接口,它实现了IStream,IDispose接口的方法。具体的我会在后面文章中说明。
3,RECT rect = new RECT(viewRect); pHandler.SetRect(ref rect);这两句话就告诉IPreviewHander,你想把取得的Preview放在多大的位置上面。
4,pHandler.SetWindow(handler, ref rect); 这句话就告诉IPreviewHander,你想把取得的Preview放在哪个窗体上面,窗体由handler指定,在C#中是IntPtr类型,C++中是HANDLE。这句话一定要有,不然Preview就是取到了,也显示不出来。
5,pHandler.DoPreview(); 只要程序调用这一句,那么它就开始取Preivew了。
6,pHandler.Unload():取完Preview后,不要忘记释放IPreviewHandler对象,不然会有内存泄漏。直接调用IPreviewHandler接口的Unload()函数。
7,Marshal.ReleaseComObject(pHandler); 释放COM对象
小结:
1,根据文件后缀名从注册表中得到支持的Preview的GUID。
2,根据得到的GUID,创建COM实例。
3,得到COM实例后,将其转换为IInitializeWithFile或IInitializeWithStream,调用Initialize对这个COM实例进行初始化。
4,如果初始化成功,将这个COM实例转换为IPreviewHandler接口对象。
5,调用IPreviewHandler接口的函数,先指定要用于显示Preview的窗体和Rect,把窗体的Handler和Rect作为SetWindow的参数传入。SetWindow会计算出合适的Rect,再调用SetRect设置显示范围,再调用DoPreview函数来得到Preview,最后调用Unload释放对象。
IPreviewHandler接口里面的函数定义的相当合理,你一定要查MSDN,好好看看,把它具体是干什么的搞明白。下文我将会说明COM与C#是如何交互的。
未完待续,敬看下文