In my studies of the IMemoryÂBuffer
interface, I found three implementations of that interface in the Windows Runtime.
Windows.
, obtained fromFoundation. MemoryBuffer Buffer.
.CreateÂMemoryÂBufferÂOverÂIBuffer() Windows.
, obtained fromGraphics. Imaging. BitmapBuffer SoftwareÂBitmap.
.LockÂBuffer() - Unnamed class obtained from
PerceptionFrame.
.FrameData
We also wrote our own fourth implementation, which we called CustomÂMemoryÂBuffer
, that lets you turn any block of memory into a MemoryÂBuffer
.
All four of them behave differently. Let’s compare.
MemoryÂBuffer | BitmapÂBuffer | FrameÂData | CustomÂMemoryÂBuffer | |
---|---|---|---|---|
Thread-safe? | No | Yes | Yes | Yes |
IMemoryBuffer supports IMemoryBufferByteAccess? | No | No | Yes | Yes |
CreateReference after Close | Empty | |||
Empty references raise Closed event? | Yes | No | No | Yes |
Raises Closed event automatically when released? | Yes | No | Yes | Yes |
Can extend lifetime during Closed event handler | No | Yes | No | Yes |
Buffer valid during Closed event? | Yes | No | No | Yes |
Can call methods during Closed event | Yes | Yes | No | Yes |
Buffer of empty or closed reference | pointer = nullptr and size = 0 |
|||
Memory freed when… | IMemoryÂBuffer and all IMemoryÂBufferÂReferences have been closed or destructed |
All happy memory buffers look alike. Each unhappy memory buffer is unhappy in its own way.
The standard MemoryÂBuffer
has the problem of not being thread-safe. If you call Close
at the same time as CreateÂReference
, you may experience use-after-free bugs. And if you call Close
twice simultaneously, you can add to your woes null pointer crashes, over-release of the underlying IBuffer
, and double-raising of the the Closed
event, depending on exactly how the race plays out.
All four implementations agree that if you call CreateÂReference
on a closed IMemoryÂBuffer
, you get an “empty reference”. An empty reference is one that protects no memory. If you ask for the buffer of an empty reference, you get a null pointer and a size of zero.
In all of the implementations except FrameÂData
, empty references raise the Closed
event.
The BitmapÂBuffer
‘s memory buffer reference raises the Closed
event only on an explicit call to Closed
. The others raise the Closed
event either on explicit closure or when the last reference is released. This means that BitmapÂBuffer
reference’s Closed
event is even more unreliable than the Closed
event already is by its nature.
The MemoryÂBuffer
and FrameÂData
ignore attempts by the Closed
event handler to extend the reference’s lifetime. The biggest consequence of this is that the Closed
event in those implementations will corrupt memory if consumed from a GC language. The BitmapÂBuffer
sneakily passes this test because it is masked by the other defect of simply not raising the Closed
event in the dangerous scenario in the first place.
The BitmapÂBuffer
and FrameÂData
raise the Closed
event after freeing the memory, which means that the event is useless for triggering cleanup: Since you are told that the memory has been freed only after it happened, all you’re really learning is that “Oops, you already corrupted memory.”
The FrameÂData
has the bonus insult of passing you an IMemoryÂBufferÂReference
in the Closed
handler that cannot be used! Any attempt to obtain the buffer’s capacity or pointer will hang. (That’s because it raises the Closed
event while still holding its internal lock. Calling to outside code while holding a lock is a bad idea for reasons like this.)
Our CustomÂMemoryÂBuffer
tries to avoid all of these little defects.
But what if you are forced to use one of the other three implementations of IMemoryÂBuffer
, or some other fifth implementation from an external source that isn’t even on the list. Seeing as the first three attempts at implementing IMemoryÂBuffer
all failed in different ways, what confidence do you have that an unknown implementation will be well-behaved?
We’ll solve this problem next time. The answer is right under our nose.
0 comments