有没有一种方法可以将 System.IO.Stream 转换为 Windows.Storage.Streams.IRandomAccessStream?

53
在Windows 8中,我想将MemoryStream的内容传递给一个接受类型为Windows.Storage.Streams.IRandomAccessStream的参数的类。有没有办法将这个MemoryStream转换为IRandomAccessStream?

难道没有扩展方法或类似.AsBuffer之类的东西吗? - sharp johnny
7个回答

98

要使用扩展:必须添加“using System.IO”

在Windows8中, .NET 和 WinRT 类型通常在幕后自动转换为兼容类型,因此您不必担心它。

但是,对于流,有一些帮助方法可在 WinRT 流和 .NET 流之间进行转换:

将 WinRT 流转换为 .NET 流:

InMemoryRandomAccessStream win8Stream = GetData(); // Get a data stream from somewhere.
System.IO.Stream inputStream = win8Stream.AsStream()

将 .NET 流转换为 WinRT 流:

Windows.Storage.Streams.IInputStream inStream = stream.AsInputStream();
Windows.Storage.Streams.IOutputStream outStream = stream.AsOutputStream();

更新:2013年9月1日

不要说Microsoft不听取开发者社区的声音;)

.NET FX 4.5.1的公告中,Microsoft表示:

很多人一直想找到一种将.NET Stream转换为Windows Runtime IRandomAccessStream的方法。我们只需称之为AsRandomAccessStream扩展方法。虽然我们无法将此功能添加到Windows 8中,但它是Windows 8.1 Preview的首批新增功能之一。

现在你可以编写以下代码,使用HttpClient下载图像,将其加载到BitmapImage中,然后将其设置为Xaml Image控件的源。

    //access image via networking i/o
    var imageUrl = "http://www.microsoft.com/global/en-us/news/publishingimages/logos/MSFT_logo_Web.jpg";
    var client = new HttpClient();
    Stream stream = await client.GetStreamAsync(imageUrl);
    var memStream = new MemoryStream();
    await stream.CopyToAsync(memStream);
    memStream.Position = 0;
    var bitmap = new BitmapImage();
    bitmap.SetSource(memStream.AsRandomAccessStream());
    image.Source = bitmap;

希望有所帮助。


7
使用这些扩展方法,需添加“using System.IO”命名空间。 - Whyllee
2
这应该是答案。非常简单明了。使用扩展方法将WinRT流转换为.Net流。 - Matthew Pitts
嗯,第一段代码片段无法编译,但我明白了。 - vidstige
谢谢留言。已修复。 - Rich Turner
更新了所选答案--这是更为现代化的方法,比预发布版本提供了更优雅的实现方式。 - bbosak
我无法形容这有多么帮助我!我像个疯子一样搜索了很久,试图找到将流转换为IInputStream的方法。 - drl

7
我找到了一种更优雅的解决方案:

public static class MicrosoftStreamExtensions
{
    public static IRandomAccessStream AsRandomAccessStream(this Stream stream)
    {
        return new RandomStream(stream);
    }

}

class RandomStream : IRandomAccessStream
{
    Stream internstream;

    public RandomStream(Stream underlyingstream)
    {
        internstream = underlyingstream;
    }

    public IInputStream GetInputStreamAt(ulong position)
    {
        //THANKS Microsoft! This is GREATLY appreciated!
        internstream.Position = (long)position;
        return internstream.AsInputStream();
    }

    public IOutputStream GetOutputStreamAt(ulong position)
    {
        internstream.Position = (long)position;
        return internstream.AsOutputStream();
    }

    public ulong Size
    {
        get
        {
            return (ulong)internstream.Length;
        }
        set
        {
            internstream.SetLength((long)value);
        }
    }

    public bool CanRead
    {
        get { return this.internstream.CanRead; }
    }

    public bool CanWrite
    {
        get { return this.internstream.CanWrite; }
    }

    public IRandomAccessStream CloneStream()
    {
        throw new NotSupportedException();
    }

    public ulong Position
    {
        get { return (ulong)this.internstream.Position; }
    }

    public void Seek(ulong position)
    {
        this.internstream.Seek((long)position, SeekOrigin.Begin);
    }

    public void Dispose()
    {
        this.internstream.Dispose();
    }

    public Windows.Foundation.IAsyncOperationWithProgress ReadAsync(IBuffer buffer, uint count, InputStreamOptions options)
    {
        return this.GetInputStreamAt(this.Position).ReadAsync(buffer, count, options);
    }

    public Windows.Foundation.IAsyncOperation FlushAsync()
    {
        return this.GetOutputStreamAt(this.Position).FlushAsync();
    }

    public Windows.Foundation.IAsyncOperationWithProgress WriteAsync(IBuffer buffer)
    {
        return this.GetOutputStreamAt(this.Position).WriteAsync(buffer);
    }
}

2
这个解决方案有两个问题。1)如果有人调用GetInputStreamAt(0),然后是GetOutputStreamAt(1),输入流的位置也会改变为1。2)它接受任何流,因此CanSeek属性可能等于false。在这种情况下将抛出NotSupportedException异常。复制流对于大流来说效率较低,但不应该有这些问题。 - Roman Boiko
@RomanBoiko 承认,这不是任何类型流的通用解决方案,但它适用于许多类型的应用程序,这些应用程序仍将使用新类型的流,就像旧方法一样(例如,仅从流中读取数据并将其传递到 WinRT 组件,然后摆脱 IRandomAccessStream。我还应指出,IRandomAccessStream 不打算用作 System.IO.Stream,反之亦然,但当您想要将 IRandomAccessStream 用作 Stream 时,这很有用。 - bbosak
当你确切知道没有人会同时调用GetInputStreamAt(someNumber)GetOutputStreamAt(otherNumber)并在同一时间段内使用它们时,这是许多问题的一个不错且简单的解决方案。可以通过存储“Position”已更改的标志并在稍后更改时抛出异常来扩展以防止这种情况的发生。尽管如此,仍可能出现一些错误... 我强调的问题涉及违反“IRandomAccessStream”的契约,尽管尚未记录。 - Roman Boiko
这个类似乎不再工作了,因为IRandomAccessStream接口已经通过添加CanRead、CanWrite、ReadAsync等方法进行了扩展。更新答案可能是个好主意,因为它在谷歌搜索结果中排名靠前。 - Mark Vincze
我在样例代码中扩展了界面的附加元素。 - Mark Vincze
显示剩余2条评论

5

经过一些试验,我发现以下代码可以正常工作。

using System;
using System.IO;
using System.Threading.Tasks;
using Windows.Storage.Streams;

partial class MainPage
{
    public MainPage()
    {
        var memoryStream = new MemoryStream(new byte[] { 65, 66, 67 });
        ConvertToRandomAccessStream(memoryStream, UseRandomAccessStream);
        InitializeComponent();
    }

    void UseRandomAccessStream(IRandomAccessStream stream)
    {
        var size = stream.Size;
    } // put breakpoint here to check size

    private static async void ConvertToRandomAccessStream(MemoryStream memoryStream,
         Action<IRandomAccessStream> callback)
    {
        var randomAccessStream = new InMemoryRandomAccessStream();
        var outputStream = randomAccessStream.GetOutputStreamAt(0);
        var dw = new DataWriter(outputStream);
        var task = new Task(() => dw.WriteBytes(memoryStream.ToArray()));
        task.Start();
        await task;
        await dw.StoreAsync();
        var success = await outputStream.FlushAsync();
        callback(randomAccessStream);
    }
}

更新:我还尝试了更优雅的方法实现:

    private static void ConvertToRandomAccessStream(MemoryStream memoryStream,
        Action<IRandomAccessStream> callback)
    {
        var randomAccessStream = new InMemoryRandomAccessStream();
        var outputStream = randomAccessStream.GetOutputStreamAt(0);
        RandomAccessStream.Copy(memoryStream.AsInputStream(), outputStream);
        callback(randomAccessStream);
    }

奇怪的是,它不起作用。当我后来调用stream.Size时,我得到了零。

更新:我将函数更改为返回IRandomAccessStream而不是使用回调函数。

public static async Task<IRandomAccessStream> ConvertToRandomAccessStream(MemoryStream memoryStream)
{
    var randomAccessStream = new InMemoryRandomAccessStream();

    var outputStream = randomAccessStream.GetOutputStreamAt(0);
    var dw = new DataWriter(outputStream);
    var task = new Task(() => dw.WriteBytes(memoryStream.ToArray()));
    task.Start();

    await task;
    await dw.StoreAsync();

    await outputStream.FlushAsync();

    return randomAccessStream;
}

4

在 Windows 8 上没有内置的方法。而在 Windows 8.1 中,我们添加了一个名为 Stream.AsRandomAccessStream() 的扩展方法:

internal static IRandomAccessStream ToRandomAccessStream(byte[] array)
{
    MemoryStream stream = new MemoryStream(array);
    return stream.AsRandomAccessStream();
}

3

今天以上方法都不适用于我(也许是因为自从回答发布以来API发生了变化)。唯一可行的方式是

IRandomAccessStream inMemoryStream = new InMemoryRandomAccessStream();
using (var inputStream = stream.AsInputStream())
{
    await RandomAccessStream.CopyAsync(inputStream, inMemoryStream);
}
inMemoryStream.Seek(0);

0
这段代码片段将一个流(stream)转换为实现了IRandomAccessStream的InMemoryRandomAccessStream(ims)。诀窍在于必须在后台线程上调用CopyTo。
        InMemoryRandomAccessStream ims = new InMemoryRandomAccessStream();
        var imsWriter = ims.OpenWrite();
        await Task.Factory.StartNew(() => stream.CopyTo(imsWriter));

1
将最后一行的任务位替换为:await stream.CopyToAsync(imsWriter); - Robert MacLean
InMemoryRandomAccessStream.OpenWrite() 似乎不再存在了? - Jay Borseth
1
@JayBorseth 为什么?根据MSDN的说法,它是存在的。 - Roman Boiko

0

请查看此链接:

如何将字节数组转换为IRandomAccessStream

它还提供了一个字节数组构造函数的示例和实现(以及.NET流的一个构造函数),如果您想使用BitmapImage类的SetSourceSetSourceAsync方法(就像我一样),这将非常有用。

希望这能帮助到某些人...


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接