Skip to content

BitmapDecoder.Create does not handle FileStream with FileOptions.Asynchronous #4355

Closed

Description

@vonzshik
  • .NET Core Version: (e.g. 3.0 Preview1, or daily build number, use dotnet --info)
    5.0.201
  • Windows version: (winver)
    19042.867
  • Does the bug reproduce also in WPF for .NET Framework 4.8?: Yes/No
    Yes.

Problem description:

BitmapDecoder.Create throws ArgumentException ("Value does not fall within the expected range.") whenever the stream argument is FileStream with FileOptions.Asynchronous.

Actual behavior:

at System.Windows.Media.Imaging.BitmapDecoder.SetupDecoderFromUriOrStream(Uri uri, Stream stream, BitmapCacheOption cacheOption, Guid& clsId, Boolean& isOriginalWritable, Stream& uriStream, UnmanagedMemoryStream& unmanagedMemoryStream, SafeFileHandle& safeFilehandle)
  at System.Windows.Media.Imaging.BitmapDecoder.CreateFromUriOrStream(Uri baseUri, Uri uri, Stream stream, BitmapCreateOptions createOptions, BitmapCacheOption cacheOption, RequestCachePolicy uriCachePolicy, Boolean insertInDecoderCache)
  at System.Windows.Media.Imaging.BitmapDecoder.Create(Stream bitmapStream, BitmapCreateOptions createOptions, BitmapCacheOption cacheOption)

Expected behavior:

BitmapDecoder.Create shouldn't throw an exception.

Minimal repro:

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);

Activity

  1. added this to the Future milestone on Apr 5, 2021
  2. lindexi commented on May 14, 2021

    @lindexi
    Member

    The BitmapDecoder.Create will call the IWICImagingFactory_CreateDecoderFromFileHandle_Proxy which in WIC to create the decoder. But the IWICImagingFactory_CreateDecoderFromFileHandle_Proxy can not receive the Asynchronous SafeFilehandle.

    IWICImagingFactory_CreateDecoderFromFileHandle_Proxy function - Win32 apps | Microsoft Docs

  3. vonzshik commented on May 14, 2021

    @vonzshik
    Author

    @lindexi thank you for taking a look. I assume there is no API for an async SafeFileHandle, yes?

    The fact that I was able to get around this issue by making a proxy stream, which wraps the original FileStream (and the API you've mentioned does confirm my suspicions), makes me think that BitmapDecoder.Create does check whether a provided stream is indeed a FileStream, in which case it uses a different set of API (I guess as a sort of optimization?), unlike for other cases.

    If so, maybe it's a good idea to disable this behavior whenever FileStream.IsAsync is true? Otherwise, I think making the exception more verbose will surely help solving this problem for other users.

  4. lindexi commented on May 14, 2021

    @lindexi
    Member

    @vonzshik I agree with you.

  5. lindexi commented on May 15, 2021

    @lindexi
    Member

    I write a demo to show the IWICImagingFactory_CreateDecoderFromFileHandle_Proxy do not support the Asynchronous file stream.

        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;
                }
            }
        }

    See https://github.com/lindexi/lindexi_gd/tree/78c73fe25229f0b50992102e59c01cd535e60c31/JemlemlacuLemjakarbabo

  6. lindexi commented on Aug 2, 2021

    @lindexi
    Member

    @vonzshik I fixed it, see #4966

    I follow your opinion, when I judge this file is asynchronous, I read the file into memory.

    I wrote a demo code, and it work well. See https://github.com/lindexi/lindexi_gd/tree/20b78c5547b2ecacdd244a438ef58518469a91d7/GeafakaijawkeheahemLejeehocear

    You can test the demo by publish the code with self contained.

    dotnet publish --self-contained -r win-x86
    
  7. dipeshmsft commented on Aug 10, 2022

    @dipeshmsft
    Member

    We have taken this PR for the current Community Test Pass. Thanks for the contribution.

  8. ghost removed this from the Future milestone on Aug 12, 2022
  9. ghost locked as resolved and limited conversation to collaborators on Sep 11, 2022
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions