Improve company productivity with a Business Account.Sign Up


API Shell Commands

Posted on 2000-03-16
Medium Priority
Last Modified: 2008-02-26
Here's the situation:

First the background:
We have a VB project which consists of 1 form and 11 Custom User Controls, each User control contains up to 300 individual controls.  (Don't ask, it was client insistence).  Because of the number of controls, we always seem to be on the edge of available memory.  We discovered that we weren't destroying all the UC objects which was leading to "Out of memory" errors.  That has been fixed.

Within one of the UC's we're using the CreateProcess API to launch other Apps (Word, Excel, IE, etc.).

If we launch the App, close the App, click on another UC then back, launch and App, close it, click a couple of UC's... we get "out of memory".  

Now we're also using the WaitforSingleObject API to determine when the launched APP is closed then we close the Process using the CloseHandle API.

The Problem:
We're still getting "out of Memory" errors when we launch and close the apps. We ran the Resource Manager and noticed that even when we close the App, we never recover all the memory resources.  Therefore, opening and closing Apps will eventually eat up all memory resources.

NOTE: this only is a problem within Windows 95/98.  We do see the memory resources eaten up in WIN NT but we haven't been able to get "out of memory".

The Client uses Windows 95 so switching to NT is not a viable option.

I'm assigning many points to this 'cause we really need the answer.
Question by:gabbro

Expert Comment

ID: 2623928
Try to use Shell function - even if it is not suitable for you try to see if the memory leak persists.

Also try to run some other program (prefferably written by you), which you are sure that does not have memory leaks. And check again if the problem persists.
LVL 14

Expert Comment

ID: 2623944
Are you closing the handles????

See the following microsoft KB article:

HOWTO: Launch a Win32 Application from Visual Basic 

LVL 15

Accepted Solution

ameba earned 4000 total points
ID: 2623974
UserControls with arrays of constituent controls may leak memory

But you should reduce memory usage: e.g. use lightweight controls
Free Tool: Subnet Calculator

The subnet calculator helps you design networks by taking an IP address and network mask and returning information such as network, broadcast address, and host range.

One of a set of tools we're offering as a way of saying thank you for being a part of the community.


Expert Comment

ID: 2623987
check out this may be of use
PSS ID Number: Q190217
Article last modified on 08-07-1998

The information in this article applies to:
 - Microsoft Visual Basic for Windows Learning, Professional, and
   Enterprise Editions, versions 5.0, 6.0
 - Microsoft Visual Basic Standard, Professional, and Enterprise Editions,
   16-bit and 32-bit, for Windows, version 4.0
In 16-bit versions of Visual Basic, you can determine free system resources
by calling the GetFreeSystemResources Windows API function. For 32-bit
versions of Visual Basic, there is no Win32 API function that provides that
information. To determine free system resources in a 32-bit Visual Basic
application, you must call a 16-bit application. This article includes
instructions to:
1. Create a 16-bit Visual Basic 4.0 OLE server that determines free system
   resources and provides that information to OLE clients.
2. Call the 16-bit OLE server from a 32-bit version of Visual Basic and get
   free system resources from it.
If your Visual Basic application loads a very large number of certain
controls, such as ComboBoxes, you may run out of system resources, which
will result in an "out of memory" error (error #7), and may cause Windows
to stop responding. You can monitor system resources with the 16-bit
Windows API function GetFreeSystemResources.
In Windows 95 and Windows 98, GetFreeSystemResources checks the percentage
of free memory in the following five resource heaps:
1. The 16-bit User heap (64K).
2. The 32-bit User window heap (2MB).
3. The 32-bit User menu heap (2MB).
4. The 16-bit GDI heap (64K).
5. The 32-bit GDI heap (2MB).
First, it finds the lowest of the three User heaps, and the lowest of the
two GDI heaps. The 16-bit, 64K heaps are almost always the lowest.
Next, for each of the two lowest, it divides the percentage free by the
percentage that was free when Windows was started.
Finally, it returns the lowest of the two.
For example, suppose the five heaps had the following percentages free at
the time Windows started:
   Heap                              Percentage Free
   =============================     ===============
   16-bit User heap (64K)            80%
   32-bit User window heap (2MB)     99%
   32-bit User menu heap (2MB)       99%
   16-bit GDI heap (64K)             75%
   32-bit GDI heap (2MB)             99%
Later, with some applications running, suppose the free memory in the five
heaps had dropped to these percentages:
   Heap                              Percentage Free
   =============================     ===============
   16-bit User heap (64K)            40%
   32-bit User window heap (2MB)     98%
   32-bit User menu heap (2MB)       98%
   16-bit GDI heap (64K)             25%
   32-bit GDI heap (2MB)             98%
GetFreeSystemResources starts by finding the lowest of the three User heaps
(40%), and the lowest of the two GDI heaps (25%). In both cases, the 16-
bit, 64K heap is lowest.
Next, GetFreeSystemResources divides the percentage free by the percentage
that was free when Windows was started. For the User heap, that's 40%/80% =
50%. For the GDI heap, it calculates 25%/75% = 33%.
Finally, it returns the lowest of the two, 33%.
Note, this behavior is different than GetFreeSystemResources in Windows 3.1
in two ways:
1. The three 2MB heaps did not exist in Windows 3.1.
2. It did not divide the percentage free from the percentage that was free
   at the time Windows started. As a result, it returned a lower
   percentage. In the example above, GetFreeSystemResources in Windows 3.1
   would have returned 25% instead of 33%.
There is no 32-bit version of GetFreeSystemResources. In Windows 95, 98,
and NT, GetFreeSystemResources has been replaced by GlobalMemoryStatus,
which returns the amount of free global memory. Because Windows 95, 98 and
NT handle system resources more efficiently than Windows 3.x, system
resources are usually less of a concern and free global memory is usually
more relevant. GlobalMemoryStatus is sometimes confused as being equivalent
to GetFreeSystemResources, but in fact it is designed to return entirely
different information. While GetFreeSystemResources returns the free
percentage of the GDI and User heaps, GlobalMemoryStatus returns the amount
of free global memory. In Windows 3.x, 95, and 98, Windows allocates a
fixed amount of memory to each of the GDI and User heaps at startup, and
can never allocate any more to them no matter how much global memory is
free. As a result, it is possible to run out of free system resources while
there is still abundant free memory. Because GlobalMemoryStatus does not
return information about the GDI and User heaps, it is not appropriate to
use GlobalMemoryStatus to monitor these system resources.
To determine free system resources in a 32-bit Visual Basic application,
you must create and call a 16-Bit application that calls
GetFreeSystemResources and exposes the information to other applications.
Note that Windows NT (all versions) is designed to dynamically allocate
memory to the GDI and User heaps as needed. GetFreeSystemResources always
returns 90% on Windows NT. Unlike other versions of Windows, Windows NT
does not run out of system resources and stop responding. It is highly
unlikely that your Visual Basic application will have any need to monitor
system resources on Windows NT.
Also note that Windows 95 and 98 manage system resources considerably more
efficiently than Windows 3.x. Many items formerly stored in the 64K User
and GDI heaps in Windows 3.x are now stored in the three 2MB heaps in
Windows 95 and 98. See the REFERENCES section below for more information.
The following steps show how to create a 16-bit Visual Basic 4.0 OLE server
that determines free system resources and provides that information to OLE
clients, and to call the 16-bit OLE server from a 32-bit version of Visual
Basic and get free system resources from it.
Note, the OLE Server must be created with 16-bit Visual Basic 4.0; it
cannot be created with Visual Basic 5.0 or later, or with 32-bit Visual
Basic 4.0.
If you do not have Visual Basic 4.0, but do have Visual Basic 1.0 for
Windows, 2.0, or 3.0, you can use DDE (Dynamic Data Exchange) rather than
OLE to provide the information to 32-bit applications. In Visual Basic 3.0,
see chapter 21 in the Visual Basic Programmer's Guide, "Communicating with
Other Applications."
If you are a C programmer, you can create a "flat thunking" DLL to provide
the information on Windows 95 and 98. You cannot do flat thunking directly
in Visual Basic, because it requires calling functions given only pointers
to their addresses in memory. See the REFERENCES section below for where to
find more information.
Step-by-Step Example
1. Start 16-bit Visual Basic 4.0. Note this will not work with 32-bit
   Visual Basic 4.0, or with Visual Basic 5.0 or later.
2. Create a new project. Form1 is added by default.
3. Choose Module from the Insert menu to add a standard module, and add
   the following code to it:
      Declare Function GetFreeSystemResources Lib "User" _
         (ByVal fuSysResource As Integer) As Integer
      Sub Main()
         'Stub only.
      End Sub
4. Choose Class Module from the Insert menu to add a class module, and add
   the following code to it:
      Public Function SystemResources()
         SystemResources = GetFreeSystemResources _
      End Function
5. Change the Name property of the class module to clsSR.
6. Change the Instancing property of the class module to Createable
7. Change the Public property of the class module to True.
8. Alternate click on Form1 in the Project window, and choose "Remove File"
   to remove the default form.
9. Choose Options from the Tools menu, and select the Project tab. Change
   the project name to prjSR, and then change StartMode to "OLE Server."
10. From the File menu, save the project, and make the EXE.
11. Start a 32-bit version of Visual Basic, such as Visual Basic 4.0
    32-bit, or version 5.0 or later.
12. Create a new project. Form1 is added by default.
13. Choose References from the Project menu (in 32-bit Visual Basic 4.0,
   choose References from the Tools menu), and check prjSR. Click OK.
14. Double-click on Form1, and add the following code to the Form_Load
      Dim oSR As New prjSR.clsSR
      MsgBox oSR.SystemResources & _
         "% of system resources are free."
      Set oSR = Nothing
15. Run the project. The Message Box will show the percentage of free
   system resources.
"Windows 95 System Programming Secrets," by Matt Pietrek, IDG Books
For more information on improvements in system resources handling in
Windows 95 and 98, please see the following article in the Microsoft
Knowledge Base:
   ARTICLE-ID: Q117744
   TITLE     : Explanation of System Resources in Windows 95


Author Comment

ID: 2623989
Handles are being closed using the CloseHandle method
LVL 10

Expert Comment

ID: 2624018
"The Client uses Windows 95 so switching to NT is not a viable option."

It's the only option. If you must run such heavyweight applications you must run a decent operating system. Your client's insistence is like demanding that you make their VW beetle go 300 mph.

Author Comment

ID: 2624039
Unfortunately the Client is the Government and anyone who's ever dealt with them, knows that they want a Jaguar for a VW...
LVL 143

Expert Comment

by:Guy Hengel [angelIII / a3]
ID: 2624058
You may try to use the Windows Scripting Host Objects to create a IWshShellObject from which you can call another Application with the Wait Option.
This object may resolve your problem.
Hope this helps
LVL 32

Expert Comment

ID: 2624086
"BUG: UserControls with Menus Cause Resource Leak"

"BUG: UserControl Containing Array of Controls Leaks Memory"

"INFO: Maximum Number of Controls on a Form"
LVL 14

Expert Comment

ID: 2624116
Actually, they want a jaguar and only want to pay for a 10-speed bicycle. ;-)

Instead of launching shelled process the way you are currently doing it, you could try this...

Add the following to a module:

Global ProgHandle As Long

Declare Sub SleepAPI Lib "kernel32" Alias "Sleep" (ByVal dwMilliseconds As Long)
Declare Function GetExitCodeProcess Lib "kernel32" _
    (ByVal hProcess As Long, lpExitCode As Long) As Long

Declare Function OpenProcess Lib "kernel32" _
    (ByVal dwDesiredAccess As Long, ByVal bInheritHandle As Long, _
    ByVal dwProcessId As Long) As Long

Declare Function CloseHandle Lib "kernel32" _
    (ByVal hObject As Long) As Long

Declare Function SysSetFocus Lib "user32" Alias "SetFocus" _
    (ByVal hwnd As Long) As Long
Function IsActive(hprog) As Long
    Dim hProc, RetVal As Long
    hProc = OpenProcess(0, False, hprog)
    If hProc <> 0 Then GetExitCodeProcess hProc, RetVal
    IsActive = (RetVal = 259)
    CloseHandle hProc
End Function


When you shell the program, do it like this, (remember ProgHandle is a global definition):

ProgHandle = Shell("notepad.exe", vbNormalFocus)

You can then make the following call to see if the shelled process is still running:

    If IsActive(ProgHandle) Then
    End If

The "If Active" can be put in a loop, so that you poll the shelled program to see if it has terminated instead of freezing your app completely until the shell terminates.

You can do it like this:

      If IsActive(ProgHandle) Then
         SleepAPI 55
         MsgBox "Execution completed"
         Exit Do
      End If

Remember, if you do it this way, your program events will still fire... So it would be a good idea to disable the button or menu item or form you use to get into this code before you start the DO...Loop.

You can then enable it again when the loop is complete.  That way you don't get multiple instanciations of the shell.


LVL 15

Expert Comment

ID: 2624144
I don't think this (shelling) causes memory leak.
Remember, you are 'on the edge', now you start another big app, and OS says 'this is too much'.

lightweight controls info:

Author Comment

ID: 2624200
It definitely appears to be the "shelling" that's causing the problem.  We monitor the system resources and see that every time we open and close an app we never fully recover the resources.

Here's a code snippet of the Shell mehtod we're using...

        hProcess As Long
        hThread As Long
        dwProcessId As Long
        dwThreadId As Long
End Type

Public Type StartupInfo
        cb As Long
        lpReserved As Long
        lpDesktop As Long
        lpTitle As Long
        dwX As Long
        dwY As Long
        dwXSize As Long
        dwYSize As Long
        dwXCountChars As Long
        dwYCountChars As Long
        dwFillAttribute As Long
        dwFlags As Long
        wShowWindow As Integer
        cbReserved2 As Integer
        lpReserved2 As Long
        hStdInput As Long
        hStdOutput As Long
        hStdError As Long
End Type

' some api functions
Public Declare Function GetCurrentProcessId Lib "kernel32" () As Long

Public Declare Function WaitForSingleObject Lib "kernel32" (ByVal hHandle As
Long, ByVal dwMilliseconds As Long) As Long

Public Declare Function CloseHandle Lib "kernel32" (ByVal hObject As Long)
As Long

Public Declare Function OpenProcess Lib "kernel32" (ByVal dwDesiredAccess As
Long, ByVal bInheritHandle As Long, ByVal
dwProcessId As Long) As Long

Public Declare Function GetExitCodeProcess Lib "kernel32" (ByVal hProcess As
Long, lpExitCode As Long) As Long

Public Declare Function TerminateProcess Lib "kernel32" (ByVal hProcess As
Long, ByVal uExitCode As Long) As Long
Public Declare Function FindExecutable Lib "shell32.dll" Alias
"FindExecutableA" (ByVal lpFile As String, ByVal lpDirectory As String,
ByVal lpResult As String) As Long

Public Declare Function CreateProcessFlat Lib "kernel32" Alias
"CreateProcessA" (ByVal lpApplicationName As Long, ByVal lpCommandLine As
String, ByVal lpProcessAttributes As Long, ByVal lpThreadAttributes As Long,
ByVal bInheritHandles As Long, ByVal dwCreationFlags As Long, ByVal
lpEnvironment As Long, ByVal lpCurrentDirectory As Long, lpStartupInfo As
StartupInfo, lpProcessInformation As PROCESS_INFORMATION) As Long

' constants used
Public Const STILL_ACTIVE = &H103
Public Const INFINITE = -1&
Public Const SYNCHRONIZE = &H100000
Public Const ERROR_ABNORMAL_EXIT = -1&

'  ShellEx : routine to run a program and wait for it's exit code.
'            uses CreateProcess, WaitForSingleObject, GetExitCodeProcess
'            and CloseHandle.
'  Returns : the program's exit code.
'  Errors  : Raises VB error on any api error.
Public Function ShellEx(ByVal CmdLine As String, Optional ByVal WaitDead As
Boolean) As Long
    Dim iExit       As Long
    Dim iResult     As Long
    Dim StartupInfo As StartupInfo

    ' run it
    StartupInfo.cb = Len(StartupInfo)                   'startupinfo
    If CreateProcessFlat(0, CmdLine, 0, 0, 0, 0, 0, 0, StartupInfo,
ProcessInfo) <> 0 Then

        ' initialize the exit code to default error value

        If WaitDead Then
            ' freeze until process terminates
            iResult = WaitForSingleObject(ProcessInfo.hProcess, INFINITE)
            If iResult = WAIT_FAILED Then
                iResult = 0
                ' get the exit code
                iResult = GetExitCodeProcess(ProcessInfo.hProcess, iExit)
            End If
            ' try getting the exit code
            iResult = GetExitCodeProcess(ProcessInfo.hProcess, iExit)
            ' wait...
            Do While (iExit = STILL_ACTIVE) And (iResult <> 0)
                Call GetExitCodeProcess(ProcessInfo.hProcess, iExit)
        End If

        ' close the handle - just in case
        Call CloseHandle(ProcessInfo.hProcess)
        Call CloseHandle(ProcessInfo.hThread)

    End If

    ' error check
    If iResult = 0 Then
        Err.Raise Err.LastDllError
    End If

    ' return the exit code
    ShellEx = iExit
End Function


Expert Comment

ID: 2624226
is there any reason preventing you using
an OLE control ????

Author Comment

ID: 2624260
Unfortunatley, it's not possible to use an OLE Control within a Custom User Control.  We tried.
LVL 14

Expert Comment

ID: 2624273
So do you think you'll try the alternate example of shelling and waiting that I gave you??

LVL 15

Expert Comment

ID: 2624291
Try measuring resources *without* your app:
before loading IE, Word...
and after closing.
LVL 15

Expert Comment

ID: 2624374
Found a sample with CreateProcessA. It has parameter bInheritHandles = 1 (you are using 0), but I don't think this is important.
Do you use control arrays in your UC (bug mentioned twice)

Author Comment

ID: 2624389
All our controls within the Custom controls are Control Arrays.  This was required due to the limitation of 255 individual controls per form.
LVL 15

Expert Comment

ID: 2624423
Thx, gab
(I would normally say: "Thanks, gabbro", but this is to save some memory and some disk space on EE) :)

Expert Comment

ID: 2624455
Unfortunately you are pushing the limits of Windows 95/98 system resources by loading this many control (windows) at one time. Remember that although Windows 95/98 provides for  significantly more system resources than Win 3.1, the model is the same (for backward compatibility) and there still is a limitation.

Windows NT/2000 does not suffer from this problem since it implements a different model and does not have the same limitation.


Expert Comment

ID: 2624474
How are you "closing" the app? For example, if you run Word from your you

(1) Close it by clicking the little X button in the top right corner of the Word application window


(2) Call TerminateProcess?

Note the following comment in the documentation for TerminateProcess...

Terminating a process does not necessarily remove the process object from the system. A process object is deleted when the last handle to the process is closed.

Just offering ideas.

Expert Comment

ID: 2624484
Also this comment from the same documentation...

The state of global data maintained by dynamic-link libraries (DLLs) may be compromised if TerminateProcess is used rather than ExitProcess.

If you are calling TerminateProcess, you aren't giving the running application a chance to properly clean itself up.

Author Comment

ID: 2624506

We tried, but no luck.  Still having "memory leaks"

Author Comment

ID: 2624525

The app is closed by the app (ie exit or little "x").  We also have a wait loop waiting for the app to close and then we go in to "clean up" any remaining handles or processes.
LVL 14

Expert Comment

ID: 2624719
Just out of curiosity, you've got all of these controls on your form... Do they all have their own name or did you index them??  Indexing takes up less space...


Author Comment

ID: 2625005

The controls are indexed...
LVL 14

Expert Comment

ID: 2625278
Well, Here's a radical idea for you if you don't get a solution to your memory leak... You could monitor the system resources in your program by using the pBGetFreeSystemResources API defined in the rsrc32.dll.  It requires both the rsrc32.dll and the rsrc16.dll.  These DLL's are only available on windows 95, but you can move them to windows 98 and run them with no problem...

Set up the following in a module:

   Declare Function pBGetFreeSystemResources Lib "rsrc32.dll" _
      Alias "_MyGetFreeSystemResources32@4" (ByVal iResType As Integer) As Integer

Then you can do this:

    Const SR = 0
    Const GDI = 1
    Const USR = 2
    Dim UserFree As Integer
    Dim SysFree As Integer
    Dim GDIFree As Integer
    UserFree = pBGetFreeSystemResources(USR)
    SysFree = pBGetFreeSystemResources(SR)
    GDIFree = pBGetFreeSystemResources(GDI)

The above values will contain a percentage of free resources...  If your percentage drops below a threshold that you set, notify the user that they are running low on memory...

You could also take a "snapshot" of where you are and shutdown and restart the program right where they left off...



Expert Comment

ID: 2625335
Just for grins, call CloseHandle on the process a few more times...see if it makes a difference.

I'm not suggesting that (if this worked) this code should be in production, but it could be enlightening.

Have you tried that?  Also, I don't think it would be necessary to close the handle on the main thread.

Author Comment

ID: 2625364

Still working on it and we now have Microsoft working on the problem as well.  First glance, they have no idea but I'll keep you all posted.  Thanks for all the input.  There's still a couple of suggestions we haven't tried yet, but we're working are way through them...

Expert Comment

ID: 2625540

Check each variable that you used to open your applications. It may be possible that, after closing the applications, you are not setting these to Nothing. Therefore, the allocated memory is not released.

Try using:     Var_Excel = Nothing

Do the same for all objects that you open.

Good luck,
LVL 14

Expert Comment

ID: 2625692
Ya know they say that SEX improves memory!!.. Er.. uh.. wait.. what was the question again?.. <smile> and a <wink>
LVL 14

Expert Comment

ID: 2625702

Author Comment

ID: 2628217

The winner is Ameba, though Erik37 also sent the appropriate document link.  In the wee hours of the morning, it was discovered that one element of a control array was assigned to an module level object as part of a work around dealing with validate events.  For whatever reason, the dbl-click event bypasses our fix, and the reference is never explicity released, therefore the UC object is never released.  As it turns out the problem has nothing to do with the API, it was a coincidence that the problem was noticed while trying to open other Apps.

Thanks for all your help.  I kinda figured that a 1000 pt offering would get lots of reponses, though I'm not sure if the sex comment helped, it propbably didn't hurt ;)

LVL 15

Expert Comment

ID: 2628676
I'm glad you solved it!

Thanks for the points, although it was not that hard - actually, according to EE timestamps, solution was posted before your question. :)

Good luck to you and your client!

Author Comment

ID: 2628842
The question is never hard in hind sight, the urgency was worth 1000 pts and then some...

Featured Post

Get expert help—faster!

Need expert help—fast? Use the Help Bell for personalized assistance getting answers to your important questions.

Question has a verified solution.

Are you are experiencing a similar issue? Get a personalized answer when you ask a related question.

Have a better answer? Share it in a comment.

Join & Write a Comment

If you have ever used Microsoft Word then you know that it has a good spell checker and it may have occurred to you that the ability to check spelling might be a nice piece of functionality to add to certain applications of yours. Well the code that…
This article describes some techniques which will make your VBA or Visual Basic Classic code easier to understand and maintain, whether by you, your replacement, or another Experts-Exchange expert.
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 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…

607 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