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