如何获取 CFAPI 同步根目录中已水合文件的缩略图?
优先通过 IShellItemImageFactory::GetImage 或 IThumbnailCache::GetThumbnail 获取并复用系统缩略图。Explorer 已显示预览,并不保证另一进程在当前请求条件下也能取得该图像。直接调用 IThumbnailProvider 可以生成位图,但不会自动将其写入 Shell 缓存。
现有示例还需要修正一处 COM 方法声明和一个枚举值。这两处问题不能直接解释 CFAPI 目录内的所有失败;0x8004B207 的具体触发原因仍需结合存储提供商注册、文件状态和调用日志确认。
先修正接口声明和枚举值
如果要把原生 HRESULT 作为 C# 返回值自行检查,GetImage 应声明 [PreserveSig]。否则,COM 互操作默认会转换方法签名,并将失败的 HRESULT 转为异常。SHCreateItemFromParsingName 上的 PreserveSig = false 只影响该 P/Invoke,不会替 GetImage 保留返回值。参见 .NET 的 PreserveSig 说明。
C# 中的 10 是十进制,而 SIIGBF_INCACHEONLY 的正确值是 0x10,即十进制 16。原来的调用没有使用这个标志,因此它不能直接解释该调用的失败,但会影响后续缓存测试。
将原接口和枚举替换为:
[ComImport]
[Guid("bcc18b79-ba16-442f-80c4-8a59c30c463b")]
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
public interface IShellItemImageFactory
{
[PreserveSig]
int GetImage(
[In] SIZE size,
[In] SIIGBF flags,
out IntPtr phbm);
}
[Flags]
public enum SIIGBF : uint
{
SIIGBF_RESIZETOFIT = 0x00,
SIIGBF_BIGGERSIZEOK = 0x01,
SIIGBF_MEMORYONLY = 0x02,
SIIGBF_ICONONLY = 0x04,
SIIGBF_THUMBNAILONLY = 0x08,
SIIGBF_INCACHEONLY = 0x10
}
这里的 SIZE 应与原生结构布局一致,由两个 32 位整数表示宽和高。
如何读取 Explorer 可能正在使用的缓存?
先使用 SIIGBF_THUMBNAILONLY | SIIGBF_INCACHEONLY,将缓存读取和提取分开测试。前者禁止图标回退,后者禁止重新提取;缓存中没有可用缩略图时,需要处理失败情况。SIIGBF_MEMORYONLY 不能替代缩略图缓存查询。具体语义见 GetImage 文档。
下面的方法沿用问题中的 SHCreateItemFromParsingName、DeleteObject 和 SIZE 定义,并使用修正后的接口:
public Bitmap GetThumbnail(string parsingName, bool cacheOnly)
{
var factory = SHCreateItemFromParsingName(
parsingName,
IntPtr.Zero,
typeof(IShellItemImageFactory).GUID);
IntPtr hBitmap = IntPtr.Zero;
try
{
var flags = SIIGBF.SIIGBF_BIGGERSIZEOK
| SIIGBF.SIIGBF_THUMBNAILONLY;
if (cacheOnly)
flags |= SIIGBF.SIIGBF_INCACHEONLY;
int hr = factory.GetImage(
new SIZE(256, 256), flags, out hBitmap);
if (hr < 0)
throw new COMException(
$"GetImage failed: 0x{unchecked((uint)hr):X8}", hr);
if (hBitmap == IntPtr.Zero)
throw new InvalidOperationException(
"GetImage succeeded without an HBITMAP.");
return Image.FromHbitmap(hBitmap);
}
finally
{
if (hBitmap != IntPtr.Zero)
DeleteObject(hBitmap);
if (factory != null)
Marshal.ReleaseComObject(factory);
}
}
先用 cacheOnly: true 查询。没有可用缓存时,再在后台按正确的 COM 线程模型调用 cacheOnly: false。这次调用允许 Shell 提取缩略图,但仍可能遇到存储提供商错误。示例只释放本次创建且未共享的 COM 引用;调用方使用完返回的 Bitmap 后,应调用 Dispose()。
这段修改有助于获得可信的错误码,并区分缓存读取与提取,但不保证消除 0x8004B207。
如何定位 CFAPI 目录内的失败?
WTS_E_NOSTORAGEPROVIDERTHUMBNAILHANDLER 这个错误名称提示应检查存储提供商缩略图处理路径。Windows 对云文件提供单独的 ThumbnailProviderHandler 注册机制,不能只检查扩展名对应的处理器。参见 CloudFiles 扩展声明。
建议对同一个失败文件做以下对照:
- 分别记录仅查询缓存和允许提取时的完整
HRESULT,同时记录路径、请求尺寸和标志。 - 将已确认可完整读取的本地内容复制到同步根目录之外,用相同代码请求缩略图。若副本成功,优先调查云文件身份、状态和处理器路由。
- 检查自己的存储提供商缩略图处理器是否正确注册,以及失败请求是否实际进入该处理器。
- 对比调用进程的用户身份、位数、COM 初始化方式,以及本地编辑前后的文件状态和修改时间。
现有信息不足以认定必须新增某个处理器、修改某项 hydration 策略,或将问题归因于某个 Windows 版本。
Explorer 可能正在显示之前取得的图像,也可能使用不同尺寸的缓存。它能显示预览,只能说明它曾取得可展示的图像,不能证明当前请求必然命中缓存,也不能证明预览已反映最新本地修改。
可以直接调用 IThumbnailProvider 吗?
可以按处理器支持的初始化接口调用,但需要自行处理兼容性和故障隔离。仅按扩展名读取注册表并创建 CLSID,不能保证复现 Shell 对具体文件的处理器选择。
直接在应用进程中加载第三方处理器时,处理器阻塞或崩溃会影响应用。Shell 缩略图处理通常有进程隔离机制;绕过 Shell 时,需要自行考虑这种保护。参见 Thumbnail Handlers 的进程说明。
如果直接提取只是为了让自己的 UI 及时显示本地修改,可以把结果保存在应用缓存中,并按文件修改和请求尺寸更新。一次 IThumbnailProvider::GetThumbnail 成功,并不代表 Shell 缓存已更新。
如何让不同进程复用 Shell 缓存?
使用 IThumbnailCache::GetThumbnail 可以控制缓存与提取行为:
| 标志 | 用途 |
|---|---|
WTS_INCACHEONLY | 仅返回已缓存的缩略图 |
WTS_EXTRACT | 缓存不可用时提取,并缓存提取结果 |
WTS_FORCEEXTRACTION | 忽略缓存,重新提取 |
WTS_EXTRACTDONOTCACHE | 提取,但不缓存结果 |
GetImage 也使用这套缓存。因此,换成 IThumbnailCache 能提供更明确的控制,但不能保证绕过同一个存储提供商处理器错误。参见 IThumbnailCache::GetThumbnail 和 WTS_FLAGS。
IThumbnailCache 没有接收任意 HBITMAP 并将其写入缓存的方法。要让系统管理共享缓存,应让系统缓存接口调用合适的缩略图处理器,而不是先手工生成位图,再尝试把它写入缓存。该接口面向跨应用共享,见 IThumbnailCache 接口说明。
对于本地修改,文件修改时间变化可能触发重新提取,不同请求尺寸也可能需要不同缓存结果。因此,可以减少重复生成,但不能承诺一个文件永久只生成一次。若预览必须反映尚未上传的本地修改,还应验证处理器实际读取的是当前本地内容。
备注:内容仅供参考。
