Solved

Process memory information listing

Posted on 2004-10-20
8
2,217 Views
Last Modified: 2010-05-20
I'm developing some process listing software, and I need to know how much memory each process consumes. I need a clean and small solution for this. Listing the processes and threads etc. is no problem, it's just getting the memory usage information. If you have a solution not based on the performance monitoring objects, that would be preferred, but if it's small and easy it doesn't matter that much.
0
Comment
Question by:Zinvob
  • 4
  • 3
8 Comments
 
LVL 17

Expert Comment

by:Wim ten Brink
ID: 12356918
Use WMI!

program ShowProcesses;

uses
  Windows, SysUtils, ActiveX, WbemScripting_TLB;

function ADsEnumerateNext(pEnumVariant: IEnumVARIANT; cElements: ULONG; var pvar: OleVARIANT; var pcElementsFetched: ULONG): HRESULT; safecall; external 'activeds.dll';

procedure DumpWMI_Process(Process: SWBemObject);
var
  Enum: IEnumVARIANT;
  varArr: OleVariant;
  lNumElements: ULong;
  SProp: ISWbemProperty;
  Prop: OleVariant;
  PropName: string;
  PropType: string;
  PropValue: string;
begin
  WriteLn('+ WMI Path: ', string(Process.Path_.Path));
  Enum := Process.Properties_._NewEnum as IEnumVariant;
  while (Succeeded(ADsEnumerateNext(Enum, 1, VarArr, lNumElements))) and (lNumElements > 0) do begin
    if Succeeded(IDispatch(varArr).QueryInterface(SWBemProperty, SProp)) and Assigned(SProp) then begin
      try
        PropName := SProp.Name;
        Prop := SProp.Get_Value;
        PropType := IntToStr(VarType(Prop));
        PropValue := VarToStr(Prop);
        WriteLn('  + ', string(PropName), '[', PropType, '] = ', string(PropValue));
      except on E: Exception do WriteLn(PropName, ': ', E.Message);
      end;
    end;
  end;
end;

const
  sExecQuery = 'Select * from Win32_Process';
var
  Buffer: array[0..16 * 1024 - 1] of char;
  Server: string;
  Enum: IEnumVARIANT;
  varArr: OleVariant;
  lNumElements: ULong;
  AName: array[0..255] of char;
  ASize: DWORD;
begin
  SetTextBuf(Output, Buffer);
  if Succeeded(CoInitializeEx(nil, COINIT_MULTITHREADED or COINIT_DISABLE_OLE1DDE or COINIT_SPEED_OVER_MEMORY)) then begin
    if (ParamCount = 0) then begin
      Server := '';
      ASize := SizeOf(AName) - 1;
      if Windows.GetComputerName(@AName, ASize) then Server := AName;
    end
    else begin
      Server := ParamStr(1);
    end;
    WriteLn('Processes retrieved through Windows Machine Instrumentation.');
    WriteLn;
    try
      Enum := CoSWbemLocator.Create.ConnectServer(Server, 'root\cimv2', '', '', '', '', 0, nil).ExecQuery(sExecQuery, 'WQL', wbemFlagBidirectional, nil)._NewEnum as IEnumVariant;
      while (Succeeded(ADsEnumerateNext(Enum, 1, varArr, lNumElements))) and (lNumElements > 0) do begin
        DumpWMI_Process(IUnknown(varArr) as SWBemObject);
        WriteLn;
      end;
    except on E: Exception do WriteLn(E.Message);
    end;
    CoUninitialize;
  end;
end.

You can create the unit WbemScripting_TLB yourself by importing the type library C:\WINNT\system32\wbem\wbemdisp.tlb but just in case, I also have one available at http://www.workshop-alex.org/Sources/WbemScripting_TLB.pas and remember, above code provides a lot of process information. More than you might need.
0
 

Author Comment

by:Zinvob
ID: 12357069
How does this one work with W2k, NT4.0 and 98?

It's a little bit overkill, but might be the solution. If someone has a smaller solution that would be nice, if not I'm going to accept this one if I just get around the exception I get on CoUninitialize (access violation at 0x00000000: read of address 0x0000000). If I remove CoUninitialize I get the same problem.

Used the source you gave me, only adding Variants to the uses clause and {$APPTYPE Console} at the top. Also used the import of the typelibrary as you provided with the link, instead of creating it. I'm using XP sp1. If the implementation differs from one OS to another, maybe late bindings would be better to use?
0
 
LVL 17

Assisted Solution

by:Wim ten Brink
Wim ten Brink earned 250 total points
ID: 12357487
Well, technically the required WMI unit makes it appear to be fast but if you remove the useless units from the WbemScripting_TLB unit then above example compiles to about 60 KB. (Without runtime packages.) Most overhead comes from the SysUtils unit in this case.

You do not need CoInitializeEx/CoUninitialize if you're using forms in your application. TheComObj unit combined with the Forms unit will set things up for you. However, there is a problem when you add interface variables in your main project part (*.dpr, between the begin-end where end ends with a dot.) If you put the code within a procedure, Delphi's garbage collector will clean it up nicely.

A minor change to above code: (Removed the CoInitializeEx by adding support for ComObj and showing how to retrieve a property directly by name. And replaced ADsEnumerateNext with a nicer method called NextItem.)

program ShowProcesses;

{$APPTYPE Console}

uses
  Windows, SysUtils, ActiveX, ComObj, WbemScripting_TLB;

function NextItem( var Enum: IEnumVARIANT; const riid: TGUID; out ppObject ): Boolean;
var
  OleProperty: OleVariant;
  NumProp: LongWord;
begin
  try
    Result := Succeeded( Enum.Next( 1, OleProperty, NumProp ) ) and ( NumProp > 0 ) and Succeeded( IDispatch( OleProperty ).QueryInterface( riid, ppObject ) );
  except
    Result := False;
    IUnknown( ppObject ) := nil;
  end;
end;

procedure DumpWMI_Process( Process: SWBemObject );
var
  Enum: IEnumVARIANT;
  WbemProperty: ISWbemProperty;
  Prop: OleVariant;
  PropName: string;
  PropType: string;
  PropValue: string;
begin
  WriteLn( '+ WMI Path: ', string( Process.Path_.Path ), ': ', VarToStr( Process.Properties_.Item( 'Description', 0 ).Get_Value ) );
  Enum := Process.Properties_._NewEnum as IEnumVariant;
  while NextItem( Enum, SWBemProperty, WbemProperty ) and Assigned( WbemProperty ) do begin
    try
      PropName := WbemProperty.Name;
      Prop := WbemProperty.Get_Value;
      PropType := IntToStr( VarType( Prop ) );
      PropValue := VarToStr( Prop );
      WriteLn( '  + ', string( PropName ), '[', PropType, '] = ', string( PropValue ) );
    except on E: Exception do WriteLn( PropName, ': ', E.Message );
    end;
  end;
end;

procedure RunThisStuff;
const
  sExecQuery = 'Select * from Win32_Process';
var
  Server: string;
  Enum: IEnumVARIANT;
  WBemObject: SWBemObject;
  AName: array[ 0..255 ] of char;
  ASize: DWORD;
begin
  if ( ParamCount = 0 ) then begin
    Server := '';
    ASize := SizeOf( AName ) - 1;
    if Windows.GetComputerName( @AName, ASize ) then Server := AName;
  end
  else begin
    Server := ParamStr( 1 );
  end;
  WriteLn( 'Processes retrieved through Windows Machine Instrumentation.' );
  WriteLn;
  try
    Enum := CoSWbemLocator.Create.ConnectServer( Server, 'root\cimv2', '', '', '', '', 0, nil ).ExecQuery( sExecQuery, 'WQL', wbemFlagBidirectional, nil )._NewEnum as IEnumVariant;
    while NextItem( Enum, SWBemObject, WBemObject ) and Assigned( WBemObject ) do begin
      DumpWMI_Process( WBemObject );
      WriteLn;
    end;
  except on E: Exception do WriteLn( E.Message );
  end;
end;

var
  Buffer: array[ 0..16 * 1024 - 1 ] of char;
begin
  if Assigned( InitProc ) then TProcedure( InitProc );
  SetTextBuf( Output, Buffer );
  RunThisStuff;
end.

If this WMI data isn't enough, you can try other WMI queries for additional data. Above code runs fine on my W2K system and basically should run on any system that has WMI installed on it. (Which is part of the newest OS releases and Internet Explorer 5, if I'm not mistaken.) According to http://www.microsoft.com/technet/scriptcenter/resources/wmifaq.mspx WMI is installed by default with Windows Me, Windows 2000, Windows XP and Windows Server 2003. For Windows 98 and Windows NT 4.0, WMI is available as an Internet download from http://www.microsoft.com/downloads. Search for the download “Windows Management Instrumentation (WMI) CORE 1.5 (Windows 95/98/NT 4.0)." However, WMI is required for IE 5.0 and for the Windows Scripting Host.

Basically, WMI is a powerful, but relative easy tool once you understand how it works. It doesn't much overhead although the unit you need to install seems quite large.

Oh, did I forget to add {$APPTYPE Console}? Never actually noticed it since I always redirected the output of this test application to a text file. :-)
I am using Delphi 5 now on W2K, btw. Which is why I don't use Variants. At home I use Delphi 7 on XP, though. And that's where I created that unit on my site. It is already quite optimized. It just doesn't work in Delphi 5. I hope above code is a bit easier to follow.
0
 

Author Comment

by:Zinvob
ID: 12357890
Thank you. The last one works. Still, it's very overscaled for my use, and I would like to have something not dependant on this WMI core. So I'm gonna wait for answers here for about a week. If no smaller and simpler solutions is given, I'll accept your answer.
0
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!

 
LVL 14

Accepted Solution

by:
DragonSlayer earned 250 total points
ID: 12366965
Guess this is what you need http://www.experts-exchange.com/Programming/Programming_Languages/Delphi/Q_20974300.html

The above link shows how to get the current memory usage for your own process (via GetCurrentProcess), but since you mentioned that you can already retrieve the process listing, well, then it's as simple as calling the function with each of your process handle ;-)
0
 

Author Comment

by:Zinvob
ID: 12367722
Thank you for you answer. Pitty I didn't find this when searching. And the API is not documented inside any help files with Delphi, not even Windows SDK (Delphi's version).

This is indeed the answer to my problem (with some small modifications). Since Alex gave such a good solution also, I'm going to split the points between you 50/50.

Final solution, example snippet source:
------------------------------------------------
uses
  tlhelp32, psapi;

var
  sh,
  ph: THandle;
  pe: TProcessEntry32;
  pmc: TProcessMemoryCounters;

sh := CreateToolhelp32Snapshot(TH32CS_SNAPALL,0);
try
  pe.dwSize := SizeOf(pe);
  if Process32First(sh,pe) then
  repeat
    // Save stuff from pe, like thread count, filename and other stuff
    ph := OpenProcess(PROCESS_QUERY_INFORMATION, false, pe.th32ProcessID);
    try
      pmc.cb := SizeOf(pmc);
      if GetProcessMemoryInfo(ph,@pmc,SizeOf(pmc)) then
      begin
        //Save memory information
      end;
    finally
      CloseHandle(ph);
    end;
  until not Process32Next(sh,pe);
finally
  CloseHandle(sh);
end;
----------------------------------------------

Simple and easy, just the way I like it :)
0
 
LVL 17

Expert Comment

by:Wim ten Brink
ID: 12368112
I'm not 100% sure but I believe the tlhelp32 and psapi are used for different platforms. It could be that your code won't run on NT or 95/98, because of dependencies on certain platform-specific files. The unit tlhelp32 wraps around the ToolHelp API of Windows but the Microsoft NT team didn't like these methods so they added the Process Status functions instead. (psapi) Only with W2K and higher they decided to support both API sets. You're probably using code from both ToolHelp as PSAPI thus chances are that it fails on NT, 95 and 98. Test it well, I would say...
0
 

Author Comment

by:Zinvob
ID: 12368179
For now I'm only going to use this software on W2K and up, but it's good to have in mind if we are going to use something else. As I understand, PSAPI is not supported by 95/98 and ToolHelp is not supported in NT only. But PSAPI has it's own functions for enumerate the processes (only it's more difficult to get thread count from this one). I have done that before. But since ToolHelp does not have any memory usage API calls, I would have to use something else than this solution to make it work under 95/98.
0

Featured Post

Maximize Your Threat Intelligence Reporting

Reporting is one of the most important and least talked about aspects of a world-class threat intelligence program. Here’s how to do it right.

Join & Write a Comment

Suggested Solutions

The uses clause is one of those things that just tends to grow and grow. Most of the time this is in the main form, as it's from this form that all others are called. If you have a big application (including many forms), the uses clause in the in…
Introduction The parallel port is a very commonly known port, it was widely used to connect a printer to the PC, if you look at the back of your computer, for those who don't have newer computers, there will be a port with 25 pins and a small print…
Excel styles will make formatting consistent and let you apply and change formatting faster. In this tutorial, you'll learn how to use Excel's built-in styles, how to modify styles, and how to create your own. You'll also learn how to use your custo…
This demo shows you how to set up the containerized NetScaler CPX with NetScaler Management and Analytics System in a non-routable Mesos/Marathon environment for use with Micro-Services applications.

708 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