Solved

How does VB6 manage memory allocated by API calls?

Posted on 2010-11-08
9
1,452 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
Master Your Team's Linux and Cloud Stack!

The average business loses $13.5M per year to ineffective training (per 1,000 employees). Keep ahead of the competition and combine in-person quality with online cost and flexibility by training with Linux Academy.

 
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
 
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

Does Powershell have you tied up in knots?

Managing Active Directory does not always have to be complicated.  If you are spending more time trying instead of doing, then it's time to look at something else. For nearly 20 years, AD admins around the world have used one tool for day-to-day AD management: Hyena. Discover why

Question has a verified solution.

If you are experiencing a similar issue, please ask a related question

The CRUD Functions CRUD, meaning "Create, Read, Update, Delete (http://en.wikipedia.org/wiki/Create,_read,_update_and_delete)" is a common term to data base developers.  It describes the essential functions of data base table maintenance.  This art…
Introduction Many of the most common information processing tasks require sorting data sets.  For example, you may want to find the largest or smallest value in a collection.  Or you may want to order the data set in numeric or alphabetical order. …
As developers, we are not limited to the functions provided by the VBA language. In addition, we can call the functions that are part of the Windows operating system. These functions are part of the Windows API (Application Programming Interface). U…
Get people started with the process of using Access VBA to control Excel using automation, Microsoft Access can control other applications. An example is the ability to programmatically talk to Excel. Using automation, an Access application can laun…

773 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