一、引言

Windows Thumbnail Handler是Windows平台下用来为关联的文件类型提供内容预览图的一套COM接口。通过实现Thumbnail相关的COM接口,就可以为为自定义的文件格式提供内容预览图。如下图所示:

Thumbnail handler以COM组件的形式注册使用。因此,如果我们想给自己的文件格式开发一个Thumbnail Handler以提供内容预览图,要以COM组件的开发方式进行开发。本人在之前并没有相关的COM开发经验,对于COM组件相关的概念、线程模型及原理也知之甚少。幸好微软为我们提供了一个样板工程(CppShellExtThumbnailHandler)。在此工程的基础上,我们可以进行修改以完成我们自己的功能。

二、实现

在动手修改代码之前,我们不妨先编译运行一下这个工程。这个工程通过读取.recipe格式的文件中的图片内容,来为其生成预览图。这个倒在其次,关键的关键是:工程中的RecipeThumbnailProvider继承自IInitializeWithStream。这个类有一个纯虚函数Initialize,其函数原型为:

IInitializeWithStream : public IUnknown
{
public:
virtual /* [local] */ HRESULT STDMETHODCALLTYPE Initialize(
/* [annotation][in] */
_In_ IStream *pstream,
/* [annotation][in] */
_In_ DWORD grfMode) = 0; };

  其中唯一一个对我们有用的参数是pstream还是IStream*类型的。通过这个接口我们只能获取到关联文件的字节流。这对于小文件而言问题不大,直接把字节流读到内存中来操作也无妨;但如果自定义文件达到数百MB或者数个GB时,这么做肯定是不现实的。这时候我们更希望得到文件的绝对路径。第一想法是看看有没有传递文件路径的接口呢?MSDN中赫然列出了另外一个接口:IInitializeWithFile。这个接口也有一个纯虚函数,其原型为:

IInitializeWithFile : public IUnknown
{
public:
virtual HRESULT STDMETHODCALLTYPE Initialize(
/* [string][in] */ __RPC__in_string LPCWSTR pszFilePath,
/* [in] */ DWORD grfMode) = 0; };

  喜出望外,pszFilePath不正是我们梦寐以求的么!那好啊,基本上来讲,只要把跟IInitializeWithStream相关的部分全部替换掉不就OK了么。 该修改的地方涉及如下:

class RecipeThumbnailProvider :
public IInitializeWithFile,
public IThumbnailProvider
{
public:
// IUnknown
IFACEMETHODIMP QueryInterface(REFIID riid, void **ppv);
IFACEMETHODIMP_(ULONG) AddRef();
IFACEMETHODIMP_(ULONG) Release(); // IInitializeWithFile
IFACEMETHODIMP Initialize(LPCWSTR pfilePath, DWORD grfMode); // IThumbnailProvider
IFACEMETHODIMP GetThumbnail(UINT cx, HBITMAP *phbmp, WTS_ALPHATYPE *pdwAlpha);
...
...
}
// Query to the interface the component supported.
IFACEMETHODIMP RecipeThumbnailProvider::QueryInterface(REFIID riid, void **ppv)
{
static const QITAB qit[] =
{
QITABENT(RecipeThumbnailProvider, IThumbnailProvider),
QITABENT(RecipeThumbnailProvider, IInitializeWithFile),
{ 0 },
};
return QISearch(this, qit, riid, ppv);
}
// Initializes the thumbnail handler with a stream.
IFACEMETHODIMP RecipeThumbnailProvider::Initialize(LPCWSTR pfilePath, DWORD grfMode)
{
LOGINFO(pfilePath);
return 1;
}

  其他的文件都不需要动,编译后注册使用。满以为可以看到日志文件中有文件路径的输出,哪知道什么反应都没有。显然,我们修改之后的Initialize()方法并没有得到调用。网上一搜,不少人也有类似的需求,也有着一样的遭遇,却并没有找到有效的解决方案。怎么解决呢?根据MSDN的解释是,需要在注册表中注册DissableProcessIsolation=1这个项。根据StackOverflow上面的解释是:旧的Windows是将Shell Extension加载到Explorer.exe中运行的,然而这样并不十分安全。于是新的Windows系统将这部分功能独立出来,用Dllhost.exe来加载Shell Extension,脱离与Explorer.exe的关联。这在一定程度降低了Explorer.exe崩溃的概率。相比于IInitializeWithFile, MSDN上也更推崇IInitializeWithStream,以保障系统的安全。

既然如此,还得再修改下程序中操作注册表部分的代码:

HRESULT SetHKCRRegistryKeyAndValue(PCWSTR pszSubKey, PCWSTR pszValueName, PCWSTR pszData, UINT type)
{
HRESULT hr;
HKEY hKey = NULL; // Creates the specified registry key. If the key already exists, the
// function opens it.
hr = HRESULT_FROM_WIN32(RegCreateKeyEx(HKEY_CLASSES_ROOT, pszSubKey, 0,NULL, REG_OPTION_NON_VOLATILE, KEY_WRITE, NULL, &hKey, NULL));
if (SUCCEEDED(hr))
{
if (pszData != NULL)
{
// Set the specified value of the key.
DWORD cbData = lstrlen(pszData) * sizeof(*pszData);
if (type == REG_DWORD)
{
cbData = 4;
}
hr = HRESULT_FROM_WIN32(RegSetValueEx(hKey, pszValueName, 0, type, reinterpret_cast<const BYTE *>(pszData), cbData));
}
RegCloseKey(hKey);
}
return hr;
}

  

HRESULT RegisterInprocServer(PCWSTR pszModule, const CLSID& clsid, PCWSTR pszFriendlyName, PCWSTR pszThreadModel)
{
if (pszModule == NULL || pszThreadModel == NULL)
{
return E_INVALIDARG;
} HRESULT hr;
wchar_t szCLSID[MAX_PATH];
StringFromGUID2(clsid, szCLSID, ARRAYSIZE(szCLSID));
wchar_t szSubkey[MAX_PATH]; // Create the HKCR\CLSID\{<CLSID>} key.
hr = StringCchPrintf(szSubkey, ARRAYSIZE(szSubkey), L"CLSID\\%s", szCLSID);
if (SUCCEEDED(hr))
{
hr = SetHKCRRegistryKeyAndValue(szSubkey, NULL, pszFriendlyName, REG_SZ);
// Create the HKCR\CLSID\{<CLSID>}\InprocServer32 key.
if (SUCCEEDED(hr))
{
WCHAR data[4] = { 0x01, 0x00, 0x00, 0x00 };
SetHKCRRegistryKeyAndValue(szSubkey, L"DisableProcessIsolation", data, REG_DWORD);
hr = StringCchPrintf(szSubkey, ARRAYSIZE(szSubkey), L"CLSID\\%s\\InprocServer32", szCLSID);
if (SUCCEEDED(hr))
{
// Set the default value of the InprocServer32 key to the
// path of the COM module.
hr = SetHKCRRegistryKeyAndValue(szSubkey, NULL, pszModule, REG_SZ);
if (SUCCEEDED(hr))
{
// Set the threading model of the component.
hr = SetHKCRRegistryKeyAndValue(szSubkey, L"ThreadingModel", pszThreadModel, REG_SZ);
}
}
}
} return hr;
}

  注册看看结果:

注册表上是没什么问题了。而我们的文件路径也顺利在日志文件中出现了:

而我们也可以看到自定义文件也能获取到内容预览图了:

三、小结

整个摸索过程中,最痛苦的就是调试方法的盲目性。因为网上没有具体的指导教程,根本不知道这样改是因为原理上不通还是因为操作上的错误,而导致Shell Extension不起作用的。此外,Shell Extension的调试也很困难,只能通过日志文件的输出来判定大致的出错范围。编译出来的COM服务只能通过RegSvr32.exe注册使用:

$ RegSvr32 CppShellExtThumbnailHandler.dll

  虽然RegSvr32.exe中带了一个32,但其实32位和64位的都叫这个名字。在64位系统上,32位的RegSvr32.exe会把服务注册到HKEY_CLASSES_ROOT\Wow6432Node\CLSID下面去,64位的才会注册到HKEY_CLASSES_ROOT\CLSID下面去。RegSvr32.exe会根据编译出来的dll的位数来调用对应版本的RegSvr32.exe

另外,在使用RegSvr32.exe进行注册服务时,如果当前的DLL还依赖其他的DLL,那么会出现注册失败的情况:

这时候要做的就是,把所有依赖的DLL都放到一起,或者放到System32目录下面去。这样就可以正常的注册了。

详细的样例工程已经上传到我的githubhttps://github.com/csuft/WindowsThumbnail

四、参考链接

  1. http://slion.net/view/Dev/MakingOfMs3dThumbnailProvider#Code_Samples
  2. https://social.msdn.microsoft.com/Forums/en-US/80617ead-f9c4-422a-a405-06fd3837f7be/problem-about-iinitializewithfile-ithumbnailprovider?forum=windowssearch
  3. http://stackoverflow.com/questions/24232451/debugging-shell-extensions-in-win-7-and-8-1
  4. https://code.msdn.microsoft.com/windowsapps/CppShellExtThumbnailHandler-32399b35
  5. http://stackoverflow.com/questions/4508012/unable-to-register-dll-using-regsvr32

Windows下Thumbnail的开发总结的更多相关文章

  1. Windows下USB磁盘开发系列二:枚举系统中所有USB设备

    上篇 <Windows下USB磁盘开发系列一:枚举系统中U盘的盘符>介绍了很简单的获取系统U盘盘符的办法,现在介绍下如何枚举系统中所有USB设备(不光是U盘). 主要调用的API如下: 1 ...

  2. Windows下搭建Git开发环境

    Windows下搭建Git开发环境主要有以下三种方法: 1,VS,vs2013和vs2015中已经集成了git插件了 2,msysGit+TortoiseGit 3,msysGit+SourceTre ...

  3. Windows下USB磁盘开发系列三:枚举系统中U盘、并获取其设备信息

    前面我们介绍了枚举系统中的U盘盘符(见<Windows下USB磁盘开发系列一:枚举系统中U盘的盘符>).以及获取USB设备的信息(见<Windows下USB磁盘开发系列二:枚举系统中 ...

  4. [转]windows下安装Object-C开发环境

    本文转自:http://hi.baidu.com/jeremylai/item/f40b9116cb3c5d582b3e22f5 在Windows下搭建Objective C开发环境,需要到GNUst ...

  5. [转]MonkeyRunner在Windows下的Eclipse开发环境搭建步骤(兼解决网上Jython配置出错的问题)

    MonkeyRunner在Windows下的Eclipse开发环境搭建步骤(兼解决网上Jython配置出错的问题)   网上有一篇shangdong_chu网友写的文章介绍如何在Eclipse上配置M ...

  6. 转:Windows下的PHP开发环境搭建——PHP线程安全与非线程安全、Apache版本选择,及详解五种运行模式。

    原文来自于:http://www.ituring.com.cn/article/128439 Windows下的PHP开发环境搭建——PHP线程安全与非线程安全.Apache版本选择,及详解五种运行模 ...

  7. 在windows下进行linux开发:利用Vagrant+virtualbox(ShowDoc与mp3dish的作者)

    1,介绍Vagrant 我们做web开发的时候经常要安装各种本地测试环境,比如apache,php,mysql,redis等等.出于个人使用习惯,可能我们还是比较习惯用windows.虽然说在wind ...

  8. Windows下的PHP开发环境搭建——PHP线程安全与非线程安全、Apache版本选择,及详解五种运行模式。

    今天为在Windows下建立PHP开发环境,在考虑下载何种PHP版本时,遭遇一些让我困惑的情况,为了解决这些困惑,不出意料地牵扯出更多让我困惑的问题. 为了将这些困惑一网打尽,我花了一下午加一晚上的时 ...

  9. windows下python web开发环境的搭建

    windows下python web开发环境: python2.7,django1.5.1,eclipse4.3.2,pydev3.4.1 一. python环境安装 https://www.pyth ...

随机推荐

  1. CSS的定位

        定位的基本思想:允许你定义元素框相对于其正常位置应该出现的位置,或者相对于父元素.另一个元素甚至浏览器窗口本身的位置        一切皆为框   div.h1 或 p 元素常常被称为块级元素 ...

  2. Java Web项目报错java.lang.NullPointerException at org.apache.jsp.front.index_jsp._jspInit(index_jsp.java:30)

    环境:myeclipse+tomcat6+jdk6 今天搭建了一个Java Web项目,访问index.jsp时报如下错误: 严重: Servlet.service() for servlet jsp ...

  3. CSS 使用母版页的内容页如何调用css和javascript

    方案一: 把所有的css样式和javascript函数放到母版页的<head></head>中,我觉得这样做的弊端就是导致母版页的<head></head&g ...

  4. C ReadProcessMemory

    ReadProcessMemory函数用于读取其他进程的数据. BOOL STDCALL ReadProcessMemory ( HANDLE hProcess, LPCVOID lpBaseAddr ...

  5. 让Response.Redirect页面重定向更有效率

    用 Redirect 方法可将浏览器重定向到另一个 URL,而不是将内容发送给用户. 这里有一篇文章介绍使用Redirect<Using Response.Redirect Effectivel ...

  6. 安卓动态调试七种武器之离别钩 – Hooking(上)

    安卓动态调试七种武器之离别钩 – Hooking(上) 作者:蒸米@阿里聚安全 0x00 序 随着移动安全越来越火,各种调试工具也都层出不穷,但因为环境和需求的不同,并没有工具是万能的.另外工具是死的 ...

  7. 程序员的经济学系列——你不可不知的生存智慧——第一篇:小X是要成为IT精英的男人!

    21世纪,不懂经济学就是耍流氓!如何才能生活得更好?作为程序员你一定也思考过这个问题.今天我们就来从经济学中寻找这问题的答案吧! 一·PPF与机会成本 1.PPF综述 首先为大家介绍第一个最简单的经济 ...

  8. ASP.NET MVC 5 入门指南汇总

    经过前一段时间的翻译和编辑,我们陆续发出12篇ASP.NET MVC 5的入门文章.其中大部分翻译自ASP.NET MVC 5 官方教程,由于本系列文章言简意赅,篇幅适中,从一个web网站示例开始讲解 ...

  9. 每周一书-《鸟哥的Linux私房菜》获奖公布

    <鸟哥的Linux私房菜>一书的赠书活动时间为2016年10月19日到10月31日, 也就是今天结束. 首先要感谢QQ号为:1084830483(路在远方),来自哈尔滨工程大学的同学赠送给 ...

  10. xamarin UWP自定义圆角按钮

    uwp自带的button本身不支持圆角属性,所以要通过自定义控件实现. 通过设置Button的Background=“{x:Null}”设置为Null使背景为空,再设置Button.Content中的 ...