一、引言

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. java身份证验证

    import java.text.ParseException; import java.text.SimpleDateFormat; import java.util.Calendar; impor ...

  2. (一)Linux相关内容的简介

    1.Linux内核官网:www.kernel.org 2.Linux主要发行版本: (1)redhat  服务器中常见(部分功能收费); (2)Ubuntu  图形界面较好,但是服务器中很少用: (3 ...

  3. .Net 跨平台可移植类库PCL可用于任何平台包括Mono

    Microsoft 在 .NET Framework 4 中添加了一个名为可移植类库 (PCL) 的新功能. 利用 PCL,您可以有选择性地面向 .NET Framework.Silverlight ...

  4. 可在广域网部署运行的QQ高仿版 -- GGTalk总览

     (最新版本:V5.5,2016.12.06  增加对MySQL数据库的支持.) (android移动端:2015.09.24 最初发布 ,2016.11.25 最后更新) GGTalk(简称GG)是 ...

  5. Linux环境下部署完JDK后运行一个简单的Java程序

    前言 前一篇文章详细讲解了如何在Windows环境下安装虚拟机+Linux系统,并且成功部署了JDK. 不过部署完JDK之后,我们判断部署是否成功的依据是看"java -version&qu ...

  6. Python黑帽编程 3.1 ARP欺骗

    Python灰帽编程 3.1 ARP欺骗 ARP欺骗是一种在局域网中常用的攻击手段,目的是让局域网中指定的(或全部)的目标机器的数据包都通过攻击者主机进行转发,是实现中间人攻击的常用手段,从而实现数据 ...

  7. [.net 面向对象程序设计进阶] (26) 团队开发利器(五)分布式版本控制系统Git——图形化Git客户端工具TortoiseGit

    [.net 面向对象程序设计进阶] (26) 团队开发利器(五)分布式版本控制系统Git——图形化Git客户端工具TortoiseGit 读前必备: 接上篇: 分布式版本控制系统Git——使用GitS ...

  8. css水平居中那点事

    昨晚深夜写了css垂直居中那点事,今晚该写他的兄弟篇:css水平居中那点事了..…^^ 其实本来这两个可以连在一起写,可是为了不要搞混,为了让思路更清晰,最后决定还是分开来些比较好...这样以后也有利 ...

  9. Android开发学习之路-该怎么学Android(Service和Activity通信为例)

    在大部分地方,比如书本或者学校和培训机构,教学Android的方式都基本类似,就是告诉先上原理方法,然后对着代码讲一下. 但是,这往往不是一个很好的方法,为什么? ① 学生要掌握这个方法的用途,只能通 ...

  10. Nginx重写

    一.location匹配 1.分类:(1)正则location:~,~*(2)普通location:=,^~,@,无2.匹配规则:(1) =    精确匹配.如果找到,停止搜索(2) ^~    普通 ...