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


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM