从非托管代码传递HBITMAP句柄到托管代码创建System.Drawing.Bitmap的安全性

Safety of passing HBITMAP handle from unmanaged to managed code for created a System.Drawing.Bitmap

本文关键字:System 创建 Drawing Bitmap 安全性 托管代码 非托管代码 HBITMAP 句柄      更新时间:2023-10-16

我对托管/非托管互操作很陌生,所以我希望得到一些关于以下过程从非托管c++到托管c#获取位图的安全性的意见。基本思想是:

  1. c#调用一个互操作函数FetchImage,它在非托管c++中。它传递一个out int参数。FetchImage有对应的long *参数。
  2. 在c++中,FetchImage在某个安全的地方创建了一个CBitmap,即不是本地的,在它上面画一些东西,使用HandleToLong()将位图的HBITMAP句柄转换为long,将其存储在c#的参数中,并返回。
  3. 在c#中,out int参数被转换为IntPtr,并使用System.Drawing.Image.FromHbitmap复制数据并产生System.Drawing.Bitmap对象。然后c#调用另一个互操作函数ReleaseImage
  4. 在c++中,ReleaseImage释放与之前创建的CBitmap相关的资源。

这是给没有耐心的人的要点。下面是更具体的代码示例。

c++互操作函数定义:

namespace {
    std::unique_ptr< CBitmap > bitty;
}
HRESULT __stdcall Helper::FetchImage( /*[out]*/ long * hBitmap )
{
    bitty.reset( new CBitmap );
    // call CreateBitmap and then draw something,
    // ensure it's not selected into a DC when done
    *hBitmap = HandleToLong( bitty->GetSafeHandle() );
    return S_OK;
}
HRESULT __stdcall Helper::ReleaseImage()
{
    bitty.reset();
    return S_OK;
}

互操作函数的IDL原型,在c#中包装在一个helper类中:

[id(1)] HRESULT FetchImage( long * hBitmap );
[id(2)] HRESULT ReleaseImage();

在helper类中生成这些c#原型:

void FetchImage( out int hBitmap );
void ReleaseImage();

调用它们的c#看起来像这样:

int ret;
helper.FetchImage( out ret );
Bitmap b = Image.FromHbitmap( (IntPtr)ret );
helper.ReleaseImage();
// do anything I want with b

我自己提出的唯一问题是从其他地方调用FetchImageReleaseImage使事情不同步的情况。所以我可能会有一个CBitmap的列表,而不是只有一个,然后将句柄传递回ReleaseImage,这样它只会从匹配的FetchImage调用中销毁一个。

有我不知道的陷阱吗?我只是想确保我没有做一些危险的事情,因为我不知道有什么更好的

您可以直接声明释放HBITMAP是调用者的责任。这将简化您的c++代码,因为您可以删除ReleaseImage方法。例子:

HRESULT __stdcall Helper::FetchImage( /*[out]*/ HBITMAP * hBitmap )
{
    *hBitmap = NULL; // assume failure
    unique_ptr<CBitmap> bmp(new CBitmap);
    // call CreateBitmap and then draw something,
    // ensure it's not selected into a DC when done
    *hBitmap = (HBITMAP)bmp->Detach();
    return S_OK;
}
// Delete ReleaseImage and all supporting global variables...
// C# example:
IntPtr ret;
helper.FetchImage( out ret );
try {
    Bitmap b = Image.FromHbitmap( ret );
} finally {
    DeleteObject(ret); // pinvoke call into GDI
}

或者,您可以查看使用OleCreatePictureIndirect返回IPicture。这提供了一些优势:

    调用者使用标准的COM引用计数释放返回的图像。这通常使调用者不必担心释放返回的图像(除非调用者是另一个需要手动调用IUnknown::Release的c++程序)。
  • 与其他支持com的语言更好的兼容性,如VBA/VB6。IPicture是COM中传递图片的标准方式。