WPF 已知問題 BitmapDecoder.Create 不支持傳入 Asynchronous 的文件流

這是在 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_ProxyIWICImagingFactory::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

本文全部代碼放在 githubgitee 歡迎小夥伴訪問

當前的 WPF 在 https://github.com/dotnet/wpf 徹底開源,使用友好的 MIT 協議,意味着容許任何人任何組織和企業任意處置,包括使用,複製,修改,合併,發表,分發,再受權,或者銷售。在倉庫裏面包含了徹底的構建邏輯,只須要本地的網絡足夠好(由於須要下載一堆構建工具),便可進行本地構建

相關文章
相關標籤/搜索