• [Bug] VisualStyleRenderer may cause GDI leak!


        今天下午突然接到一个很奇怪的bug,发现在打开Theme的环境下,show OverflowTip时会导致每次1个GDI的泄漏,拿到示例,跟了许久,居然没有发现任何线索,只能通过GDIUsage查到泄漏了相当多的GDI Region,很郁闷,按照新的设计,应该不会有明显的GDI泄漏问题,即使有也可能只是开发时的“手误”,只要打开GDI的Trace工具,肯定就可以发现,结果什么都没有,难道Trace工具也会错?
        只好再查,发现问题只发生在Theme打开的环境,恰好这时看到代码中有一段关于Theme的处理,于是抱着试试的心理,封了这段处理(代码3-11行),意外竟然发现不再有GDI Leak了,显然就是这里有鬼了。

     1public override Region CreateRegion()
     2{
     3    if (this.UseSystemVisualStyle
     4        && Application.RenderWithVisualStyles
     5        && VisualStyleRenderer.IsElementDefined(VisualStyleElement.ToolTip.Standard.Normal))
     6    {
     7        VisualStyleRenderer render = new VisualStyleRenderer(VisualStyleElement.ToolTip.Standard.Normal);
     8
     9        return render.GetBackgroundRegion(DefaultGraphics, this.Bounds);
    10    }

    11    else
    12        return new Region(this.Bounds);
    13}
        但是这里很简单,基本上没有什么复杂的,构造了个VisualStyleRenderer,调用GetBackgroundRegion获得一个Region,于是再次把这段代码打开,GDI又泄漏了,嗯,既然外表看来没有问题,那只能去查查他的家底了。
        打开Reflector,找到VisaulStyleRenderer.GetBackgroundRegion,发现这里是通过uxTheme的API——GetThemeBackgroundRegion来获取一个GDI Region Object,然后通过System.Drawing.Region.FromHrgn获得Region并返回;或许你也发现了,这里的GDI Region Object没有释放!就是说就是这个函数导致了GDI泄漏。
     1[SuppressUnmanagedCodeSecurity]
     2public Region GetBackgroundRegion(IDeviceContext dc, Rectangle bounds)
     3{
     4    if (dc == null)
     5    {
     6        throw new ArgumentNullException("dc");
     7    }

     8    if ((bounds.Width < 0|| (bounds.Height < 0))
     9    {
    10        return null;
    11    }

    12    IntPtr zero = IntPtr.Zero;
    13    using (WindowsGraphicsWrapper wrapper = new WindowsGraphicsWrapper(dc, TextFormatFlags.PreserveGraphicsTranslateTransform | TextFormatFlags.PreserveGraphicsClipping))
    14    {
    15        HandleRef hdc = new HandleRef(wrapper, wrapper.WindowsGraphics.DeviceContext.Hdc);
    16        this.lastHResult = SafeNativeMethods.GetThemeBackgroundRegion(new HandleRef(thisthis.Handle), hdc, this.part, this.state, new NativeMethods.COMRECT(bounds), ref zero);
    17    }

    18    if (!(zero != IntPtr.Zero))
    19    {
    20        return null;
    21    }

    22    return Region.FromHrgn(zero);
    23}
        分析到此,已经可以得出是MS在这里的bug了。为了再次验证,我写了个简单的Demo,当每次点击Button后,就会发现系统资源里多一个GDI Object:
     1public partial class Form1 : Form
     2{
     3    public Form1()
     4    {
     5        InitializeComponent();
     6
     7        this.Text = "Current GDI Object Number: " + GetGuiResources(Process.GetCurrentProcess().Handle, 0).ToString();
     8    }

     9
    10    private void button1_Click(object sender, EventArgs e)
    11    {
    12        if (Application.RenderWithVisualStyles
    13            && VisualStyleRenderer.IsElementDefined(VisualStyleElement.ToolTip.Standard.Normal))
    14        {
    15            VisualStyleRenderer render = new VisualStyleRenderer(VisualStyleElement.ToolTip.Standard.Normal);
    16            using (Graphics g = this.CreateGraphics())
    17            {
    18                using (Region region = render.GetBackgroundRegion(g, new Rectangle(00100100)))
    19                {
    20                    g.FillRegion(Brushes.Red, region);
    21                }

    22            }

    23        }

    24
    25        GC.Collect();
    26        GC.WaitForPendingFinalizers();
    27
    28        this.Text = "Current GDI Object Number: " + GetGuiResources(Process.GetCurrentProcess().Handle, 0).ToString();
    29    }

    30
    31    [DllImport("User32.dll")]
    32    private static extern int GetGuiResources(IntPtr hProcess, uint uiFlags);
    33}

        下面是运行结果截图:


        OK,到此为止,确定这是MS VisualStyleRenderer导致GDI leak了,面对bug,该怎么解决呢?既然这个接口会导致bug,我们只能提供一个相似功能的函数,来作为短期替代方案,待MS修复bug之后再恢复现在code。
        下面是解决方案:

     1public override Region CreateRegion()
     2{
     3    if (this.UseSystemVisualStyle
     4        && Application.RenderWithVisualStyles
     5        && VisualStyleRenderer.IsElementDefined(VisualStyleElement.ToolTip.Standard.Normal))
     6    {
     7        VisualStyleRenderer render = new VisualStyleRenderer(VisualStyleElement.ToolTip.Standard.Normal);
     8
     9        // MS VisualStyleRenderer.GetBackgroundRegion() would cause GDI leak, so I write a same logic
    10        // but clean the bug.
    11        // If MS have fixed this bug, please take care to rollback if possible.
    12        // ---------------------------------------------------------------
    13        //return render.GetBackgroundRegion(DefaultGraphics, this.Bounds);
    14        IntPtr hrgn = IntPtr.Zero;
    15        IntPtr hdc = DefaultGraphics.GetHdc();
    16
    17        try
    18        {
    19            Platform.UnsafeNativeMethods.GetThemeBackgroundRegion(render.Handle, hdc,
    20                render.Part, render.State, new Platform.NativeMethods.RECT(this.Bounds), ref hrgn);
    21            if (hrgn == IntPtr.Zero)
    22            {
    23                return null;
    24            }

    25
    26            return Region.FromHrgn(hrgn);
    27        }

    28        finally
    29        {
    30            if (hdc != IntPtr.Zero)
    31            {
    32                DefaultGraphics.ReleaseHdc(hdc);
    33            }

    34            if (hrgn != IntPtr.Zero)
    35            {
    36                Platform.UnsafeNativeMethods.DeleteObject(hrgn);
    37            }

    38        }

    39    }

    40    else
    41        return new Region(this.Bounds);
    42}

    To be the apostrophe which changed “Impossible” into “I’m possible”
    ----------------------------------------------------
    WinkingZhang's Blog (http://winkingzhang.cnblogs.com)
    GCDN(http://gcdn.grapecity.com/cs)
  • 相关阅读:
    常用的服务器简介
    PHP Proxy 负载均衡技术
    Hexo 博客Next 搭建与美化主题
    Tomcat PUT方法任意文件上传(CVE-2017-12615)
    哈希爆破神器Hashcat的用法
    内网转发随想
    Oauth2.0认证
    Github搜索语法
    记一次挖矿木马清除过程
    利用ICMP进行命令控制和隧道传输
  • 原文地址:https://www.cnblogs.com/winkingzhang/p/1034095.html
Copyright © 2020-2023  润新知