Link to home
Create AccountLog in
Avatar of SwingDancer
SwingDancer

asked on

How does VB6 manage memory allocated by API calls?

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
Avatar of nffvrxqgrcfqvvc
nffvrxqgrcfqvvc

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
 
Avatar of SwingDancer

ASKER

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?
ASKER CERTIFIED SOLUTION
Avatar of nffvrxqgrcfqvvc
nffvrxqgrcfqvvc

Link to home
membership
Create an account to see this answer
Signing up is free. No credit card required.
Create Account
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

<< 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.
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?
<< 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.
Thank you egl1044 for all your explanations.
- Aaron
No problem buddy,  Welcome to EE we hope you stick around =)