1

Looking to use BitBlt for an application, I found a C# reference at BitBlt code not working and converted it to VB.net. I mainly use VB.net which is why I converted it, and trying to use it as such. It works fine in C#, but in VB.net it has a memory leak and I'm not sure how to fix it.

Code:

C# version (modified from above source a little). Open new project, add 1 button and 1 picturebox, modify lstPics.Add():

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
using System.Runtime.InteropServices;

namespace WindowsFormsApplication1
{
    public partial class Form1 : Form
    {
        [DllImport("gdi32.dll", EntryPoint = "SelectObject")]
        public static extern System.IntPtr SelectObject(
        [In()] System.IntPtr hdc,
        [In()] System.IntPtr h);

        [DllImport("gdi32.dll", EntryPoint = "DeleteObject")]
        [return: MarshalAs(UnmanagedType.Bool)]
        public static extern bool DeleteObject(
            [In()] System.IntPtr ho);

        [DllImport("gdi32.dll", EntryPoint = "BitBlt")]
        [return: MarshalAs(UnmanagedType.Bool)]
        public static extern bool BitBlt(
            [In()] System.IntPtr hdc, int x, int y, int cx, int cy,
            [In()] System.IntPtr hdcSrc, int x1, int y1, uint rop);

        public Form1()

        {
            InitializeComponent();
        }

        public Int16 lstLoc = 0;

        private void button1_Click(object sender, EventArgs e)
        {
            List<string> lstPics = new List<string>();
            lstPics.Add("C:\\1.jpg");
            lstPics.Add("C:\\2.jpg");
            lstPics.Add("C:\\3.jpg");
            if ((lstLoc == lstPics.Count))
            {
                lstLoc = 0;
            }

            string strLoc = lstPics[lstLoc];
            lstLoc++;

            using (Bitmap bmp = (Bitmap)Bitmap.FromFile(strLoc))
            using (Graphics grDest = Graphics.FromHwnd(pictureBox1.Handle))
            using (Graphics grSrc = Graphics.FromImage(bmp))
            {
                IntPtr hdcDest = IntPtr.Zero;
                IntPtr hdcSrc = IntPtr.Zero;
                IntPtr hBitmap = IntPtr.Zero;
                IntPtr hOldObject = IntPtr.Zero;

                try
                {
                    hdcDest = grDest.GetHdc();
                    hdcSrc = grSrc.GetHdc();
                    hBitmap = bmp.GetHbitmap();

                    hOldObject = SelectObject(hdcSrc, hBitmap);
                    if (hOldObject == IntPtr.Zero)
                        throw new Win32Exception();

                    if (!BitBlt(hdcDest, 0, 0, pictureBox1.Width, pictureBox1.Height,
                        hdcSrc, 0, 0, 0x00CC0020U))
                        throw new Win32Exception();
                }
                finally
                {
                    if (hOldObject != IntPtr.Zero) SelectObject(hdcSrc, hOldObject);
                    if (hBitmap != IntPtr.Zero) DeleteObject(hBitmap);
                    if (hdcDest != IntPtr.Zero) grDest.ReleaseHdc(hdcDest);
                    if (hdcSrc != IntPtr.Zero) grSrc.ReleaseHdc(hdcSrc);
                }
            }
        }
    }
}

VB.net version (converted by me). Open new project, add 1 button and 1 picturebox, modify lstPics.Add():

Imports System.ComponentModel

Public Class Form1
    Public Declare Function SelectObject Lib "gdi32.dll" Alias "SelectObject" (ByVal hdc As System.IntPtr, ByVal h As System.IntPtr) As System.IntPtr
    Public Declare Function DeleteObject Lib "gdi32.dll" Alias "DeleteObject" (ByVal ho As System.IntPtr) As Boolean
    Public Declare Function BitBlt Lib "gdi32.dll" Alias "BitBlt" (ByVal hdc As System.IntPtr, ByVal x As Integer, ByVal y As Integer, ByVal cx As Integer, ByVal cy As Integer, ByVal hdcSrc As System.IntPtr, ByVal x1 As Integer, ByVal y1 As Integer, ByVal rop As UInteger) As Boolean

    Public lstLoc As Int16 = 0

    Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
        Dim mil1 As Int64 = 0
        Dim mil2 As Int64 = 0
        Dim mil3 As Int64 = 0

        Dim lstPics As New List(Of String)
        lstPics.Add("C:\1.jpg")
        lstPics.Add("C:\2.jpg")
        lstPics.Add("C:\3.jpg")

        If lstLoc = lstPics.Count Then lstLoc = 0
        Dim strLoc As String = lstPics(lstLoc)
        lstLoc += 1

        Using bmp As Bitmap = Bitmap.FromFile(strLoc),
            grDest As Graphics = Graphics.FromHwnd(PictureBox1.Handle),
            grSrc As Graphics = Graphics.FromImage(bmp)

            Dim hdcDest As IntPtr = IntPtr.Zero
            Dim hdcSrc As IntPtr = IntPtr.Zero
            Dim hBitmap As IntPtr = IntPtr.Zero
            Dim hOldObject As IntPtr = IntPtr.Zero

            Try
                hdcDest = grDest.GetHdc()
                hdcSrc = grSrc.GetHdc()
                hBitmap = bmp.GetHbitmap()

                hOldObject = SelectObject(hdcSrc, bmp.GetHbitmap)

                If (hOldObject = IntPtr.Zero) Then Throw New Win32Exception()

                If Not BitBlt(hdcDest, 0, 0, PictureBox1.Width, PictureBox1.Height, hdcSrc, 0, 0, 13369376) Then Throw New Win32Exception()

            Catch ex As Exception
                MessageBox.Show(ex.Message.ToString & vbNewLine & vbNewLine & ex.ToString)

            Finally
                If (hOldObject <> IntPtr.Zero) Then SelectObject(hdcSrc, hOldObject)
                If (hBitmap <> IntPtr.Zero) Then DeleteObject(hBitmap)
                If (hdcDest <> IntPtr.Zero) Then grDest.ReleaseHdc(hdcDest)
                If (hdcSrc <> IntPtr.Zero) Then grSrc.ReleaseHdc(hdcSrc)

            End Try

        End Using

    End Sub

End Class

When you push the button and cycle through different pictures, the C# version will spike in memory usage but then go back down. The VB.net version however will keep rising in memory usage. Where is this leak coming from, and why does it only happen in VB.net?

I know I have other options available, such as DrawImage or displaying directly in the picturebox. I wanted to use BitBlt for the speed. And wanted to test it out and get it working before putting it into main application (which handles images).

Thanks for any help.

And bonus question, is there a way to get memory usage down in the above code (mainly when it spikes). Just wanna make sure I'm not being wasteful. Thanks.

TimothyC
  • 55
  • 9
  • 1
    The C# version properly wraps the 2 `Graphics` objects in `using` blocks to dispose of them. Your VB version does not. – Ňɏssa Pøngjǣrdenlarp Oct 23 '16 at 00:32
  • 1
    @Plutonix: Actually, he is - in VB you can combine more than one object in a single 'Using'. – Dave Doknjas Oct 23 '16 at 00:43
  • Ah, yes - I didnt notice the trailing commas, sorry – Ňɏssa Pøngjǣrdenlarp Oct 23 '16 at 00:44
  • I notice that you didn't try to reproduce the attribute usage on the 'declare' statements (on the parameters and return types). I don't know if this is part of the problem, but you should reproduce the attributes if you know that the original C# code works. – Dave Doknjas Oct 23 '16 at 00:46
  • @Dave Doknjas Oh, right I forgot to mention that. All those except 'using System.Runtime.InteropServices;' are added by default when starting a new c# project, so I just left them there. I did add it to the VB.net project, but it wasn't used at all. – TimothyC Oct 23 '16 at 00:54
  • No, I meant the attributes on the parameters, such as [In()] - these can translate to VB's . Also, the [return: MarshalAs(UnmanagedType.Bool)] can translate similarly. – Dave Doknjas Oct 23 '16 at 00:56
  • Doesn't answer the question, but try using [Instant VB](http://www.tangiblesoftwaresolutions.com/Product_Details/Instant_VB.html) for your C# to VB.NET conversion needs. It works wonderfully and there's a demo version available. Edit: I don't work there. – Jed Burke Oct 23 '16 at 00:58
  • @Dave Doknjas Ah, I did not know that thank you. I'll try that soon. – TimothyC Oct 23 '16 at 01:00
  • @Jed Burke Thank you for the link! – TimothyC Oct 23 '16 at 01:27
  • No problem, I hope it helps you in the long run. – Jed Burke Oct 23 '16 at 01:30
  • @Dave Doknjas Alright, I converted and replaced them. It didn't fix the memory leak, but now I do have it translated properly, thank you. – TimothyC Oct 23 '16 at 01:36

1 Answers1

1

The VB version calls bmp.GetHbitmap() twice:

hBitmap = bmp.GetHbitmap()
hOldObject = SelectObject(hdcSrc, bmp.GetHbitmap)
'                                      ^^^ Here it is again

While the C# version only calls it once:

hBitmap = bmp.GetHbitmap();
hOldObject = SelectObject(hdcSrc, hBitmap);
//                                ^^^ uses the handle from the previous line

Note these excerpts from the documentation on GetHbitmap:

Creates a GDI bitmap object from this Bitmap.
...

Remarks:

You are responsible for calling the GDI DeleteObject method to free the memory used by the GDI bitmap object.

So the GetHbitmap() method is confusingly named, in that it doesn't just give you the handle that's already there, but actually creates a new GDI resource you must clean up. To make the VB code equivalent to the C#, do this:

hBitmap = bmp.GetHbitmap()
hOldObject = SelectObject(hdcSrc, hBitmap)
Joel Coehoorn
  • 399,467
  • 113
  • 570
  • 794