Link to home
Start Free TrialLog in
Avatar of gabbro
gabbro

asked on

API Shell Commands

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.
Avatar of tchalkov
tchalkov

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.
Are you closing the handles????

See the following microsoft KB article:

HOWTO: Launch a Win32 Application from Visual Basic
http://support.microsoft.com/support/kb/articles/Q129/7/97.asp 


Cheers!®©
ASKER CERTIFIED SOLUTION
Avatar of ameba
ameba
Flag of Croatia image

Link to home
membership
This solution is only available to members.
To access this solution, you must be a member of Experts Exchange.
Start Free Trial
check out this may be of use
 
PSS ID Number: Q190217
Article last modified on 08-07-1998
 
WINDOWS:4.0,5.0,6.0
 
WINDOWS
 

======================================================================
---------------------------------------------------------------------
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
---------------------------------------------------------------------
 
SUMMARY
=======
 
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.
 
MORE INFORMATION
================
 
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
 
      Const GFSR_SYSTEMRESOURCES = &H0
 
      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 _
            (GFSR_SYSTEMRESOURCES)
 
      End Function
 
5. Change the Name property of the class module to clsSR.
 
6. Change the Instancing property of the class module to Createable
   SingleUse.
 
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
   event:
 
      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.
 
REFERENCES
==========
 
"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
   http://support.microsoft.com/support/kb/articles/q117/7/44.asp.
 

 
Avatar of gabbro

ASKER

Handles are being closed using the CloseHandle method
"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.
Avatar of gabbro

ASKER

Unfortunately the Client is the Government and anyone who's ever dealt with them, knows that they want a Jaguar for a VW...
Avatar of Guy Hengel [angelIII / a3]
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
"BUG: UserControls with Menus Cause Resource Leak"
http://support.microsoft.com/support/kb/articles/Q239/9/43.ASP

"BUG: UserControl Containing Array of Controls Leaks Memory"
http://support.microsoft.com/support/kb/articles/Q190/5/11.ASP

"INFO: Maximum Number of Controls on a Form"
http://support.microsoft.com/support/kb/articles/Q229/7/56.ASP
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
        'THE SHELLED PROGRAM IS ACTIVE
        'DO WHATEVER YOUR GOING TO DO
    Else
        'THE SHELLED PROGRAM IS NOT ACTIVE
        'DO WHATEVER YOUR GOING TO DO
    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:

---------------------------------------
   Do
      If IsActive(ProgHandle) Then
         DoEvents
         SleepAPI 55
      Else
         MsgBox "Execution completed"
         Exit Do
      End If
    Loop
'---------------------------------------

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.


Cheers!®©




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:
http://mstech.born.com/mstech/vbtips/lightwgt.htm
Avatar of gabbro

ASKER

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

Public Type PROCESS_INFORMATION
        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 STANDARD_RIGHTS_REQUIRED = &HF0000
Public Const PROCESS_ALL_ACCESS = STANDARD_RIGHTS_REQUIRED Or SYNCHRONIZE Or
&HFFF
Public Const WAIT_FAILED = &HFFFFFFFF
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 ProcessInfo As PROCESS_INFORMATION
    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
        iExit = ERROR_ABNORMAL_EXIT

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

is there any reason preventing you using
an OLE control ????
Avatar of gabbro

ASKER

Unfortunatley, it's not possible to use an OLE Control within a Custom User Control.  We tried.
So do you think you'll try the alternate example of shelling and waiting that I gave you??

Cheers!®©
Try measuring resources *without* your app:
before loading IE, Word...
and after closing.
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)
Avatar of gabbro

ASKER

All our controls within the Custom controls are Control Arrays.  This was required due to the limitation of 255 individual controls per form.
Thx, gab
(I would normally say: "Thanks, gabbro", but this is to save some memory and some disk space on EE) :)
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.

How are you "closing" the app? For example, if you run Word from your app...do you

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

or

(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.
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.
Avatar of gabbro

ASKER

mcrider,

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

ASKER

mandhjo,

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


Cheers!®©
Avatar of gabbro

ASKER

mcrider,

The controls are indexed...
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...


Cheers!®©

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.
Avatar of gabbro

ASKER

FYI

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...
Hi,

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,
jusclev..
Ya know they say that SEX improves memory!!.. Er.. uh.. wait.. what was the question again?.. <smile> and a <wink>
<G>
Avatar of gabbro

ASKER

Solved!!!!!!!!!!!

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

Ciao,
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!
Avatar of gabbro

ASKER

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