• 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 HKCRCLSID{<CLSID>} key.
        hr = StringCchPrintf(szSubkey, ARRAYSIZE(szSubkey), L"CLSID\%s", szCLSID);
        if (SUCCEEDED(hr))
        {
            hr = SetHKCRRegistryKeyAndValue(szSubkey, NULL, pszFriendlyName, REG_SZ);
            // Create the HKCRCLSID{<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_ROOTWow6432NodeCLSID下面去,64位的才会注册到HKEY_CLASSES_ROOTCLSID下面去。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
  • 相关阅读:
    剑指offer思路总结
    redis为什么设计成单线程
    vscode 常用工具
    redis 数据结构的实现
    记一次rm -rf 的悲伤故事
    netstate端口连接状态
    查询mysql最后更新时间
    boost库安装
    docker镜像不支持常用命令
    消息队列--线程安全
  • 原文地址:https://www.cnblogs.com/csuftzzk/p/thumbnail_handler_development.html
Copyright © 2020-2023  润新知