January 30th, 2024

Smoothing over the differences (and defects) in the various implementations of IMemory­Buffer

We saw last time that each unhappy implementation of IMemory­Buffer is unhappy in its own way. How can you avoid tripping over all of these differences and defects?

Fortunately, all of the implementations satisfy the following minimum requirements:

  • The underlying memory is freed when the IMemory­Buffer and all IMemory­Buffer­References have been closed or destructed.
  • The objects are reliable provided you call only one method at a time.

We can operate within these minimum requirements and treat it as an external memory buffer that is wrapped inside our Custom­Memory­Buffer.

winrt::array_view<uint8_t> GetView(
    winrt::IMemoryBufferReference const& reference)
{
    uint8_t* buffer;
    uint32_t size;
    winrt::check_hresult(reference.as<
        ABI::Windows::Foundation::IMemoryBufferByteAccess>()->
        GetBuffer(&buffer, &size));
    return { buffer, size };
}

We start with a generally useful function that obtains the buffer behind an IMemory­Buffer­Reference and returns it in the form of an array_view<uint8_t>.

// Takes ownership of the IMemoryBufferReference
winrt::IMemoryBuffer WrapAsMemoryBuffer(
    winrt::IMemoryBufferReference const& reference)
{
    return CreateCustomMemoryBuffer(
        GetView(reference),
        [reference]
        {
            reference.Close();
        });
}

The Wrap­As­Memory­Buffer method takes an IMemory­Buffer­Reference and wraps it inside our Custom­Memory­Buffer. We call GetView only once, and it never happens concurrently with the Close of the buffer, so we avoid any multithreaded race conditions.

Basically, we treat IMemory­Buffer­Reference as just another source of memory with a cleanup function. That the memory source happens to be the same family as the wrapper we are producing is just a coincidence.

// Does not take ownership of the IMemoryBuffer
winrt::IMemoryBuffer WrapMemoryBuffer(
    winrt::IMemoryBuffer const& buffer)
{
    return WrapMemoryBuffer(buffer.CreateReference());
}

This overload of Wrap­Memory­Buffer uses an IMemory­Buffer as the source. It just creates a reference from the IMemory­Buffer and then wraps that reference.

Note that the IMemory­Buffer overload does not take ownership of the IMemory­Buffer, since it never closes it. This is a weird asymmetry that is bound to cause confusion. Maybe it should close the IMemory­Buffer?

// Takes ownership of the IMemoryBuffer
winrt::IMemoryBuffer WrapMemoryBuffer(
    winrt::IMemoryBuffer const& buffer)
{
    auto reference = buffer.CreateReference();
    buffer.Close();                           
    return WrapMemoryBuffer(reference);
}

Alternatively, we can ask WIL to close the buffer.

// Takes ownership of the IMemoryBuffer
winrt::IMemoryBuffer WrapMemoryBuffer(
    winrt::IMemoryBuffer const& buffer)
{
    auto close = wil::scope_exit([&] { buffer.Close(); });
    return WrapMemoryBuffer(buffer.CreateReference());
}

But maybe the function name in both cases should be something like Wrap­Memory­Buffer­And­Take­Ownership? I’m not sure. You can decide.

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

Discussion is closed. Login to edit/delete existing comments.

  • Brian Boorman

    Think of a package. If you are wrapping something, it could already be wrapped, perhaps multiple times.
    If you are going to remove a previous wrapper before adding yours, then the terminology should be Rewrap