這是在 GitHub 上有小夥伴報的問題,在 WPF 中,不支持調用 BitmapDecoder.Create 方法,傳入的 FileStream 是配置了 FileOptions.Asynchronous 選項的文件流。本質緣由是 WIC 層不支持,和 WPF 沒有關係git
GitHub 連接: BitmapDecoder.Create does not handle FileStream with FileOptions.Asynchronous · Issue #4355 · dotnet/wpfgithub
現象是傳入 BitmapDecoder.Create 的 FileStream 配置了 FileOptions.Asynchronous 選項,代碼以下windows
using var fs = new FileStream("image.jpg", FileMode.Open, FileAccess.Read, FileShare.Read, 4096, FileOptions.Asynchronous); var decoder = BitmapDecoder.Create(fs, BitmapCreateOptions.PreservePixelFormat, BitmapCacheOption.None);
運行以上代碼將會拋出 ArgumentException
而建立解碼器失敗api
本質緣由是 WIC 層不支持 FileStream 配置了 FileOptions.Asynchronous 選項。在 BitmapDecoder.Create 的底層,調用了 IWICImagingFactory_CreateDecoderFromFileHandle_Proxy function - Win32 apps 方法建立解碼器,代碼以下網絡
public static BitmapDecoder Create( Stream bitmapStream, BitmapCreateOptions createOptions, BitmapCacheOption cacheOption ) { // 忽略代碼 return CreateFromUriOrStream( null, null, bitmapStream, createOptions, cacheOption, null, true ); } internal static BitmapDecoder CreateFromUriOrStream( Uri baseUri, Uri uri, Stream stream, BitmapCreateOptions createOptions, BitmapCacheOption cacheOption, RequestCachePolicy uriCachePolicy, bool insertInDecoderCache ) { // 忽略代碼 decoderHandle = BitmapDecoder.SetupDecoderFromUriOrStream( finalUri, stream, cacheOption, out clsId, out isOriginalWritable, out uriStream, out unmanagedMemoryStream, out safeFilehandle ); // 忽略代碼 } internal static SafeMILHandle SetupDecoderFromUriOrStream( Uri uri, Stream stream, BitmapCacheOption cacheOption, out Guid clsId, out bool isOriginalWritable, out Stream uriStream, out UnmanagedMemoryStream unmanagedMemoryStream, out SafeFileHandle safeFilehandle ) { using (FactoryMaker myFactory = new FactoryMaker()) { HRESULT.Check(UnsafeNativeMethods.WICImagingFactory.CreateDecoderFromFileHandle( myFactory.ImagingFactoryPtr, safeFilehandle, ref vendorMicrosoft, metadataFlags, out decoder )); } }
也就是說最底層調用就是經過 CreateDecoderFromFileHandle 方法建立解碼器,而 CreateDecoderFromFileHandle 的定義以下app
internal static class WICImagingFactory { [DllImport(DllImport.WindowsCodecs, EntryPoint = "IWICImagingFactory_CreateDecoderFromFileHandle_Proxy")] internal static extern int /*HRESULT*/ CreateDecoderFromFileHandle( IntPtr pICodecFactory, Microsoft.Win32.SafeHandles.SafeFileHandle /*ULONG_PTR*/ hFileHandle, ref Guid guidVendor, UInt32 metadataFlags, out IntPtr /* IWICBitmapDecoder */ ppIDecode); }
從以上代碼能夠看到是在 WindowsCodecs.dll
也就是 WIC 層的 IWICImagingFactory_CreateDecoderFromFileHandle_Proxy
拋出失敗的異步
在 FileStream 建立中,若是傳入了 FileOptions.Asynchronous 參數,將會在 Windows 下,調用的是 CreateFileW function (fileapi.h) - Win32 apps 方法,在這個方法裏面將會設置 FILE_FLAG_OVERLAPPED
參數。不過我沒有從網上找到在設置了 FILE_FLAG_OVERLAPPED
參數的文件句柄將在 IWICImagingFactory_CreateDecoderFromFileHandle_Proxy
或 IWICImagingFactory::CreateDecoderFromFileHandle 方法不支持的知識工具
我寫了一個簡單的 demo 程序,用來測試是否 FileOptions.Asynchronous 參數的文件句柄將會在 WIC 層不支持測試
class Program { static void Main(string[] args) { CheckHResult(UnsafeNativeMethods.WICCodec.CreateImagingFactory(UnsafeNativeMethods.WICCodec.WINCODEC_SDK_VERSION, out var pImagingFactory)); using var fs = new FileStream("image.jpg", FileMode.Open, FileAccess.Read, FileShare.Read, 4096, FileOptions.Asynchronous); Guid vendorMicrosoft = new Guid(MILGuidData.GUID_VendorMicrosoft); UInt32 metadataFlags = (uint)WICMetadataCacheOptions.WICMetadataCacheOnDemand; CheckHResult ( UnsafeNativeMethods.WICImagingFactory.CreateDecoderFromFileHandle ( pImagingFactory, fs.SafeFileHandle, ref vendorMicrosoft, metadataFlags, out var decoder ) ); } static void CheckHResult(int hr) { if (hr < 0) { Exception exceptionForHR = Marshal.GetExceptionForHR(hr, (IntPtr)(-1)); throw exceptionForHR; } } }
經過以上代碼能夠看到,若是 FileStream 的建立參數裏面加上了 FileOptions.Asynchronous 那麼將會在 CreateDecoderFromFileHandle 拋出錯誤ui
所以在 WPF 中,調用 BitmapDecoder.Create 方法,傳入的帶 FileOptions.Asynchronous 的 FileStream 拋出錯誤,不是 WPF 層的鍋,而是 WIC 層不支持。在 GitHub 上報告的做者 Nikita Kazmin 給了一個我贊成的建議是 WPF 在 BitmapDecoder.Create 方法裏面應該判斷一下,若是傳入的 FileStream 是異步的,那麼在 WPF 層拋出錯誤,這樣方便開發者瞭解不能這樣使用
我也有另外一個想法,若是是 FileStream 是異步的,不如徹底讀取到內存裏面,這樣開發者也就能夠不關注這部分的邏輯。我當前將此邏輯放入到 WPF 倉庫中,詳細請看 https://github.com/dotnet/wpf/pull/4966
本文全部代碼放在 github 和 gitee 歡迎小夥伴訪問
當前的 WPF 在 https://github.com/dotnet/wpf 徹底開源,使用友好的 MIT 協議,意味着容許任何人任何組織和企業任意處置,包括使用,複製,修改,合併,發表,分發,再受權,或者銷售。在倉庫裏面包含了徹底的構建邏輯,只須要本地的網絡足夠好(由於須要下載一堆構建工具),便可進行本地構建