如何使用Delphi获取其他进程的信息?

3

我想制作一个任务管理程序,显示以下信息:

  1. 图像名称
  2. 内存使用情况
  3. PID

我该如何做到这一点?


1
你为什么要撤销那些改进你问题的编辑,并添加无用的“信息”标签? - mghie
5个回答

7

你不需要使用J(WS)CL,因为有一个简单的WinAPI调用可以做到几乎所有的事情,那就是CreateToolhelp32Snapshot。要获取所有正在运行的进程的快照,需要按以下方式调用:

var
  snapshot: THandle;
begin
  snapshot := CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0); 

现在你有一个所有正在运行的进程列表。你可以使用Process32FirstProcess32Next函数浏览这个列表,列表条目是PROCESSENTRY32结构体(其中包含进程ID和映像名称等信息)。
uses 
  Windows, TLHelp32, SysUtils; 

var 
  snapshot: THandle; 
  ProcEntry: TProcessEntry32; 
  s: String; 
begin
  snapshot := CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0); 
  if (snapshot <> INVALID_HANDLE_VALUE) then begin 
    ProcEntry.dwSize := SizeOf(ProcessEntry32); 
    if (Process32First(snapshot, ProcEntry)) then begin 
      s := ProcEntry.szExeFile; 
      // s contains image name of the first process 
      while Process32Next(snapshot, ProcEntry) do begin 
        s := ProcEntry.szExeFile; 
        // s contains image name of the current process 
      end; 
    end; 
  end; 
  CloseHandle(snapshot);

然而,内存消耗信息似乎没有被包含在内,但是您可以通过另一个简单的API调用GetProcessMemoryInfo来获取此信息。

uses
  psAPI;

var
  pmc: TProcessMemoryCounters;
begin
  pmc.cb := SizeOf(pmc) ;
  if GetProcessMemoryInfo(processID, @pmc, SizeOf(pmc)) then
    // Usage in Bytes: pmc.WorkingSetSize
  else
    // fail

您只需使用从快照中检索到的进程ID调用此函数即可。


你应该删除最后一段代码中的动态内存分配,并在堆栈上直接使用TProcessMemoryCounters变量。 - mghie
1
+1;太棒了!我不知道这个,只知道PSAPI。请注意,在线文档在此处:http://msdn.microsoft.com/en-us/library/ms682489(VS.85).aspx - Jeroen Wiert Pluimers
可能性非常小,几乎不可能在这里发生,但是PID可以被重复使用,因此不能百分之百保证返回的内存信息实际上属于快照中获取的PID。 - Remko
@Remko:你可以在这里的任何回复下面留下评论,我非常怀疑除了内核外,是否有任何方法可以在请求的确切时间点告诉您给定进程的精确内存使用情况 - 你不可能是认真的。 (请记住,PID重用理论上甚至可能发生在GetProcessMemoryInfo函数的调用期间,因此这很可能是最好的方法)。 - Leo
@Mef:我说这很不可能;-)话虽如此,你可以使用NtQuerySystemInformation和SystemProcessInformation infoclass来检索所有信息,这将返回一个_SYSTEM_PROCESSES记录数组,其中包含进程名称、pid和内存使用情况。这就是任务管理器所做的,也是WMI、PsApi和Tool Help函数在底层执行的操作。 - Remko
@Remko:这很可能是真的,但即使是NtQuerySystemInformation也不是原子操作,这意味着在调用此函数期间,PID可能会发生变化。没有保证。 - Leo

2
您可以使用 WMI Win32_Process 类获取所有运行进程的信息。此外,您还可以检查 Win32_PerfFormattedData_PerfProc_Process 类以获取与 CPU 和内存使用相关的性能计数器。
请参考以下示例:
program WMIProcessInfo;

{$APPTYPE CONSOLE}

uses
  SysUtils
  ,ActiveX
  ,ComObj
  ,Variants;


procedure GetWin32_Process;
var
  objWMIService : OLEVariant;
  colItems      : OLEVariant;
  colItem       : OLEVariant;
  oEnum         : IEnumvariant;
  iValue        : LongWord;

  User          : OLEVariant;
  Domain        : OLEVariant;

  function GetWMIObject(const objectName: String): IDispatch;
  var
    chEaten: Integer;
    BindCtx: IBindCtx;
      Moniker: IMoniker;
  begin
    OleCheck(CreateBindCtx(0, bindCtx));
    OleCheck(MkParseDisplayName(BindCtx, StringToOleStr(objectName), chEaten, Moniker));
    OleCheck(Moniker.BindToObject(BindCtx, nil, IDispatch, Result));
  end;

begin
  objWMIService := GetWMIObject('winmgmts:\\localhost\root\cimv2');
  colItems      := objWMIService.ExecQuery('SELECT * FROM Win32_Process','WQL',0);
  oEnum         := IUnknown(colItems._NewEnum) as IEnumVariant;

      WriteLn(Format('%-20s %6s %10s %10s %10s',['Caption','PID','User','Domain','Working Set ( Kb Memory)']));
  while oEnum.Next(1, colItem, iValue) = 0 do
  begin
      colItem.GetOwner(User,Domain);
      if colItem.GetOwner( User, Domain ) =0 then //get the user and domain
      WriteLn(Format('%-20s %6s %10s %10s %10s',[colItem.Caption,colItem.ProcessId,User,Domain,colItem.WorkingSetSize / 1024]))
      else
      WriteLn(Format('%-20s %6s %10s %10s %10s',[colItem.Caption,colItem.ProcessId,'','',colItem.WorkingSetSize / 1024]));

  end;
end;


begin
 try
    CoInitialize(nil);
    try
      GetWin32_Process;
      Readln;
    finally
    CoUninitialize;
    end;
 except
    on E:Exception do
    Begin
        Writeln(E.Classname, ': ', E.Message);
        Readln;
    End;
  end;
end.

WMI非常慢,如果NiliDelphi想要创建类似于经常刷新进程列表的TaskManager之类的东西,我不建议使用WMI。 - Remko

2
使用PSAPI(进程状态 API)。 开源的JCL具有用于 PSAPI 的 Delphi 封装器。
您可以查看一些更好的 stackoverflow Delphi PSAPI 问题 来获取答案。
--jeroen

1
在Jwscl中有一个可以为您完成此操作的类(JwsclTerminalServer):
 var
   ATerminalServer: TJwTerminalServer;
   i: Integer;
 begin
   // Create Terminal Server instance and allocate memory for it
   ATerminalServer := TjwTerminalServer.Create;

   // Set servername (only in case of remote server)
   ATerminalServer.Server := 'TS001';

   // Remember that EnumerateProcesses will automatically connect to the
   // Terminal Server for you. The connect function raises an Exception
   // if the connection attempt was unsuccessfull, so better use try..except
   try
     if ATerminalServer.EnumerateProcesses then
     begin

       // Now loop through the list
       for i := 0 to ATerminalServer.Processes.Count - 1 do
       begin
         Memo1.Lines.Add(ATerminalServer.Processes[i].ProcessName);
       end;

     end;
   except
     on E: EJwsclWinCallFailedException do
     begin
       // Handle Exception here
     end;
   end;

   // Free Memory
   ATerminalServer.Free;
 end;

尽管该单元针对终端服务器,但此部分可在有或没有终端服务器的情况下使用,并且作为奖励,您还可以在远程系统上使用它。

对于每个进程,都会返回详细信息,请查看文档以获取详细信息。

对于内存使用情况,您可以使用ProcessMemUsage和ProcessVirtualSize属性,对于Pid,有ProcessId属性。


1

ProcessInfo 提供有关 Windows 运行进程的基本信息。它是开源的,并包含任务管理器的演示。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接