Windows下Thumbnail的开发总结

一、引言

       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

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏高性能服务器开发

关于windows完成端口(IOCP)的一些理解(二)

1 不知道你是否记得前面中说过每消耗一个预先准备客户端的socket,就要补上一个。这个代码现在看来就应该放在连接成功事件里面了: DWORD ThreadFu...

46590
来自专栏本立2道生

Win32对话框程序(1)

之前学C语言是一直都是在控制台下面操作的,面对的都是黑框框,严重的打击了学习的兴趣。后来在TC下进行C语言课程设计,做了图形界面编程,但都是点线面画的…… 

19810
来自专栏王大锤

再谈RunLoop

29640
来自专栏王大锤

再谈RunLoop

14030
来自专栏张善友的专栏

重新审视SqlDataReader的使用

      ADO.NET 1.x 利用SqlDataReader读取数据,针对每个结果集需要一个独立的连接。当然,你还必须管理这些连接并且要付出相应的内存和潜...

21190
来自专栏纯洁的微笑

springboot(十一):Spring boot中mongodb的使用

mongodb是最早热门非关系数据库的之一,使用也比较普遍,一般会用做离线数据分析来使用,放到内网的居多。由于很多公司使用了云服务,服务器默认都开放了外网地址,...

37660
来自专栏张善友的专栏

依赖注入容器Autofac

在.NET上现在存在许多的依赖注入容器, 我也在实践中使用过Castle Windsor、StructureMap、Autofac 、Unity。这些容器的简要...

27690
来自专栏张善友的专栏

CLR 4.0 安全模型

在公共语言运行时(CLR)过往的版本中,安全模型一直是最为复杂的模块之一,由于涉及Evidence,CAS策略等机制,难以被用户使用。在Silverlight中...

19780
来自专栏张戈的专栏

移动搜索SEO:网站移动适配之Meta标注、移动跳转终结篇

这些天,在给博客的标签页(tag)添加跳转和 META 动态申明时,居然让我醍醐灌顶,发现之前的动态适配的做法是多么的苦逼和小白! 总结前,先来回顾下小白张戈在...

52060
来自专栏菩提树下的杨过

Flash/Flex学习笔记(10):FMS 3.5之Hello World!

Adobe的FMS真的是一个倍儿牛叉的技术!(至少Silverlight在"实时广播"方面目前还没有任何能超越FMS的迹象) 曾经盛极一时的tudou,ku6,...

21780

扫码关注云+社区

领取腾讯云代金券