实现Windows PSD缩略图Provider时Magick++无法识别任何文件格式导致图片加载失败
我之前也遇到过几乎一模一样的问题——写COM缩略图Provider用Magick++加载图片全失败,命令行工具正常但DLL里就是不行,折腾了好几天才搞定,给你梳理下问题根源和解决办法:
核心问题:Magick++在COM宿主(DllHost.exe)里找不到依赖的解码模块和配置文件
命令行里的magick工具能正常工作,是因为它运行在ImageMagick的安装目录下,能自动找到delegates.xml、各种解码DLL(比如CORE_RL_jpeg_.dll、CORE_RL_psd_.dll)。但你的缩略图Provider是在DllHost.exe中加载的,它的工作目录不是你的DLL所在目录,Magick++初始化时找不到这些关键文件,自然无法识别任何图片格式。
第一步:修正Magick++的初始化逻辑
你现在用GetModuleFileNameA拿自己DLL的路径传给InitializeMagick,这不对——InitializeMagick的参数是用来定位Magick配置文件(delegates.xml等)的根路径,应该传你的DLL所在的目录路径,而不是DLL文件本身的路径:
#include <Shlwapi.h> #pragma comment(lib, "Shlwapi.lib") bool g_magickInitialized = false; CRITICAL_SECTION g_initLock; PSDThumbnailProvider::PSDThumbnailProvider() { InitializeCriticalSectionIfNeeded(&g_initLock); EnterCriticalSection(&g_initLock); if (!g_magickInitialized) { char dllPath[MAX_PATH]; GetModuleFileNameA(g_hModule, dllPath, MAX_PATH); PathRemoveFileSpecA(dllPath); // 去掉DLL文件名,得到所在目录 // 用目录路径初始化,让Magick能找到同目录下的配置和解码DLL Magick::InitializeMagick(dllPath); g_magickInitialized = true; } LeaveCriticalSection(&g_initLock); }
这里加了临界区是为了防止多线程并发初始化(COM组件可能被多个线程实例化,全局变量的判断会有竞态条件)。
第二步:完整部署Magick++的依赖文件
把以下文件和你的PSDThumbnailProvider.dll放在同一个目录下:
- ImageMagick的核心DLL:
CORE_RL_magick_.dll、CORE_RL_Magick++_.dll - 所有需要的解码模块DLL:比如
CORE_RL_jpeg_.dll、CORE_RL_png_.dll、CORE_RL_psd_.dll(根据你要支持的格式选,或者全复制) - 配置文件:
delegates.xml、policy.xml、magick.xml(从ImageMagick安装目录的config文件夹复制)
如果嫌麻烦,也可以直接把ImageMagick安装目录下的所有文件复制到你的DLL目录,但注意要排除不需要的工具(比如magick.exe)。
第三步:修复IStream读取和Blob加载的细节
你从IStream读取数据到buffer的逻辑有个隐患:调用Stat后,流的当前位置可能不是开头,读取前要重置流指针,否则会读空数据:
HRESULT getStreamLength(IStream* pstream, size_t* pLength) { STATSTG stat; HRESULT hr = pstream->Stat(&stat, STATFLAG_DEFAULT); if (SUCCEEDED(hr)) { // 重置流到开头,避免Stat操作改变了位置 LARGE_INTEGER liZero = {0}; hr = pstream->Seek(liZero, STREAM_SEEK_SET, NULL); *pLength = stat.cbSize.QuadPart; } return hr; }
另外,加载Blob后,建议先强制指定图片格式(如果能确定的话),或者让Magick自动检测,但确保Blob数据完整:
Magick::Blob blob(buffer.data(), bytesRead); Magick::Image magickImage; // 可选:如果是PSD可以强制指定格式,避免检测失败 magickImage.read(blob, "PSD");
第四步:解决"unrecognized pixel map"错误
这个错误是因为你直接导出图片的原始像素格式,可能和BGRA不兼容(比如图片是索引色、灰度图或者通道顺序不对)。导出前先把图片转换为TrueColor带Alpha的格式:
const size_t actualWidth = magickImage.columns(); const size_t actualHeight = magickImage.rows(); std::vector<unsigned char> pixels(actualWidth * actualHeight * 4); // 先转换为BGRA格式的TrueColor图片 magickImage.type(Magick::TrueColorAlphaType); magickImage.magick("BGRA"); try { magickImage.write(0, 0, actualWidth, actualHeight, "BGRA", Magick::CharPixel, pixels.data()); } catch (Magick::Exception& ex) { // 现在应该不会再报像素映射错误了 MessageBoxA(NULL, ex.what(), "Debug", MB_OK | MB_ICONERROR); return E_FAIL; }
第五步:控制台测试程序的坑
你写的控制台程序卡住不报错,是因为静态链接了Magick++?或者是因为控制台程序找不到Magick的依赖DLL,导致magickImage.read()卡住(其实是DLL加载失败,但没有抛出异常)。解决方法是把控制台程序的输出目录和你的Provider DLL目录设为同一个,或者把Magick的DLL复制到控制台程序的输出目录。
验证方法:用Process Monitor排查依赖问题
如果还是不行,打开Process Monitor,过滤DllHost.exe的文件操作,看有没有NAME NOT FOUND的记录——如果有Magick相关的DLL或配置文件找不到,就说明部署还没到位,把缺失的文件补上就行。
内容来源于stack exchange

