判断IntPtr指向托管内存还是非托管内存

3

我是在C#中使用封装的C库,需要将从该库传来的图像转换为位图并重新转换回去,但不需要复制像素缓冲区。

转换为位图很简单:

Bitmap WrapAsBitmap(CImage image)
{
    return new Bitmap(image.Width, image.Height, image.BytesPerLine, PixelFormat.Format24bppRgb, image.Data);
}

只需将原始像素缓冲区 (image.Data) 传递给 Bitmap 构造函数,无需复制。要执行相反操作,我需要调用 LockBitsUnlockBits 来访问原始像素缓冲区的 IntPtr

CImage WrapAsCImage(Bitmap bitmap)
{
    BitmapData data = bitmap.LockBits(new Rectangle(0, 0, bitmap.Width, bitmap.Height), ImageLockMode.ReadOnly, bitmap.PixelFormat);
    var image = new CImage(bitmap.Width, bitmap.Height, data.Stride * 3, data.Scan0);
    bitmap.UnlockBits(data);
    return image;
}

现在,正如您所看到的,如果在托管内存中,这实际上是一种危险的方法,因为位在新构建的图像将被使用之前被“解锁”。如果是从方法构建的,则没有问题,因为指向不会被.NET移动的非托管内存。因此,我想建立一个安全检查,检查指针的位置(托管/非托管)。
我考虑的另一种解决方案是继承类并跟踪原始指针以及,但是类是密封的,所以我没办法。
简而言之:有什么想法可以检测的位置(托管/非托管)吗?

这里的CImage是什么? - Alex F
一个包装来自C库的图像的包装类。这个类中唯一相关的成员在WrapAsBitmap方法中显示。 - huysentruitw
如果可能的话(我不确定),你在这里还有另一个问题:CImage -> WrapAsBitmap -> WrapAsCImage -> CImage,现在你有两个指向相同内存的CImage实例。也许你需要在WrapAsCImage中复制内存? - Alex F
@AlexFarber 我知道这一点,但这里并不是问题。 - huysentruitw
愚蠢的建议:使用Image.Tag属性怎么样?虽然不是通用的,但如果正确使用可能会起作用。 - Alex F
我也考虑过那个方案,但真的不想走那条路 :) - huysentruitw
2个回答

5

在任何情况下,IntPtr都不会指向托管内存。当然,当图像数据由C代码生成时也不会这样。当它由Bitmap生成时也不会这样,因为GDI+是非托管代码。你可以自己推理出这一点,你只能通过固定内存来获得托管内存的IntPtr。使用GCHandle.AddrOfPinnedObject()方法。这是一个硬性要求,防止垃圾收集器在压缩堆时使指针无效。没有任何文档需要固定内存。

实施测试没有意义。

你确实有一个硬性要求,即在Bitmap包装器的生命周期内保持IntPtr有效,直到调用其Dispose()方法为止。你不能让这个过程自动化,除非将Bitmap对象封装在一个实现IDisposable接口的类中。如果不这样做,就会出现非常丑陋的错误行为,幸运的话,你只会得到一个AccessViolationException异常。一个带有随机像素内容的损坏位图是正常的失败模式。

如果你无法实现这个保证,那么就不应该这样做,而是复制位图数据。这并不难实现,只是不太美观。


0

Bitmap类实际上只是一个GDI+ Bitmap对象的管理包装器。由于该对象不在固定缓冲区上进行操作,也不保证该缓冲区以特定格式呈现给用户代码,因此您将无法执行所描述的操作。

换句话说,在不限制问题空间的情况下,肯定会有需要复制缓冲区的情况。


嗯,我认为并不是这样的。请看构造函数以及备注:调用方负责分配和释放由scan0参数指定的内存块。但是,在相关的Bitmap被释放之前,内存不应该被释放。 - huysentruitw
@WouterHuysentruit 底层 GDI+ 方法的文档表明缓冲区被复制:http://msdn.microsoft.com/en-us/library/ms536315.aspx - Sam Harwell

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接