一、引言

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目录下面去。这样就可以正常的注册了。

详细的样例工程已经上传到我的github: https://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. CQOI 2016 不同的最小割

    题目大意:一个无向图,求所有点对不同的最小割种类数 最小割最多有n-1个,这n-1个最小割构成一个最小割树 分治法寻找n-1个最小割.对于当前点集X,任选两点为ST做最小割,然后找出与S相连的所有点和 ...

  2. SQL Server的镜像是基于物理块变化的复制 镜像Failover之后数据的预热问题

    SQL Server的镜像是基于物理块变化的复制 镜像Failover之后数据的预热问题 基于物理块变化的复制,没有并行也是很快的. 逻辑复制的日志是按事务结束的时间排序的,而物理复制是与事务无关的, ...

  3. ABP理论学习之工作单元(Unit of Work)

    返回总目录 本篇目录 公共连接和事务管理方法 ABP中的连接和事务管理 仓储类 应用服务 工作单元 工作单元详解 关闭工作单元 非事务的工作单元 工作单元方法调用其它 工作单元作用域 自动保存 IRe ...

  4. 使用 SailingEase WinForm 框架构建复合式应用程序(插件式应用程序)

    对于一些较小的项目,具备一定经验的开发人员应该能够设计和构建出便于进行维护和扩展的应用程序.但是,随着功能模块数量(以及开发维护这些部件的人员)的不断增加,对项目实施控制的难度开始呈指数级增长. Sa ...

  5. Python读取二进制文件

    import os import sys import socket mypath = sys.argv[1] if not os.path.exists(mypath): print "T ...

  6. Android APK是否需要预解压

    今天在逛论坛的时候,发现有一个朋友问的问题.其主要目的,是想实现 玩家首次进入游戏的时候,或者新安装了版本的时候,对APK进行解压,写入SD卡.这样游戏运行过程中,将不会再从APK中读取资源. 以提高 ...

  7. 60分钟Python快速学习(给发哥一个交代)

    60分钟Python快速学习 之前和同事谈到Python,每次下班后跑步都是在听他说,例如Python属于“胶水语言啦”,属于“解释型语言啦!”,是“面向对象的语言啦!”,另外没有数据类型,逻辑全靠空 ...

  8. JS实战 · 仿css样式选择器

    代码如下: <html> <head>     <meta http-equiv="Content-Type" content="text/ ...

  9. Socket programing(make a chat software) summary 1:How to accsess LAN from WAN

    First we should know some basic conceptions about network: 1.Every PC is supposed to have its own IP ...

  10. php单条件查询,关键字查询

    <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/ ...