使用 gdiplus 打印 png24 图像时 GDI 内存泄漏

Posted

技术标签:

【中文标题】使用 gdiplus 打印 png24 图像时 GDI 内存泄漏【英文标题】:GDI memory leak when printing png24 images with gdiplus 【发布时间】:2021-06-26 13:15:57 【问题描述】:

在 Windows 10 下的某些打印机上打印时,我遇到了一个非常奇怪的遗留“gdi 对象”泄漏。

即使在 Microsoft 虚拟打印机(例如本示例中使用的“Microsoft XPS Document Writer”)上打印时也会发生这种情况。 当涉及 png 24 图像时,似乎会发生 GDI 泄漏。

我创建了一个测试项目来轻松重现该行为:

Imports System.Drawing
Imports System.Drawing.Printing
    
Module Module1
    
    ' The PNG 24 source image
    Private Const SOURCE_PNG24_FILE As String = [Path to any png 24 image]
    
    ' The number of rendering repetitions
    Private Const REPETITIONS As Integer = 100
    
    Sub Main()
    
        MsgBox("Open the task manager and check GDI objects count for this application, should be around 15.", MsgBoxStyle.Information)
    
        ' Get Microsoft XPS Document Writer printer settings
        Dim prnSettings As New PrinterSettings() With .PrinterName = "Microsoft XPS Document Writer"
    
        ' Ensure printer is valid
        If prnSettings.IsValid = False Then
            MsgBox("Can't find Microsoft XPS Document Writer.", MsgBoxStyle.Exclamation)
            Exit Sub
        End If
    
        ' Init document settings
        m_prtDoc = New PrintDocument With .DocumentName = "PNG24 print test",
                                           .PrinterSettings = prnSettings
    
        ' Start printing
        m_prtDoc.Print()
    
        MsgBox("Check GDI objects count, probably thru the roof.", MsgBoxStyle.Information)
    
    End Sub
    
    ' Document settings
    Private WithEvents m_prtDoc As PrintDocument = Nothing
    
    ' Page count
    Private m_iPageCount As Integer = 0
    
    
    Private Sub PrintPage(sender As Object, e As PrintPageEventArgs) Handles m_prtDoc.PrintPage
    
        Try
    
            ' Load png 24 image
            Using imgSourcePng24 As Image = Image.FromFile(SOURCE_PNG24_FILE)
    
                ' Create in memory image of the same size
                Using imgMemory As New Bitmap(imgSourcePng24.Width, imgSourcePng24.Height)
    
                    ' Get the in-memory graphics
                    Using gphMemory As Graphics = Graphics.FromImage(imgMemory)
    
                        ' Draw source image
                        gphMemory.DrawImage(imgSourcePng24, New Rectangle(Point.Empty, imgSourcePng24.Size), New Rectangle(Point.Empty, imgSourcePng24.Size), GraphicsUnit.Pixel)
    
                    End Using
    
                    ' Draw in-memory image -> Comment this line and no GDI object spike
                    e.Graphics.DrawImage(imgMemory, New Rectangle(Point.Empty, imgMemory.Size), New Rectangle(Point.Empty, imgMemory.Size), GraphicsUnit.Pixel)
    
                End Using
    
            End Using
    
            ' Increase page count
            m_iPageCount += 1
    
            ' Print up to REPETITIONS pages
            e.HasMorePages = (m_iPageCount < REPETITIONS)
    
        Catch ex As Exception
            ' Error, Stop printing
            MsgBox(ex.Message, MsgBoxStyle.Critical)
        End Try
    
    End Sub
    
End Module

内存泄漏发生在 Windows 10 19042.867 上,而在具有相同虚拟打印机的 Windows 7 上不会发生。 png 8 或其他图像格式(例如 jpg)不会发生这种情况。

我有另一个导致相同奇怪行为的 win32 项目,似乎问题可能与 GdipCreateBitmapFromScan0 有关,它在创建新的空位图时在后台调用。

如果您将 png24 直接渲染到打印机的 Graphics 上一切正常,但如果需要中间步骤(例如裁剪)并创建空白图像,则会发生泄漏。

任何帮助表示赞赏。 谢谢。

编辑

在运行了更多测试后,事实证明它与将 32bppARGB 图像渲染到打印机 Graphics 有关,无论其来源如何。 当您使用 argb 内容渲染内存中创建的位图时也会发生这种情况:

Private Sub PrintPage(sender As Object, e As PrintPageEventArgs) Handles m_prtDoc.PrintPage

    Try

        ' Create in memory image 
        Using imgMemory As New Bitmap(600, 400)

            ' Fill with alpha color
            Using gphMemory As Graphics = Graphics.FromImage(imgMemory)
                gphMemory.Clear(Color.FromArgb(128, 255, 0, 0)) ' Red at 0.5 alpha
            End Using

            ' Draw in-memory image
            e.Graphics.DrawImageUnscaled(imgMemory, e.PageBounds.Location)

        End Using

        ' Increase page counter
        m_iPageCount += 1

        ' The number of rendering repetitions
        Const REPETITIONS As Integer = 20

        ' Print up to REPETITIONS pages
        e.HasMorePages = (m_iPageCount < REPETITIONS)

    Catch ex As Exception
        ' Error, Stop printing
        MsgBox(ex.Message, MsgBoxStyle.Critical)
    End Try

End Sub

【问题讨论】:

一些工具可以帮助您调试泄漏:How to debug GDI Object Leaks? 感谢Xingyu Zhao,我一直在尝试其中的一些。问题是:当不是您的代码导致泄漏时,您会怎么做? 向导致泄漏的代码的所有者提交错误报告? @Craig,那就是微软 :) 旁注:PrintDocument 也实现了IDisposable。可能还需要using 【参考方案1】:

对于遇到此问题的任何人,似乎问题已通过更新 KB5001649 解决

确保您和/或您的客户使 Windows 保持最新状态,此问题不会影响您的项目。

【讨论】:

以上是关于使用 gdiplus 打印 png24 图像时 GDI 内存泄漏的主要内容,如果未能解决你的问题,请参考以下文章

在 C++ 中使用 Gdiplus 创建透明位图

VB6添加PNG图片

VB6添加PNG图片

C++ GDI+如何从资源中获取和加载图像?

c++ gdi图像数组

C++ gdi::Bitmap 到内存中的 PNG 图像