5

Smoothing over the differences (and defects) in the various implementations of I...

 7 months ago
source link: https://devblogs.microsoft.com/oldnewthing/20240130-00/?p=109336
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

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

RaymondChen_5in-150x150.jpg

Raymond Chen

January 30th, 20241 0

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.


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK