Windows资源管理器中自定义文件格式显示缩略图和预览图功能(C++)

前言

  最近在开发工作中,接到一个需求,要在Windows资源管理器中显示自定义文件格式缩略图预览图,由于没有接触过 Shell 加上网上资源较少,经过一番努力终于实现了需要的功能,遂记录一下。

介绍

  根据官方文档介绍,不同的操作系统提供了不同的解决方案。Windows XP 操作系统 通过继承 IExtractImageIExtractImage2 接口实现缩略图功能。Windows Vista 及以上操作系统 ,提供更简单易用的 IThumbnailProivder 接口来代替之前的 IExtractImageIExtractImage2 接口,但后两个接口现在仍然可以使用。
  PSIExtractImage2 继承至 IExtractImage,比后者多了一个 GetDateStamp 方法,通过重写这个方法,允许 Shell 确定缓存的图像是否已经过期。

实现过程

Windows XP

  要实现缩略图功能,必须继承IExtractImageIExtractImage2 接口和下列三个接口中的一个并重写GetLocationExtractLoad 接口:

例子

HRESULT CTPeExtract::Load(LPCOLESTR pszFileName, DWORD dwMode)
{   
    USES_CONVERSION;
    _tcscpy_s(m_szFileName, OLE2T((WCHAR*)pszFileName));

    return S_OK;
};

说明:重写Load函数,在函数中保存自定义文件的路径。

HRESULT CTPeExtract::GetLocation(LPWSTR pszPathBuffer,
    DWORD cchMax, DWORD *pdwPriority,
    const SIZE *prgSize, DWORD dwRecClrDepth,
    DWORD *pdwFlags)
{
    if (*pdwFlags & IEIFLAG_ASYNC)
    {
        return E_PENDING;
    }

    return NOERROR;
}

说明:重写GetLocation函数。

HRESULT CTPeExtract::Extract(HBITMAP* phBmpThumbnail)
{
    /// 解析自定义文件
    tinyxml2::XMLDocument doc;
    doc.LoadFile(m_szFileName);
    XMLElement *pRoot = doc.RootElement();
    const char* cSource = pRoot->FirstChildElement("Attachments")
        ->FirstChildElement("Picture")->Attribute("source");

    /// base64解码
    int datalen(0);
    DWORD dwritelen(0);
    std::string strdcode = CBase64::Base64Decode(cSource, strlen(cSource), datalen);

    /// 字符串转换成字节流
    int iLength = strdcode.length();
    BYTE* pBuffer = new BYTE[iLength + 1];
    for (int i = 0; i < iLength; ++i)
    {
        pBuffer[i] = strdcode[i];
    }

    /// 字节流转换成HBITMAP
    *phBmpThumbnail = ConvertDibToHBitmap(pBuffer);

    delete[] pBuffer;

    return NOERROR;
}

说明:重写Extract函数,在函数中实现自定义文件的解析(主要是解析出自定义文件中缩略图的base64编码),根据缩略图的base64解码构建字节流,然后将字节流转换成HBITMAP

HRESULT CTPeExtract::GetDateStamp(FILETIME *pDateStamp)
{
    FILETIME ftCreationTime, ftLastAccessTime, ftLastWriteTime;

    HANDLE hFile = CreateFile(m_szFileName, GENERIC_READ, FILE_SHARE_READ, NULL,
        OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
    if (!hFile)
    {
        return E_FAIL;
    }

    GetFileTime(hFile, &ftCreationTime, &ftLastAccessTime, &ftLastWriteTime);
    CloseHandle(hFile);

    *pDateStamp = ftLastWriteTime;

    return NOERROR;
}

说明:重写GetDateStamp函数。

HBITMAP CTPeExtract::ConvertDibToHBitmap(void* bmpData)
{
    HBITMAP hBitmap = NULL;
    BOOL success = FALSE;

    LPBITMAPFILEHEADER bfh = (LPBITMAPFILEHEADER)bmpData;
    LPBITMAPINFOHEADER bih = (LPBITMAPINFOHEADER)(bfh + 1);
    void* pixels = (char*)(bih + 1); // NOTE: Assumes no color table (i.e., bpp >= 24) 

    HDC hdc = GetDC(NULL);
    if (NULL == hdc)
    {
        return NULL;
    }

    hBitmap = CreateCompatibleBitmap(hdc, bih->biWidth, bih->biHeight);
    if (NULL == hBitmap)
    {
        return NULL;
    }

    HDC hdcMem = CreateCompatibleDC(hdc);
    if (NULL == hdcMem)
    {
        return NULL;
    }

    HBITMAP hOldBitmap = (HBITMAP)SelectObject(hdcMem, hBitmap);
    if (StretchDIBits(hdcMem, 0, 0, bih->biWidth, bih->biHeight,
        0, 0, bih->biWidth, bih->biHeight, pixels,
        (LPBITMAPINFO)bih, DIB_RGB_COLORS, SRCCOPY) > 0)
    {
        success = TRUE;
    }

    SelectObject(hdcMem, hOldBitmap);
    DeleteDC(hdcMem);

    ReleaseDC(NULL, hdc);
    
    if (!success && hBitmap != NULL)
    {
        DeleteObject(hBitmap);
        hBitmap = NULL;
    }

    return hBitmap;
}

说明:位图字节流转HBITMAP函数。

Windows Vista 及以上版本

  要实现缩略图功能,必须继承IThumbnailProivder接口和下列三个接口中的一个并重写 InitializeGetThumbnail 接口:

  • IInitializeWithStream官方推荐继承这个接口,增强安全性和稳定性
  • IInitializeWithItem
  • IInitializeWithFile
    注意:如果不是继承自 IInitializeWithStream 接口,则必须设置下列的注册表值:
    HKEY_CLASSES_ROOT
        CLSID
            {The CLSID of your thumbnail handler}
                DisableProcessIsolation = 1
    

例子(详细例子见参考中的官方例子)

class CTestThumbnail : public IInitializeWithStream,
    public IThumbnailProvider
{
public:
    CTestThumbnail() : _cRef(1), _pStream(NULL)
    {
    }

    virtual ~CTestThumbnail()
    {
        if (_pStream)
        {
            _pStream->Release();
        }
    }

    // IUnknown
    IFACEMETHODIMP QueryInterface(REFIID riid, void **ppv)
    {
        static const QITAB qit[] =
        {
            QITABENT(CTestThumbnail, IInitializeWithStream),
            QITABENT(CTestThumbnail, IThumbnailProvider),
            { 0 },
        };
        return QISearch(this, qit, riid, ppv);
    }

    IFACEMETHODIMP_(ULONG) AddRef()
    {
        return InterlockedIncrement(&_cRef);
    }

    IFACEMETHODIMP_(ULONG) Release()
    {
        ULONG cRef = InterlockedDecrement(&_cRef);
        if (!cRef)
        {
            delete this;
        }
        return cRef;
    }

    // IInitializeWithStream
    IFACEMETHODIMP Initialize(IStream *pStream, DWORD grfMode);

    // IThumbnailProvider
    IFACEMETHODIMP GetThumbnail(UINT cx, HBITMAP *phbmp, WTS_ALPHATYPE *pdwAlpha);

private:
    HRESULT _LoadXMLDocument(IXMLDOMDocument **ppXMLDoc);
    HRESULT _GetBase64EncodedImageString(UINT cx, PWSTR *ppszResult);
    HRESULT _GetStreamFromString(PCWSTR pszImageName, IStream **ppStream);

    long _cRef;
    IStream *_pStream;     // provided during initialization.
};

说明:主要是重写 InitializeGetThumbnail 两个函数,在 Initialize 完成初始化,在 GetThumbnail 完成自定义文件的解析,base64 的解码,HBITMAP 的构建。

参考

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,686评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,668评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 158,160评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,736评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,847评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,043评论 1 291
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,129评论 3 410
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,872评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,318评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,645评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,777评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,470评论 4 333
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,126评论 3 317
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,861评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,095评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,589评论 2 362
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,687评论 2 351