Results 1 to 13 of 13

Thread: Is there a memory leak in my code?

  1. #1

    Thread Starter
    Fanatic Member
    Join Date
    Jul 2017
    Posts
    760

    Is there a memory leak in my code?

    Hello!

    I have some code to take a screenshot of the bitmap.
    After like 300 runs, the VB6 IDE says good-bye with some graphic error. I know this behaviour from memory leaks.

    I have looked through my screencode-code again and again, but I don't see any memory leak.

    Can somebody tell me if there indeed isn't any leak? At the end of the code I do something else (using LaVolpe's c32bppDIB class, but I don't think that it doesn't have any errors, so I will leave this out for a start).

    Thank you!

    Code:
    Option Explicit
    
    Public c As c32bppDIB
    
    Private hDCSrc As Long
    Private hDCMemory As Long
    Private hBmp As Long
    Private hBmpPrev As Long
    
    Private m_bCleant As Boolean
    
    Public Sub TakeScreenshot()
    
        Dim WndHandle&
        WndHandle = GetDesktopWindow
    
        'Get Window Size
        Dim WidthSrc As Long
        Dim HeightSrc As Long
        Dim rc As RECT
        GetWindowRect WndHandle, rc
        WidthSrc = rc.Right - rc.Left
        HeightSrc = rc.Bottom - rc.Top
        
        'Get Window  device context
        hDCSrc = GetWindowDC(WndHandle)
        
        'create a memory device context
        hDCMemory = CreateCompatibleDC(hDCSrc)
        
        'create a bitmap compatible with window hdc
        hBmp = CreateCompatibleBitmap(hDCSrc, WidthSrc, HeightSrc)
        
        'copy newly created bitmap into memory device context
        hBmpPrev = SelectObject(hDCMemory, hBmp)
    
        'copy window window hdc to memory hdc
        Call BitBlt(hDCMemory, 0, 0, WidthSrc, HeightSrc, hDCSrc, 0, 0, vbSrcCopy)
          
        'Get Bmp from memory Dc
        hBmp = SelectObject(hDCMemory, hBmpPrev)
    
        'copy window window hdc to memory hdc
        Call BitBlt(hDCMemory, 0, 0, WidthSrc, HeightSrc, hDCSrc, 0, 0, vbSrcCopy)
          
        Set c = New c32bppDIB
    
        Dim b As Boolean
        b = c.LoadPicture_ByHandle(hBmp)
        Debug.Assert b
        
    End Sub
    Public Sub CleanUp()
    
        hBmp = SelectObject(hDCMemory, hBmpPrev)
        DeleteObject hBmp
        
        DeleteDC hDCMemory
        
        Set c = Nothing
        
        m_bCleant = True
    
    End Sub
    Private Sub Class_Terminate()
    
        If Not m_bCleant Then
            Me.CleanUp
        End If
    
    End Sub
    And this is how I call it:

    Code:
    Private Sub btnTest_Click()
    
        Dim l&
        For l = 1 To 50
    
            Dim cSS As clsScreenshot
            Set cSS = New clsScreenshot
            cSS.TakeScreenshot
            cSS.c.SaveToFile_PNG "d:\blatest1.png", False
            cSS.CleanUp
            Set c = Nothing
            
        Next
    
    End Sub
    Last edited by tmighty2; Dec 17th, 2018 at 08:03 AM. Reason: I have included just everything now.

  2. #2
    Fanatic Member
    Join Date
    Jan 2015
    Posts
    641

    Re: Is there a memory leak in my code?

    You should put the code

    hBmp = SelectObject(hDCMemory, hBmpPrev)
    DeleteObject hBmp
    Call DeleteDC(hDCMemory)

    At the end of you procedure

  3. #3
    PowerPoster Zvoni's Avatar
    Join Date
    Sep 2012
    Location
    To the moon and then left
    Posts
    5,261

    Re: Is there a memory leak in my code?

    I think that's the classic mistake understanding how the Terminate-Event works.
    The Terminate-Event fires only when the last instance of the class is destroyed!
    Last edited by Zvoni; Tomorrow at 31:69 PM.
    ----------------------------------------------------------------------------------------

    One System to rule them all, One Code to find them,
    One IDE to bring them all, and to the Framework bind them,
    in the Land of Redmond, where the Windows lie
    ---------------------------------------------------------------------------------
    People call me crazy because i'm jumping out of perfectly fine airplanes.
    ---------------------------------------------------------------------------------
    Code is like a joke: If you have to explain it, it's bad

  4. #4
    PowerPoster dilettante's Avatar
    Join Date
    Feb 2006
    Posts
    24,487

    Re: Is there a memory leak in my code?

    Quote Originally Posted by Zvoni View Post
    I think that's the classic mistake understanding how the Terminate-Event works.
    The Terminate-Event fires only when the last instance of the class is destroyed!
    Um.. no.

    What you might be trying to say is that the event is raised when the last counted reference to a class instance is released.

  5. #5

    Thread Starter
    Fanatic Member
    Join Date
    Jul 2017
    Posts
    760

    Re: Is there a memory leak in my code?

    I am setting the class instance to "nothing" when I'm done.

    Like that:

    Code:
          Dim c As New clsScreenshot
          c.TakeScreenshot
          Set c = Nothing
    Is that sufficient to actually terminate the class?

  6. #6

    Thread Starter
    Fanatic Member
    Join Date
    Jul 2017
    Posts
    760

    Re: Is there a memory leak in my code?

    @Thierry69

    I'm doing something from "outside", so I can not call this clean-up code at the end of the procedure:

    I call a public sub of the class (which I have left out for readability... it features LaVolpe's c32bppDIB class), so I can not call this at the end of the procedure.

    What I'm actually doing is this:

    Code:
    Private Sub btnTest_Click()
    
        Dim c As clsScreenshot
        Set c = new clsScreenshot
        c.TakeScreenshot 
        c.c32bppDIBsuite.SaveToFile_PNG "d:\sometest1.png", False
        
        Set c = Nothing
        
    Exit Sub
    Last edited by tmighty2; Dec 17th, 2018 at 06:44 AM.

  7. #7
    VB-aholic & Lovin' It LaVolpe's Avatar
    Join Date
    Oct 2007
    Location
    Beside Waldo
    Posts
    19,541

    Re: Is there a memory leak in my code?

    There are a couple things to note:

    1. When calling GetWindowDC, call ReleaseDC later. See MSDN documentation

    2. You are leaking a bitmap.

    In TakeScreenshot, you have these lines
    - hBmp is created
    - hBmp is selected into DC and previous bmp placed in hBmpPrev (so far, ok)
    - hBmp is unselected from DC (ok, normal & hBmp is no longer in the DC)

    But hBmp is never destroyed. The class terminate does not destroy it either. When that event is triggered, hBmp is already out of the DC, you assign it to the DC's current Bmp and destroy that instead -- double whammy; not only are you not destroying something you created, you are destroying something you didn't create

    Recommendation: Do not hold onto hBmp. Destroy it when done with it during TakeScreenshot
    Insomnia is just a byproduct of, "It can't be done"

    Classics Enthusiast? Here's my 1969 Mustang Mach I Fastback. Her sister '67 Coupe has been adopted

    Newbie? Novice? Bored? Spend a few minutes browsing the FAQ section of the forum.
    Read the HitchHiker's Guide to Getting Help on the Forums.
    Here is the list of TAGs you can use to format your posts
    Here are VB6 Help Files online


    {Alpha Image Control} {Memory Leak FAQ} {Unicode Open/Save Dialog} {Resource Image Viewer/Extractor}
    {VB and DPI Tutorial} {Manifest Creator} {UserControl Button Template} {stdPicture Render Usage}

  8. #8
    PowerPoster Elroy's Avatar
    Join Date
    Jun 2014
    Location
    Near Nashville TN
    Posts
    10,909

    Re: Is there a memory leak in my code?

    Quote Originally Posted by tmighty2 View Post
    Is that sufficient to actually terminate the class?
    tmighty2,

    Here's the language I use. We've got classes, we've got objects, and we've got object variables that reference those objects.

    Now, I think of a class as a template for creating an instance (i.e., instantiating) an object. The actual machine (or p) code is re-entrant, so it really just stays the same. However, a unique set of internal variables is created for each instance of an object we create. So, here's the tricky part #1: We can use a class to instantiate more than one object, which we often do.

    Ok, moving on. When we instantiate an object from some class, we typically have some variable that references that new object. And here's tricky part #2. We often have multiple object variables that reference the same instantiated object. Internally, each instantiated object has a reference count. If things are done correctly, that object's internal reference count should equal the number of object variables that reference that object.

    So, to answer your question, an object's terminate event fires when that object's reference count goes to zero. Or, said a bit differently, an object's terminate event fires when the last object variable referencing it is set to "Nothing". And, to address a flaw in your above quote, classes aren't terminated ... only objects are terminated, and only when there are no longer any object variables referencing it.

    Here's an example that attempts to illustrate the concepts. I just put a single class (Class1) into a new project. The Form1 is also a class, but I just use some code in the Form_Load to illustrate the above concepts with Class1:

    Code:
    
    Option Explicit
    
    Private Sub Form_Load()
        Dim Object1_Variable1 As Class1
        Dim Object1_Variable2 As Class1
        Dim Object1_Variable3 As Class1
        '
        Dim Object2_Variable1 As Class1
        Dim Object2_Variable2 As Class1
        Dim Object2_Variable3 As Class1
    
    
    
        Set Object1_Variable1 = New Class1          ' Instantiate Object1 from Class1.
        Set Object1_Variable2 = Object1_Variable1   ' Create a second reference to Object1.
        Set Object1_Variable3 = Object1_Variable2   ' Create a third reference to Object1.
    
        Set Object2_Variable1 = New Class1          ' Instantiate Object2 from Class1.
        Set Object2_Variable2 = Object2_Variable1   ' Create a second reference to Object2.
        Set Object2_Variable3 = Object2_Variable1   ' Create a third reference to Object2.
    
    
        Set Object1_Variable1 = Nothing             ' Object1 still exists, but this reference is removed.
        Set Object1_Variable2 = Nothing             ' Object1 still exists, but this reference is removed.
        Set Object1_Variable3 = Nothing             ' Object1 is un-instantiated, and the "terminate" event will immediately fire before this code continues execution.
    
    
        ' When this code hits the "End Sub" (or an Exit Sub), all local variables will be freed.  Therefore, the Object2 will be un-instantiated, and it's "terminate" event will fire.
    End Sub
    
    

    Now, notice in the above that we only have one class. But we have two objects. And, for each of those objects, we have three references each (object variables referencing the objects).

    Maybe that'll help. So, to state again, an object is un-instantiated (with it's terminate event immediately fired) when its internal reference count goes to zero. And, if things are done correctly, that should correspond to when the last variable referencing that object is set to Nothing (which also happens when the variable is destroyed from its lifetime ending).

    Good Luck,
    Elroy
    Last edited by Elroy; Dec 17th, 2018 at 08:15 AM.
    Any software I post in these forums written by me is provided "AS IS" without warranty of any kind, expressed or implied, and permission is hereby granted, free of charge and without restriction, to any person obtaining a copy. To all, peace and happiness.

  9. #9

    Thread Starter
    Fanatic Member
    Join Date
    Jul 2017
    Posts
    760

    Re: Is there a memory leak in my code?

    @LaVolpe

    Thank you, but am I not destroying hBmp here?

    -->

    Code:
        hBmp = SelectObject(hDCMemory, hBmpPrev)
        DeleteObject hBmp
    I have changed my initial post to include just everything, I think that becomes important now.
    Last edited by tmighty2; Dec 17th, 2018 at 08:03 AM.

  10. #10
    VB-aholic & Lovin' It LaVolpe's Avatar
    Join Date
    Oct 2007
    Location
    Beside Waldo
    Posts
    19,541

    Re: Is there a memory leak in my code?

    Quote Originally Posted by tmighty2 View Post
    @LaVolpe

    Thank you, but am I not destroying hBmp here?
    No. In TakeScreenshot, you already have this line: hBmp = SelectObject(hDCMemory, hBmpPrev). That is removing hBmp from the DC and putting the previous one back into the DC. You then call the same line again in cleanup. Now, hBmp is the previous bitmap. You have lost/leaked the hBmp you originally created.

    Remove that line from cleanup and that leak should go away. You can verify this easily, after each of those 2 lines are called:
    1. Print out the value of hBmp you created in TakeScreenshot
    2. Print out the value of hBmp when you call cleanup

    To try to show what's happening...

    In Public Sub TakeScreenshot()

    hBmp = CreateCompatibleBitmap(hDCSrc, WidthSrc, HeightSrc)
    hBmp is YourBmp, hBmpPrev is 0
    hBmpPrev = SelectObject(hDCMemory, hBmp)
    hBmp is YourBmp, hBmpPrev is OtherBmp
    hBmp = SelectObject(hDCMemory, hBmpPrev)
    hBmp is YourBmp, hBmpPrev is OtherBmp

    In Public Sub CleanUp()

    hBmp = SelectObject(hDCMemory, hBmpPrev)
    hBmp is OtherBmp, hBmpPrev is Undefined

    Note: Cleanup line could actually fail and hBmp would be zero. It could fail because hBmpPrev may no longer be a valid bitmap, it is possible it may have already been destroyed by its creator (the Desktop)

    Your class probably should be reworked. The leak can occur if you call TakeScreenShot more than once without calling Cleanup between the calls. And in that case, why not just clean up after the screenshot? There is no point in keeping the bitmap around. Maybe keep the DC you created until the class terminates, but don't see a reason to keep the bitmap -- just a leak waiting to happen.

    Last but not least, once you are happy with your code, compile the project and run it in 150% to 175% DPI. You may be disappointed with the results unless you manifest the project as DPI aware. I try to remember to provide this note whenever I see screenshot-related code.
    Last edited by LaVolpe; Dec 17th, 2018 at 10:25 AM.
    Insomnia is just a byproduct of, "It can't be done"

    Classics Enthusiast? Here's my 1969 Mustang Mach I Fastback. Her sister '67 Coupe has been adopted

    Newbie? Novice? Bored? Spend a few minutes browsing the FAQ section of the forum.
    Read the HitchHiker's Guide to Getting Help on the Forums.
    Here is the list of TAGs you can use to format your posts
    Here are VB6 Help Files online


    {Alpha Image Control} {Memory Leak FAQ} {Unicode Open/Save Dialog} {Resource Image Viewer/Extractor}
    {VB and DPI Tutorial} {Manifest Creator} {UserControl Button Template} {stdPicture Render Usage}

  11. #11

    Thread Starter
    Fanatic Member
    Join Date
    Jul 2017
    Posts
    760

    Re: Is there a memory leak in my code?

    Thank you for the great explanation.

    I have reworked the class now, and I inserted statements to tell me that value of hBmp.
    Even after CleanUp, it stays a longer number (=not 0). Does that tell me that deleting it failed?

    I never call "TakeScreenshot" more than once.

    The huge leak went away, but I still lose 2 GDI objects per class instance.

    Is there anything in the part of the code that I wrote that would still leak? I don't see it.

    Here is the new, improved version of my class:

    Code:
    Option Explicit
    
    Public c As c32bppDIB
    
    Private hDCSrc As Long
    Private hDCMemory As Long
    Private hBmp As Long
    Private hBmpPrev As Long
    
    Private m_bCleant As Boolean
    
    Public Sub TakeScreenshot()
    
        Dim WndHandle&
        WndHandle = GetDesktopWindow
    
        'Get Window Size
        Dim WidthSrc As Long
        Dim HeightSrc As Long
        Dim rc As RECT
        GetWindowRect WndHandle, rc
        WidthSrc = rc.Right - rc.Left
        HeightSrc = rc.Bottom - rc.Top
        
        'Get Window  device context
        hDCSrc = GetWindowDC(WndHandle)
        
        'create a memory device context
        hDCMemory = CreateCompatibleDC(hDCSrc)
        
        'create a bitmap compatible with window hdc
        hBmp = CreateCompatibleBitmap(hDCSrc, WidthSrc, HeightSrc)
        'hBmp is YourBmp, hBmpPrev is 0
        
        Debug.Print "hbmp after createcompatible: " & hBmp
        
        'copy newly created bitmap into memory device context
        hBmpPrev = SelectObject(hDCMemory, hBmp)
        'hBmp is YourBmp, hBmpPrev is OtherBmp
    
        'copy window window hdc to memory hdc
        Call BitBlt(hDCMemory, 0, 0, WidthSrc, HeightSrc, hDCSrc, 0, 0, vbSrcCopy)
          
        'Get Bmp from memory Dc
        hBmp = SelectObject(hDCMemory, hBmpPrev)
        'hBmp is YourBmp, hBmpPrev is OtherBmp
    
        'copy window window hdc to memory hdc
        Call BitBlt(hDCMemory, 0, 0, WidthSrc, HeightSrc, hDCSrc, 0, 0, vbSrcCopy)
          
        Set c = New c32bppDIB
    
        Dim b As Boolean
        b = c.LoadPicture_ByHandle(hBmp)
        Debug.Assert b
        
    End Sub
    Public Sub CleanUp()
    
        DeleteObject hBmp
        
        DeleteDC hDCMemory
        
        Set c = Nothing
        
        m_bCleant = True
        
        Debug.Print "after cleanup: " & hBmp
    
    End Sub
    Private Sub Class_Terminate()
    
        If Not m_bCleant Then
            Me.CleanUp
        End If
    
    End Sub
    And that's how I call it:

    Code:
    Private Sub btnTest_Click()
    
        Dim l&
        For l = 1 To 50
    
            Dim cSS As clsScreenshot
            Set cSS = New clsScreenshot
            cSS.TakeScreenshot
            cSS.c.SaveToFile_PNG "d:\blatest1.png", False
            cSS.CleanUp
            Set cSS = Nothing
            
        Next l
        
    End Sub

  12. #12
    VB-aholic & Lovin' It LaVolpe's Avatar
    Join Date
    Oct 2007
    Location
    Beside Waldo
    Posts
    19,541

    Re: Is there a memory leak in my code?

    Well, when you run your project, ensure all IDE MDI child windows are closed and the immediate window also, if it applies. If they aren't, exit the running project, close those windows and try again. Only at that time, take the GDI handle count. If not, those windows are probably the reason for the 2 you noted.

    Now back to your revised class...
    Why do you have this line in there twice? Probably a mistake
    Call BitBlt(hDCMemory, 0, 0, WidthSrc, HeightSrc, hDCSrc, 0, 0, vbSrcCopy)

    Still recommend calling ReleaseDC before you exit your TakeScreenshot routine. I also think you should move your bitmap deletion to that routine instead of the cleanup, unless there is a reason to keep a large bitmap laying around, not being used? You probably should check whether hBmp and hDCMemory are non-zero before attempting to delete them. Also, reset their values to zero afterwards, so you know you deleted them.

    I don't recall whether the LoadPicture_ByHandle routine keeps the bitmap or copies it? If it keeps it, then leave the bitmap deletion where it is if that class doesn't delete it when the class terminates. If the class deletes the bitmap, then there is no reason for you to be deleting it.
    Insomnia is just a byproduct of, "It can't be done"

    Classics Enthusiast? Here's my 1969 Mustang Mach I Fastback. Her sister '67 Coupe has been adopted

    Newbie? Novice? Bored? Spend a few minutes browsing the FAQ section of the forum.
    Read the HitchHiker's Guide to Getting Help on the Forums.
    Here is the list of TAGs you can use to format your posts
    Here are VB6 Help Files online


    {Alpha Image Control} {Memory Leak FAQ} {Unicode Open/Save Dialog} {Resource Image Viewer/Extractor}
    {VB and DPI Tutorial} {Manifest Creator} {UserControl Button Template} {stdPicture Render Usage}

  13. #13

    Thread Starter
    Fanatic Member
    Join Date
    Jul 2017
    Posts
    760

    Re: Is there a memory leak in my code?

    Man, thank you!

    I totally forget adding

    Code:
        ReleaseDC WndHandle, hDCSrc
    That resolved the leak.

Tags for this Thread

Posting Permissions

  • You may not post new threads
  • You may not post replies
  • You may not post attachments
  • You may not edit your posts
  •  



Click Here to Expand Forum to Full Width