October 23rd, 2024

How do I create a Windows Runtime IRandom­Access­Stream around a bunch of bytes or a classic COM IStream?

There are some Windows Runtime methods which expect a random access stream in the form of a IRandom­Access­Stream. On the other hand, you might have your data in the form of a memory block. For example, you might have a memory block that represents a bitmap, and you want to wrap it in a IRandom­Access­Stream so you can use it as a Svg­Image­Source.

One option is to create an In­Memory­Random­Access­Stream and use the Write­Async() method to write the bytes (in the form of an IBuffer), then rewind the stream back to the start.

// C++/WinRT

winrt::Buffer BufferFromBytes(winrt::array_view<uint8_t> bytes)
{
    winrt::Buffer buffer(bytes.size());
    memcpy(buffer.data(), bytes.data(), bytes.size());
    buffer.Length(bytes.size());
    return buffer;
}

winrt::IAsyncOperation<winrt::IRandomAccessStream>
    BytesToRandomAccessStream(winrt::array_view<uint8_t> bytes)
{
    winrt::InMemoryRandomAccessStream stream;
    co_await stream.WriteAsync(BufferFromBytes(bytes));

    stream.Seek(0);
    co_return stream;
}

This is rather annoying because WriteAsync() is an async method, which means we have to co_await it, which in turn forces us to be a coroutine as well.

But there’s a shortcut: Create­Random­Access­Stream­Over­Stream.

The Create­Random­Access­Stream­Over­Stream function takes a classic COM IStream and creates a Windows Runtime IRandom­Access­Stream around it.

So this will actually take two steps. First we need to put the bytes into an IStream. Then we can wrap the IStream inside an IRandom­Access­Stream.

// C++/WinRT

winrt::com_ptr<IStream> StreamFromBytes(winrt::array_view<uint8_t> bytes)
{
    winrt::com_ptr<IStream> stream{
        SHCreateMemStream(reinterpret_cast<const BYTE*>(bytes.data()),
                          bytes.size()),
        winrt::take_ownership_from_abi };
    winrt::check_pointer(stream.get());
    return stream;
}

winrt::IRandomAccessStream
    BytesToRandomAccessStream(winrt::array_view<uint8_t> bytes)
{
    return winrt::capture<winrt::IRandomAccessStream>(
        CreateRandomAccessStreamOverStream,
        StreamFromBytes(bytes).get(),
        BSOS_DEFAULT);
}

Bonus reading: The difference between assignment and attachment with ATL smart pointers, exacerbated by SHCreate­Mem­Stream not following standard COM patterns.

For C#, somebody did part of the work for you: Windows­Runtime­Stream­Extensions.As­Random­Access­Stream converts a System.IO.Stream to a Windows Runtime IRandom­Access­Stream.

// C#
IRandomAccessStream
    BytesToRandomAccessStream(byte* bytes, long length)
{
    var stream = new UnmanagedMemoryStream(bytes, length);
    return stream.AsRandomAccessStream();
}

The above function looks simple, but it’s also dangerous because you don’t know when it’s safe to free the bytes. We can copy them into a Memory­Stream so that its lifetime is properly managed.

// C#
MemoryStream
    StreamFromBytes(byte* bytes, int length)
{
    var stream = new MemoryStream(length);
    stream.Write(new ReadOnlySpan<byte>(bytes, length));
    stream.Seek(0, SeekOrigin.Begin);
    return stream;
}

IRandomAccessStream
    BytesToRandomAccessStream(byte* bytes, int length)
{
    return StreamFromBytes(bytes, length).AsRandomAccessStream();
}
Topics
Code

Author

Raymond has been involved in the evolution of Windows for more than 30 years. In 2003, he began a Web site known as The Old New Thing which has grown in popularity far beyond his wildest imagination, a development which still gives him the heebie-jeebies. The Web site spawned a book, coincidentally also titled The Old New Thing (Addison Wesley 2007). He occasionally appears on the Windows Dev Docs Twitter account to tell stories which convey no useful information.

1 comment

  • Dmitry · Edited

    Frankly speaking, I often feel an overview of various API ”families” and how they relate to each other, which requirements (Windows version, particular components installed, etc.) and limitations they cause, and a brief overview of conventions for each would be a great series.

    For example, we have good old WinAPI that dates back to the stone ages and generally uses stdcall for 32-bit Windows, blah-blah. Then there’s COM-related stuff with its own long story full of...

    Read more