[Okta Webinar] Learn how to a build a cloud-first strategyRegister Now

x
?
Solved

Process memory information listing

Posted on 2004-10-20
8
Medium Priority
?
2,236 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 1000 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
Concerto's Cloud Advisory Services

Want to avoid the missteps to gaining all the benefits of the cloud? Learn more about the different assessment options from our Cloud Advisory team.

 

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

Accepted Solution

by:
DragonSlayer earned 1000 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

VIDEO: THE CONCERTO CLOUD FOR HEALTHCARE

Modern healthcare requires a modern cloud. View this brief video to understand how the Concerto Cloud for Healthcare can help your organization.

Question has a verified solution.

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

Introduction I have seen many questions in this Delphi topic area where queries in threads are needed or suggested. I know bumped into a similar need. This article will address some of the concepts when dealing with a multithreaded delphi database…
In my programming career I have only very rarely run into situations where operator overloading would be of any use in my work.  Normally those situations involved math with either overly large numbers (hundreds of thousands of digits or accuracy re…
Are you ready to place your question in front of subject-matter experts for more timely responses? With the release of Priority Question, Premium Members, Team Accounts and Qualified Experts can now identify the emergent level of their issue, signal…
As many of you are aware about Scanpst.exe utility which is owned by Microsoft itself to repair inaccessible or damaged PST files, but the question is do you really think Scanpst.exe is capable to repair all sorts of PST related corruption issues?
Suggested Courses
Course of the Month19 days, 23 hours left to enroll

872 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