Reputation: 1386
I struggle to properly release the BitmapBuffer
locked with SoftwareBitmap.LockBuffer()
method in C++/CX code on UWP.
The base code looks as below (it's a OpenCV bridge sample from Microsoft available here.
bool OpenCVHelper::GetPointerToPixelData(SoftwareBitmap^ bitmap, unsigned char** pPixelData, unsigned int* capacity)
{
BitmapBuffer^ bmpBuffer = bitmap->LockBuffer(BitmapBufferAccessMode::ReadWrite);
IMemoryBufferReference^ reference = bmpBuffer->CreateReference();
ComPtr<IMemoryBufferByteAccess> pBufferByteAccess;
if ((reinterpret_cast<IInspectable*>(reference)->QueryInterface(IID_PPV_ARGS(&pBufferByteAccess))) != S_OK)
{
return false;
}
if (pBufferByteAccess->GetBuffer(pPixelData, capacity) != S_OK)
{
return false;
}
return true;
}
This buffer (pPixelData
) is then used to initialize cv:Mat
object (shallow copy). And is never released.
Consecutive call to LockBuffer()
on the same SoftwareBitmap
object raises exception:
Platform::AccessDeniedException ^ at memory location 0x00000002CEEFDCC0. HRESULT:0x80070005 Access is denied. WinRT information: Bitmap shared lock taken
How to properly release this buffer? Especially in C++/CX?
I tried to keep the reference to release it when no more needed. In C++/CX Dispose() or Close() methods are inaccessible and compiler advises to call destructor instead:
BitmapBuffer^ bmpBuffer = nullptr;
// ... get the buffer, use it
//((IDisposable^)bmpBuffer)->Dispose();
bmpBuffer->~BitmapBuffer();
But it doesn't work (does nothing). Destructor is being invoked but another call to LockBuffer() raises the same error as before.
Upvotes: 5
Views: 1056
Reputation: 1331
I have used the above GetPointerToPixelData
(and the TryConvert
) successfully in a Windows Store app (12 months now), so maybe in the years since OP, the framework has improved, or maybe I'm using it differently.
However, this code always "bothered" me, so here is my journey putting the world to rights....
If we were using these directly in managed C#, all the examples clearly show using
on both the BitmapBuffer
and the IMemoryBufferReference
so calling the IDispose
ASAP seems to me to be obligatory. The documentation even admonishes you to not hold onto these very long!
Now that I understand how these APIs work better, IMHO the UWP sample code works "by accident" (it's a sample after all, so no judgment), so I finally decided to re-implement so everything is scoped properly (with RAII) and uses a closure for the "action", so now we also clean up if an Exception
occurs in the handler
.
This version also accounts for the Stride
which is important when you are dealing with GREY images, which my app handles (got tired of calling cvtColor
on everything...)
Disclaimer: I know enough C++/CX to be dangerous; this can probably be improved by someone who knows the "rules" better.
Edited: modified
stride
handling to use the ctor overload. Switched tovoid
. Switched toconst
closure parameter. Added error control.
void OpenCVHelper::UseBitmap(SoftwareBitmap^ sb, BitmapBufferAccessMode bbam, std::function<void(const Mat&)> handler) {
// invokes IDispose (via delete) at end-of-scope
disposer<BitmapBuffer^> bb(sb->LockBuffer(bbam));
IMemoryBufferReference^ imbr = bb->CreateReference();
disposer<IMemoryBufferReference^> xx(imbr);
ComPtr<IMemoryBufferByteAccess> pmbba;
bool error = true;
if ((reinterpret_cast<IInspectable*>(imbr)->QueryInterface(IID_PPV_ARGS(&pmbba))) == S_OK) {
unsigned char* pPixels = nullptr;
unsigned int capacity = 0;
if (pmbba->GetBuffer(&pPixels, &capacity) == S_OK) {
size_t stride = Mat::AUTO_STEP;
int width = sb->PixelWidth;
int height = sb->PixelHeight;
if (sb->BitmapPixelFormat == BitmapPixelFormat::Gray8 && bb->GetPlaneCount() > 0) {
BitmapPlaneDescription bpd = bb->GetPlaneDescription(0);
stride = (size_t)bpd.Stride;
width = bpd.Width;
height = bpd.Height;
}
int type = TypeFor(sb->BitmapPixelFormat);
Mat mat(height, width, type, (void*)pPixels, stride);
try {
handler(mat);
error = false;
}
catch(cv::Exception cex) {
throw ref new Platform::FailureException(std_to_managed((std::string)cex.msg));
}
}
}
if(error) throw ref new Platform::FailureException("Failed to attach the SoftwareBitmap");
}
The TypeFor
write your own or hard-code BGRA8. If you only care about BGRA8 the Stride
stuff is unnecessary; BGRA is 4-byte aligned. Also note we need to use Platform
exceptions back to managed-land.
I should have been able to use std::unique_ptr
to get it to invoke the operator delete
for me, but it doesn't work with ref
classes.
I also had trouble with getting the finally
helper to work with ref
classes (ref
classes are taint) so built an abbreviated version to handle the lack of try/finally
in C++ and went the RAII route (as required). Here is disposer
:
template <class F>
class disposer {
public:
explicit disposer(F inst) noexcept :f_(inst) {};
~disposer() noexcept {
delete f_;
}
F operator->() const { return f_; }
private:
F f_;
};
Setting a breakpoint verifies that delete
is called with correct order and timing.
For extra credit, here is a use case; most "operations" require 2 bitmaps, one for input and one for output, so here is a helper function that takes a closure that receives both Mat&
instances:
void OpenCVHelper::Use2Bitmaps(Windows::Graphics::Imaging::SoftwareBitmap^ sb1,
Windows::Graphics::Imaging::BitmapBufferAccessMode bbam1,
Windows::Graphics::Imaging::SoftwareBitmap^ sb2,
Windows::Graphics::Imaging::BitmapBufferAccessMode bbam2,
std::function<void(const cv::Mat&, const cv::Mat&)> handler) {
OpenCVHelper::UseBitmap(sb1, bbam1, [&](const Mat& mat1) {
OpenCVHelper::UseBitmap(sb2, bbam2, [&handler, &mat1](const Mat& mat2) {
handler(mat1, mat2);
});
});
}
And finally, here is an actual use case calling CV APIs:
void OpenCVHelper::Linear(
Windows::Graphics::Imaging::SoftwareBitmap^ input,
double alpha, double beta,
Windows::Graphics::Imaging::SoftwareBitmap^ output) {
OpenCVHelper::Use2Bitmaps(input, BitmapBufferAccessMode::Read, output, BitmapBufferAccessMode::ReadWrite, [&](const Mat& src, const Mat& dst) {
src.convertTo(dst, -1, alpha, beta);
});
}
Upvotes: 0
Reputation: 10627
How to properly Release BitmapBuffer from SoftwareBitmap (UWP)?
After BitmapBuffer
and IMemoryBufferReference
completing their work, these objects could be closed by invoking delete
expression. More details please check Destructors. For example:
SoftwareBitmap^ bitmap = ref new SoftwareBitmap(
BitmapPixelFormat::Bgra8,
100,
200,
BitmapAlphaMode::Premultiplied);
BitmapBuffer^ bmpBuffer = bitmap->LockBuffer(BitmapBufferAccessMode::ReadWrite);
IMemoryBufferReference^ reference = bmpBuffer->CreateReference();
delete reference;
delete bmpBuffer;
BitmapBuffer^ bmpBuffer2 = bitmap->LockBuffer(BitmapBufferAccessMode::ReadWrite);
As above code snippet showed, after delete the BitmapBuffer
object, you could lock the buffer again successfully.
Upvotes: 0