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

Posted

技术标签:

【中文标题】将 HBITMAP 句柄从非托管代码传递到托管代码以创建 System.Drawing.Bitmap 的安全性【英文标题】:Safety of passing HBITMAP handle from unmanaged to managed code for created a System.Drawing.Bitmap 【发布时间】:2011-09-02 14:51:02 【问题描述】:

我对托管/非托管互操作非常陌生,因此我希望就以下过程从非托管 C++ 到托管 C# 获取位图的安全性获得一些意见。基本思路是:

    C# 调用非托管 C++ 中的互操作函数 FetchImage。它传递了一个out int 参数。 FetchImage 有一个对应的 long * 参数。 在 C++ 中,FetchImage 在安全的地方(即非本地)创建一个CBitmap,在其上绘制一些东西,使用HandleToLong() 将位图的HBITMAP 句柄转换为long,将其存储在参数中C#,然后返回。 回到 C# 中,out int 参数转换为 IntPtr 并使用 System.Drawing.Image.FromHbitmap 复制数据并生成 System.Drawing.Bitmap 对象。 C# 然后调用另一个互操作函数ReleaseImage。 在 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# 的帮助器类中:

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

在帮助类中生成这些 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 导致事情不同步。所以我可能会有一个CBitmaps 的列表,而不仅仅是一个,然后将句柄传回给ReleaseImage,这样它只会从匹配的FetchImage 调用中销毁那个。

有什么我不知道的问题吗?我确实有这个工作,我只是想确保我没有做危险的事情,因为我不知道更好。

【问题讨论】:

参数类型错误,应该是 IntPtr 和 HANDLE。您可以立即释放 CBitmap,FromHbitmap 会制作副本,因此不需要痛苦和冒险的 ReleaseImage()。 @Hans 除了 CBitmap 是在非托管 C++ 代码中创建的。除非被告知,否则该代码如何知道释放它? 你说得对,在 FromHbitmap() 执行之前不能这样做。 【参考方案1】:

您可以声明释放 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 中传递图片的标准方式。

【讨论】:

以上是关于将 HBITMAP 句柄从非托管代码传递到托管代码以创建 System.Drawing.Bitmap 的安全性的主要内容,如果未能解决你的问题,请参考以下文章

将指针从非托管代码返回到托管代码

从非托管代码传递指针

将数组从非托管 C++ 传递到 C#

如何将数据从非托管应用程序传递到 C# COM DLL

无法将结构从非托管库编组到单触引中的托管代码

将图像结构从本机代码导出到托管