Solved

How does VB6 manage memory allocated by API calls?

Posted on 2010-11-08
9
1,402 Views
Last Modified: 2013-12-03
Hello,
From my understanding, VB6 cleans up memory when there are no longer any variables pointing to that piece of memory anymore (either those variables are out of scope or are now pointing elsewhere), but what happens when an API call returns a pointer to somewhere in memory.  If VB only knows about the variable containing the pointer and not the actual memory the pointer is referring to, will it ever clean up the memory being pointed to?  Or do I need to do something else to free up the memory?

Here's an example of what I'm talking about, where I tried to get the name of a printer using the GetPrinter API:
 
Private Declare Function GetPrinter Lib "winspool.drv" Alias "GetPrinterA" (ByVal hPrinter As Long, _

   ByVal Level As Long, pPrinter As Any, ByVal cbBuf As Long, pcbNeeded As Long) As Long

   

Private Declare Sub CopyMemory Lib "kernel32" Alias "RtlMoveMemory" (Destination As Any, _

   Source As Any, ByVal Length As Long)

   

Private Declare Function lstrlenA Lib "kernel32" (ByVal lpString As Long) As Long



Private Function GetPrinterName(hdc As Long) As String

  Dim lResult As Long

  Dim lBuffer() As Long

  Dim lBufferSize As Long

  Dim lSize As Long

  

  lBufferSize = 12

  ReDim lBuffer(0 To lBufferSize - 1) As Long

  

  lResult = GetPrinter(hdc, 4, lBuffer(0), lBufferSize, lBufferSize)

  If lResult = 0 Then

    'Try resizing the buffer

    If Err.LastDllError = ERROR_INSUFFICIENT_BUFFER Then

      ReDim lBuffer(0 To lBufferSize) As Long

      lResult = GetPrinter(hdc, 4, lBuffer(0), lBufferSize, lBufferSize)

      If lResult = 0 Then

        'Did not work

        Exit Function

      End If

    Else

      'Did not work

      Exit Function

    End If

  End If

  

  GetPrinterName = PointerToStringA(lBuffer(0))

End Function



Private Function PointerToStringA(ByVal lpStringA As Long) As String

   Dim Buffer() As Byte

   Dim nLen As Long

   

   If lpStringA Then

      nLen = lstrlenA(ByVal lpStringA)

      If nLen Then

         ReDim Buffer(0 To (nLen - 1)) As Byte

         CopyMemory Buffer(0), ByVal lpStringA, nLen

         PointerToStringA = StrConv(Buffer, vbUnicode)

      End If

   End If

End Function

Open in new window


In the code above (in the GetPrinterName function) lbuffer is passed in place of a Printer_Info_4 type as defined below:

typedef struct _PRINTER_INFO_4 {
  LPTSTR pPrinterName;
  LPTSTR pServerName;
  DWORD  Attributes;
} PRINTER_INFO_4, *PPRINTER_INFO_4;

So the first two longs in lbuffer are pointers  to strings in memory.  But I never access the second string as I have no need for it.  So does it just sit there in memory? Am I right to assume VB will handle the first string's memory?

Thanks in advance for your help.
Regards,
Aaron
0
Comment
Question by:SwingDancer
  • 6
  • 3
9 Comments
 
LVL 29

Expert Comment

by:nffvrxqgrcfqvvc
ID: 34089336
If your this concerned you may want to consider using the Heap Functions which would allow more memory managment control.
1) Allocate the memory on the heap
2) Pull the data you want from the memory.
3) Free the memory leaving only the pulled data in memory.
This gives you complete control over both allocating and deallocation of the memory, the only remaining memory is what you copied from the memory before releasing it.
Heap Functions
http://msdn.microsoft.com/en-us/library/aa366711(v=VS.85).aspx
 
0
 

Author Comment

by:SwingDancer
ID: 34089462
Thank you egl1044 for your answer.  The heap functions look interesting.  I think I need to do a fair bit of reading before I fully understand how they work and what they do.
However, right now I want to understand what Visual Basic 6 does with memory allocated by API calls such as GetPrinter. Does it automatically clean up the memory and if so how/when?  If not, what do I need to do to free it up?
0
 
LVL 29

Accepted Solution

by:
nffvrxqgrcfqvvc earned 500 total points
ID: 34089527
The function copies the information into a buffer that you allocate so the memory and its size is what you have allocated in the byte array before calling the function. The structure is only used as a means to easily copy the first 12 bytes of that allocated array allowing you do easily know the pointers to the strings in that byte array buffer. When you use CopyMemory your simply taking the bytes from your allocated byte array and copying them into another variable and converting it into a string.
If you wanted to invalidate the allocated memory you could use Erase on the array but this doesn't actually release the memory it simply's removes any data in the array to zero.
Ex. Erase Buffer
If you use Heap functions on the other hand you have much more control over the allocation,reallocation, and deallocation.
Your example now would become something like this using the Heap Functions. Hopefully it all makes sense =)

Option Explicit



Private Const HEAP_ZERO_MEMORY = &H8&



Private Type PRINTER_INFO_4W

pPrinterName        As Long

pServerName         As Long

Attributes          As Long

End Type



Private Declare Function GetProcessHeap Lib "kernel32.dll" () As Long

Private Declare Function HeapAlloc Lib "kernel32.dll" (ByVal hHeap As Long, ByVal dwFlags As Long, ByVal dwBytes As Long) As Long

Private Declare Function HeapReAlloc Lib "kernel32.dll" (ByVal hHeap As Long, ByVal dwFlags As Long, ByVal lpMem As Long, ByVal dwBytes As Long) As Long

Private Declare Function HeapSize Lib "kernel32.dll" (ByVal hHeap As Long, ByVal dwFlags As Long, ByVal lpMem As Long) As Long

Private Declare Function HeapFree Lib "kernel32.dll" (ByVal hHeap As Long, ByVal dwFlags As Long, ByVal lpMem As Long) As Long



Private Declare Function OpenPrinterW Lib "winspool.drv" (ByVal pPrinterName As Long, ByRef phPrinter As Long, ByVal pDefault As Long) As Long

Private Declare Function GetPrinterW Lib "winspool.drv" (ByVal hPrinter As Long, ByVal Level As Long, ByVal pPrinter As Long, ByVal cbBuf As Long, ByRef pcbNeeded As Long) As Long

Private Declare Function ClosePrinter Lib "winspool.drv" (ByVal hPrinter As Long) As Long



Private Declare Sub RtlMoveMemory Lib "kernel32.dll" (pDest As Any, pSrc As Any, ByVal ByteLen As Long)



 

Private Sub Form_Load()



    Dim printerInfo4    As PRINTER_INFO_4W

    Dim Buffer          As Long ' heap buffer

    Dim hPrinter        As Long

    Dim cbNeeded        As Long

    

    If OpenPrinterW(StrPtr("HP Deskjet 3920/3940"), hPrinter, 0) Then

        '// Allocate memory

        Buffer = HeapAlloc(GetProcessHeap, HEAP_ZERO_MEMORY, 12)

        Call GetPrinterW(hPrinter, 4, Buffer, 12, cbNeeded)

        '// Re-allocate the required size

        Buffer = HeapReAlloc(GetProcessHeap, 0, Buffer, cbNeeded)

        '// Get the printer level 4 information

        Call GetPrinterW(hPrinter, 4, Buffer, HeapSize(GetProcessHeap, 0, Buffer), cbNeeded)

        '// Make the pointers easily accessible.

        RtlMoveMemory printerInfo4, ByVal Buffer&, Len(printerInfo4)

        ' If you were to free this memory before you pulled the data

        ' it would fail because you (released) the memory the string

        ' pointers are allocated within this memory buffer so you must

        ' do the grunt work before releasing the memory.

        Debug.Print printerInfo4.pPrinterName

        Debug.Print printerInfo4.pServerName

        Debug.Print printerInfo4.Attributes

        

        ' When the memory is freed here the pointers are no longer

        ' valid within the allocated buffer. Nothing to point towards.

        

        '// free memory

        HeapFree GetProcessHeap, 0, Buffer

        

        ClosePrinter hPrinter

    End If



End Sub

Open in new window

0
 
LVL 29

Expert Comment

by:nffvrxqgrcfqvvc
ID: 34089616
Here is a full example using heap functions, some minor modifications from the previous post, you could also just use GetDefaultPrinter() API or even the Printer Class in VB directly >> (Printer.Name) If your just after the local printer name.
Option Explicit



Private Const HEAP_ZERO_MEMORY = &H8&



Private Type PRINTER_INFO_4W

pPrinterName        As Long

pServerName         As Long

Attributes          As Long

End Type



Private Declare Function GetProcessHeap Lib "kernel32.dll" () As Long

Private Declare Function HeapAlloc Lib "kernel32.dll" (ByVal hHeap As Long, ByVal dwFlags As Long, ByVal dwBytes As Long) As Long

Private Declare Function HeapReAlloc Lib "kernel32.dll" (ByVal hHeap As Long, ByVal dwFlags As Long, ByVal lpMem As Long, ByVal dwBytes As Long) As Long

Private Declare Function HeapSize Lib "kernel32.dll" (ByVal hHeap As Long, ByVal dwFlags As Long, ByVal lpMem As Long) As Long

Private Declare Function HeapFree Lib "kernel32.dll" (ByVal hHeap As Long, ByVal dwFlags As Long, ByVal lpMem As Long) As Long



Private Declare Function OpenPrinterW Lib "winspool.drv" (ByVal pPrinterName As Long, ByRef phPrinter As Long, ByVal pDefault As Long) As Long

Private Declare Function GetPrinterW Lib "winspool.drv" (ByVal hPrinter As Long, ByVal Level As Long, ByVal pPrinter As Long, ByVal cbBuf As Long, ByRef pcbNeeded As Long) As Long

Private Declare Function ClosePrinter Lib "winspool.drv" (ByVal hPrinter As Long) As Long



Private Declare Sub RtlMoveMemory Lib "kernel32.dll" (pDest As Any, pSrc As Any, ByVal ByteLen As Long)

Private Declare Function lstrlenW Lib "kernel32.dll" (ByVal ptr As Long) As Long



Private Sub Form_Load()



    Dim printerInfo4    As PRINTER_INFO_4W

    Dim Buffer          As Long ' heap buffer

    Dim hPrinter        As Long

    Dim cbNeeded        As Long

    

    If OpenPrinterW(StrPtr("HP Deskjet 3920/3940"), hPrinter, 0) Then

        '// Allocate memory

        Buffer = HeapAlloc(GetProcessHeap, HEAP_ZERO_MEMORY, 12)

        Call GetPrinterW(hPrinter, 4, Buffer, 12, cbNeeded)

        '// Re-allocate the required size

        Buffer = HeapReAlloc(GetProcessHeap, HEAP_ZERO_MEMORY, Buffer, cbNeeded)

        '// Get the printer level 4 information

        Call GetPrinterW(hPrinter, 4, Buffer, HeapSize(GetProcessHeap, 0, Buffer), cbNeeded)

        '// Make the pointers easily accessible.

        RtlMoveMemory printerInfo4, ByVal Buffer&, Len(printerInfo4)

        ' If you were to free this memory before you pulled the data

        ' it would fail because you (released) the memory. The string

        ' are allocated within this memory buffer so you must do the

        ' grunt work before releasing the memory.

        Debug.Print printerInfo4.pPrinterName

        Debug.Print printerInfo4.pServerName

        Debug.Print printerInfo4.Attributes

        

        MsgBox WidePointerString(printerInfo4.pPrinterName)

        

        ' When the memory is freed here the pointers are no longer

        ' valid within the allocated buffer. Nothing to point towards.

        

        '// free memory

         HeapFree GetProcessHeap, 0, Buffer

        

         ' The memory is no longer valid shouldn't produce a result.

         ' Note: You may need compile to executable and not test from IDE.

         MsgBox WidePointerString(printerInfo4.pPrinterName)

    

        ClosePrinter hPrinter

    End If



End Sub



Private Function WidePointerString(ByVal lpPointer As Long) As String

    ' Return WIDE pointer string

    Dim Buffer()    As Byte

    Dim lpSize      As Long

    

    If lpPointer = 0 Then

        WidePointerString = vbNullString

        Exit Function

    End If

    

    lpSize = lstrlenW(lpPointer) * 2

    

    If lpSize <> 0 Then

        ReDim Buffer(lpSize) As Byte

        RtlMoveMemory Buffer(0), ByVal lpPointer, lpSize

        WidePointerString = Buffer

    End If

    

    Erase Buffer

End Function

Open in new window

0
Find Ransomware Secrets With All-Source Analysis

Ransomware has become a major concern for organizations; its prevalence has grown due to past successes achieved by threat actors. While each ransomware variant is different, we’ve seen some common tactics and trends used among the authors of the malware.

 
LVL 29

Expert Comment

by:nffvrxqgrcfqvvc
ID: 34089748
<< Does it automatically clean up the memory and if so how/when?  If not, what do I need to do to free it up? >>
My Bad.. I missed this but simply YOU are allocating the memory not the API in this case, although some API that allocate there own memory will have a way to free that memory, but this isn't one of  these cases. When you allocate the array the data simply is copied into it and if your byte array is located at the local level it will be cleaned up when the call exits the method. However if you allocated that same byte array at the module level it would retain in memory.
0
 

Author Comment

by:SwingDancer
ID: 34090475
Thanks egl1044.
The more I read of the MSDN documentation the more sense the heaps make.  (When I first looked I was like, 'What's a heap' and 'what's a process' and 'what does it mean to allocate').
I do have a couple of questions.
1. In your example you called GetProcessHeap multiple times instead of calling it once and storing it in a variable.  Does the value returned from GetProcessHeap change?
2.  Why would someone choose to use the default heap over a private heap and vice-versa?
3. I'm still unsure about what VB does if I am not using heaps.  Eg.  If I were not using heaps and the GetPrinterName function (from my original code sample) was called lots of times would I start to run out of memory?  Would this memory get cleared once the program was closed?
0
 
LVL 29

Expert Comment

by:nffvrxqgrcfqvvc
ID: 34093772
<< In your example you called GetProcessHeap multiple times instead of calling it once and storing it in a variable. >>
Good question! I have been doing that way for awhile it will point to the same default heap value almost all the MSDN examples that use the heap functions always call GetProcessHeap() so I think it stuck with me to use it that way.
<< Why would someone choose to use the default heap over a private heap and vice-versa >>
Creating a private heap is more for threading, something you don't have to worry about in VB6 since it's single threaded. If you had multiple threads you might create a private heap for each thread so different threads do not attempt to access the same memory.
<< I'm still unsure about what VB does if I am not using heaps.  >>
No way! You have lots of memory yet and it won't run out especially with your example. When you allocate the array in VB6 it probrably uses LocalAlloc() internally im fairly certain that any local members are freed when the method returns. If you allocate the array at the module level it would retain in memory. There is not much difference other than with a heap you have the ability to allocate a block of bytes , re-allocate a block, and deallocate the block. It provides an easier means to manage the memory but you can still use a byte array in VB6 without issues.
Your code would allocate say maybe 64 bytes (enough to hold the string data to be copied into the buffer) for the printer information and then cleanup when routine exits the method. Thus the only memory footprint you have when calling the function is the size of the buffer you allocated before calling the API. If you on the other hand read a large file into a string or byte array then your application is going to use as much memory as you read into that variable until it's released.
0
 

Author Comment

by:SwingDancer
ID: 34098441
Thank you egl1044 for all your explanations.
- Aaron
0
 
LVL 29

Expert Comment

by:nffvrxqgrcfqvvc
ID: 34098462
No problem buddy,  Welcome to EE we hope you stick around =)
0

Featured Post

IT, Stop Being Called Into Every Meeting

Highfive is so simple that setting up every meeting room takes just minutes and every employee will be able to start or join a call from any room with ease. Never be called into a meeting just to get it started again. This is how video conferencing should work!

Join & Write a Comment

Software development teams often use in-memory caches to improve performance. They want to speed up access to, or reduce load on, a backing store (database, file system, etc.) by keeping some or all of the data in memory.   You should implement a …
You can of course define an array to hold data that is of a particular type like an array of Strings to hold customer names or an array of Doubles to hold customer sales, but what do you do if you want to coordinate that data? This article describes…
Get people started with the process of using Access VBA to control Outlook using automation, Microsoft Access can control other applications. An example is the ability to programmatically talk to Microsoft Outlook. Using automation, an Access applic…
Get people started with the utilization of class modules. Class modules can be a powerful tool in Microsoft Access. They allow you to create self-contained objects that encapsulate functionality. They can easily hide the complexity of a process from…

744 members asked questions and received personalized solutions in the past 7 days.

Join the community of 500,000 technology professionals and ask your questions.

Join & Ask a Question

Need Help in Real-Time?

Connect with top rated Experts

11 Experts available now in Live!

Get 1:1 Help Now