TimothyC TimothyC - 1 month ago 39
C# Question

VB.NET/C#; Using BitBlt; Using same code in both, memory leak appearing in VB.NET but not C#

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.

Answer

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)