前言
最近在开发工作中,接到一个需求,要在Windows资源管理器中显示自定义文件格式的缩略图和预览图,由于没有接触过 Shell 加上网上资源较少,经过一番努力终于实现了需要的功能,遂记录一下。
介绍
根据官方文档介绍,不同的操作系统提供了不同的解决方案。Windows XP 操作系统 通过继承 IExtractImage 或 IExtractImage2 接口实现缩略图功能。Windows Vista 及以上操作系统 ,提供更简单易用的 IThumbnailProivder 接口来代替之前的 IExtractImage 和 IExtractImage2 接口,但后两个接口现在仍然可以使用。
PS:IExtractImage2 继承至 IExtractImage,比后者多了一个 GetDateStamp 方法,通过重写这个方法,允许 Shell 确定缓存的图像是否已经过期。
实现过程
Windows XP
要实现缩略图功能,必须继承IExtractImage 或 IExtractImage2 接口和下列三个接口中的一个并重写GetLocation、Extract 和 Load 接口:
例子
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接口和下列三个接口中的一个并重写 Initialize 和 GetThumbnail 接口:
- 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.
};
说明:主要是重写 Initialize 和 GetThumbnail 两个函数,在 Initialize 完成初始化,在 GetThumbnail 完成自定义文件的解析,base64 的解码,HBITMAP 的构建。