Issue with reading file on Windows Phone 8 SD card

2019-05-05 07:07发布

问题:

I have problems reads the file on Windows Phone SD card. I get the valid Stream object using ExternalStorageFile.OpenForReadAsync. However any seek operation is ignored and position is not moved although the stream CanSeek property is true;

    private async void ReadFileOnSDCard(ExternalStorageFile file)
    {
        Stream stream = await file.OpenForReadAsync();
          using (stream)
          {
                 long curPos= stream.Seek(100, SeekOrigin.Begin);
                 long pos = stream.Position;

// both curPos and pos are 0.

回答1:

I was fighting with the very same problem. Seek is indeed broken in Microsoft.Phone.Storage.NativeFileStream, which is the type of stream for files on SD card. In the end I looked at the class with ILspy, and this is it:

public override long Seek(long offset, System.IO.SeekOrigin origin)
{
   ...
   uint num = (uint)((ulong)(offset & -4294967296L) >> 32);
   uint num2 = (uint)(offset & (long)((ulong)-1));
   uint num3 = NativeFileStream.SetFilePointer(this.m_handle, num, ref num2, 
   ...
}

And function SetFilePointer: http://msdn.microsoft.com/en-us/library/windows/desktop/aa365541%28v=vs.85%29.aspx

To make seek work, offset value should be in higher 32bits of the long.



回答2:

I guess you're reading a file with extension that is reserved by Windows Phone. Unfortunately, you can not read them from SD Card on Windows Phone. If you're reading a file falls into these reserved extensions, Windows Phone just simply ignore them.

Assume you're not reading those reserved extensions, you can follow these two steps to fix your problem.

1/ First check to see if you've already added <Capability Name="ID_CAP_REMOVABLE_STORAGE" /> to your WMAppManifest.xml

2/ Register for a file association extension if you didn't, for example .gpx

<FileTypeAssociation TaskID="_default" Name="GPX" NavUriFragment="fileToken=%s">
  <Logos>
    <Logo Size="small" IsRelative="true">Assets/Route_Mapper_Logo33x33.png</Logo>
    <Logo Size="medium" IsRelative="true">Assets/Route_Mapper_Logo69x69.png</Logo>
    <Logo Size="large" IsRelative="true">Assets/Route_Mapper_Logo176x176.png</Logo>
  </Logos>
  <SupportedFileTypes>
    <FileType ContentType="application/gpx">.gpx</FileType>
  </SupportedFileTypes>
</FileTypeAssociation>


回答3:

If the file is small then you can simply copy the stream to a MemoryStream. I have tested this and it works:

Stream s = await file.OpenForReadAsync();
MemoryStream ms = new MemoryStream();
s.CopyTo(ms);

However, if the file is too large you'll run in to memory issues so the following stream wrapper class can be used to correct Microsoft's bug (though in future versions of Windows Phone you'll need to disable this fix once the bug has been fixed):

using System;
using System.IO;

namespace WindowsPhoneBugFix
{
    /// <summary>
    /// Stream wrapper to circumnavigate buggy Stream reading of stream returned by ExternalStorageFile.OpenForReadAsync()
    /// </summary>
    public sealed class ExternalStorageFileWrapper : Stream
    {
        private Stream _stream; // Underlying stream

        public ExternalStorageFileWrapper(Stream stream)
        {
            if (stream == null)
                throw new ArgumentNullException("stream");

            _stream = stream;
        }

        // Workaround described here - http://stackoverflow.com/a/21538189/250254
        public override long Seek(long offset, SeekOrigin origin)
        {
            ulong uoffset = (ulong)offset;
            ulong fix = ((uoffset & 0xffffffffL) << 32) | ((uoffset & 0xffffffff00000000L) >> 32);
            return _stream.Seek((long)fix, origin);
        }

        public override bool CanRead
        {
            get { return _stream.CanRead; }
        }

        public override bool CanSeek
        {
            get { return _stream.CanSeek; }
        }

        public override bool CanWrite
        {
            get { return _stream.CanWrite; }
        }

        public override void Flush()
        {
            _stream.Flush();
        }

        public override long Length
        {
            get { return _stream.Length; }
        }

        public override long Position
        {
            get
            {
                return _stream.Position;
            }
            set
            {
                _stream.Position = value;
            }
        }

        public override int Read(byte[] buffer, int offset, int count)
        {
            return _stream.Read(buffer, offset, count);
        }

        public override void SetLength(long value)
        {
            _stream.SetLength(value);
        }

        public override void Write(byte[] buffer, int offset, int count)
        {
            _stream.Write(buffer, offset, count);
        }
    }
}

Code is available here to drop in to your project: https://github.com/gavinharriss/ExternalStorageFileWrapper-wp8