menu
  Home  ==>  papers  ==>  colibri_utilities  ==>  dll_and_process_viewer   

DLL and Process Viewer - Felix John COLIBRI.

  • abstract : display the list of running processes, with their associated DLLs and Memory mapped files
  • key words : Process walker, Windows Modules, Packages, PsAPI
  • software used : Windows XP, Delphi 6
  • hardware used : Pentium 2.800Mhz, 512 M memory, 140 G hard disc
  • scope : Delphi 5, Delphi 6, Delphi 7, Delphi 2005, Delphi 2006, Delphi 2007, Turbo Delphi for Windows
  • level : Delphi developer, Windows Developer
  • plan :


1 - Displaying Windows Processes

This very simple utility presents the list of running processes with their associated modules (DLLs) and memory mapped files.

The tools works for NT-class windows (NT, Windows 2K, XP).

For Windows-9x class Windows (Windows 95, Windows 98), a similar technique could be used with the Windows ToolHelp library.




2 - The Windows Process List

2.1 - Walking the Process List

Displaying the Windows Process list is a very ancient activity. We did it mainly under Windows 3.1, together with memory spelunking (Jefferey RICHTER, Max PIETRECK were our idols in those times).

We recently wanted to check the process list and their associated DLLs. Since the old utilities would not work, we had to do some Googling around. The present project is the result of those searches.



On NT, the base library is PsAPI. For Delphi 6 and later, CodeGear has included the PsAPI.PAS import unit in:

    c:\Program Files\Borland\Delphi6\Source\Rtl\Win



Using this unit, the steps are quite straightforward:

  • to get the process list, we use the EnumProcesses function:

    function EnumProcesses(p_pt_process_id_listPInteger;
        p_list_bytes_maxInteger
        var pv_list_bytes_readInteger): booleanstdcall;

    and:

    • p_pt_process_id_list is a pointer to structure which will receive a list of process IDs
    • p_list_bytes_max is the size of this structure
    • pv_list_bytes_read is the number of bytes actually transfered. This value, divided by the size of a process ID (4) will be the process count

    As in many such Windows calls, we might perform two calls
    • the first to get the actual process count, which will allow to allocate the correct process ID array
    • a second to get the process IDs

    Alternately, we can call the routine using a "reasonable" process count:

    const k_process_id_array_max= 1000;
    var l_process_id_array_sizeDWORD;
        l_process_id_arrayarray[0..k_process_id_array_maxof Integer;

    if EnumProcesses(@ l_process_id_array,
          k_process_id_array_max* 4, l_process_id_array_size)
      then // ...ooo...

  • once we have the table of Process IDs, we can open the Processes using (Windows.Pas):

    function OpenProcess(p_desired_accessDWORD;
        p_inherit_handleBOOLp_process_idDWORD): THandlestdcall;

  • and for each process, we can
    • get the process file name, by calling GetModuleFileNameExA
    • query the module (DLL) list with:

      function EnumProcessModules(p_process_handleTHandle;
          p_pt_module_handle_listPHInst
          p_list_bytes_maxInteger;
          var pv_list_bytes_readInteger) : booleanstdcall;

      and:

      • p_process_handle is the process handle
      • p_pt_module_handle_list is a pointer to a module handle list
      • p_list_bytes_max is the size of the module handle list
      • pv_list_bytes_read is the byte count transfered
    • get the memory mapped file names


2.2 - The Process viewer structure

We want to display the informations about the running processes, and for each process, the module and mapped file list.

We have two options:

  • display all the information by calling the routines and placing the results in a tMemo (or a tTreeView) on the fly
  • build an in-memory structure, and display this structure with visual components
We will use the second route, since it is independent of the display components. You can use our querying structure, and display the information using different visual component.



To save the process / DLL information, we will use our traditional tStringList encapsulation:

  • c_process_list handles the list of processes
  • c_process contains information about each process (name, memory load address, priority etc) as well as
    • a tStringList of c_module
    • a tStringList of c_memory_mapped files


The process module routine is:

procedure c_process_list.get_nt_process_list(p_do_build_memory_mapped_file_listBoolean);
  const k_process_id_array_max= 1000;
  var l_process_indexInteger;
      // -- 4x process count
      l_process_id_array_sizeDWORD;
      l_process_id_arrayarray[0..k_process_id_array_max- 1] of Integer;
      l_process_namearray[0..MAX_PATH- 1] of char;
      l_process_handleTHandle;

      l_module_handleHMODULE;
      l_module_count_x_4DWORD;
      l_module_namearray[0..MAX_PATH- 1] of char;

      l_c_processc_process;
      l_priorityString;

  begin // build_process_list
    if not EnumProcesses(@l_process_id_array,
          k_process_id_array_max* 4, l_process_id_array_size)
      then raise Exception.Create('PSAPI.DLL_not_found');

    display('process_count_x_4 'f_integer_to_hex(l_process_id_array_size));

    for l_process_index:= 0 to (l_process_id_array_size div SizeOf(Integer)- 1) do
    begin
      l_process_handle:= OpenProcess(PROCESS_QUERY_INFORMATION or PROCESS_VM_READ,
          FALSEl_process_id_array[l_process_index]);

      if l_process_handle<> 0
        then begin
            if GetModuleFileNameExA(l_process_handle, 0, l_process_name,
                SizeOf(l_process_name))> 0
              then begin
                  l_c_process:= f_c_add_process(ExtractFileName(l_process_name));

                  // -- get the first module, (2nd parameter: only 1 handle),
                  //  --  which is the process itself
                  // --   as well as the module count
                  if EnumProcessModules(l_process_handle, @l_module_handle,
                      SizeOf(l_module_handle), l_module_count_x_4)
                    then begin
                        with l_c_process do
                        begin
                          GetModuleFileNameExA(l_process_handlel_module_handle,
                              l_module_nameSizeOf(l_module_name));

                          m_main_module_name:= ExtractFileName(l_process_name);
                          m_process_id:= l_process_id_array[l_process_index];

                          fill_process_times(l_process_handlel_c_process);

                          case GetPriorityClass(l_process_handleof
                            HIGH_PRIORITY_CLASSl_priority:= 'High';
                            IDLE_PRIORITY_CLASSl_priority:= 'Idle';
                            NORMAL_PRIORITY_CLASSl_priority:= 'Normal';
                            REALTIME_PRIORITY_CLASSl_priority:= 'RealTime';
                          end;

                          m_process_priority:= l_priority;
                          m_process_path:= ExtractFilePath(l_process_name);

                          m_module_count:= l_module_count_x_4 div 4;

                          if m_module_count> 0
                            then build_module_list(l_process_handlel_c_process);

                          if p_do_build_memory_mapped_file_list
                            then build_memory_mapped_file_list(l_process_handle,
                                l_c_process);
                        end// with l_c_process
                      end// could enumerate the first module
                end// has found the process

            CloseHandle(l_process_handle);
          end// process_handle> 0
    end// for l_process_index
  end// build_process_list



And the (nested) module building routine is (partial):

procedure build_module_list(p_process_handletHandlep_c_processc_process);
  var l_module_count_x_4DWORD;
      l_module_namearray[0..MAX_PATH- 1] of char;

      l_module_indexInteger;
      l_c_modulec_module;
      l_module_infoTModuleInfo;
  begin
    with p_c_process do
    begin
      SetLength(m_module_handle_arraym_module_count);
      EnumProcessModules(p_process_handle, @m_module_handle_array[0],
          4* m_module_countl_module_count_x_4);

      for l_module_index:= 0 to m_module_count- 1 do
      begin
        GetModuleFileNameExA(p_process_handle,
            m_module_handle_array[l_module_index],
            l_module_nameSizeOf(l_module_name));

        l_c_module:= f_c_add_module(ExtractFileName(l_module_name));

        with l_c_module do
        begin
          m_module_name:= m_name;
          m_module_path:= ExtractFilePath(l_module_name);

          if GetModuleInformation(p_process_handle,
              m_module_handle_array[l_module_index],
              @l_module_infoSizeOf(l_module_info))
            then
              with l_module_info do
              begin
                m_pt_base_address:= lpBaseOfDll;
                m_image_size:= SizeOfImage;
                m_pt_entry_point:= EntryPoint;
              end// with l_module_info
        end// with l_c_module
      end// for l_module_index
    end//  with p_c_process
  end// build_module_list



To get the memory mapped files, we use:

procedure build_memory_mapped_file_list(p_process_handletHandle;
    p_c_processc_process);

  function f_memory_type(p_memory_typeDWORD): string;
    const k_memory_type_mask = DWORD($0000000F);
    begin
      Result := '';
      case p_memory_type and k_memory_type_mask of
        1: Result := 'Read-only';
        2: Result := 'Executable';
        4: Result := 'Read/write';
        5: Result := 'Copy on write';
        else
          Result := 'Unknown';
      end// case

      if p_memory_type and $100 <> 0
        then Result := Result + ', Shareable';
    end// f_memory_type

  const k_mapped_file_addresss_mask = DWORD($FFFFF000);
  var l_working_set_arrayarray[0..$3FFF - 1] of DWORD;
      l_mapped_file_indexInteger;
      l_mapped_file_namearray[0..MAX_PATHof char;
      l_pt_working_setPointer;

      l_c_memory_mapped_filec_memory_mapped_file;
      l_file_nameString;

  begin // build_memory_mapped_file_list
    with p_c_process do
      if QueryWorkingSet(p_process_handle,
          @l_working_set_arraySizeOf(l_working_set_array))
        then
          for l_mapped_file_index := 1 to l_working_set_array[0] do
          begin
            l_pt_working_set := Pointer(l_working_set_array[l_mapped_file_index]
                and k_mapped_file_addresss_mask);

            GetMappedFileName(p_process_handlel_pt_working_set,
                l_mapped_file_nameSizeOf(l_mapped_file_name));
            l_file_name:= f_get_file_name(l_mapped_file_name);

            l_c_memory_mapped_file:= f_c_add_memory_mapped_file(l_file_name);
            with l_c_memory_mapped_file do
            begin
              m_pt_working_set:= l_pt_working_set;
              m_memory_type:= f_memory_type(l_working_set_array[l_mapped_file_index]);
            end// with l_c_memory_mapped_file
          end// with p_c_process, QueryWorkinSeg, for l_mapped_file_index
  end// build_memory_mapped_file_list



2.3 - The main Process Walker form

A tButton.Click event creates and builds the process list, and displays each process in a tListBox:

var g_c_process_listc_process_listNil;

procedure TForm1.create_Click(SenderTObject);
  var l_process_indexInteger;
  begin
    g_c_process_list:= c_process_list.create_process_list('process_list');

    with g_c_process_list do
    begin
      get_nt_process_list(build_mmf_.Checked);

      process_listbox_.Items.Clear;
      for l_process_index:= 0 to f_process_count- 1 do
        with f_c_process(l_process_indexdo
          process_listbox_.Items.AddObject(m_namef_c_self);
    end// with g_c_process_list
  end// create_Click



Clicking on an item or this tListBox will display the module list:

procedure TForm1.process_listbox_Click(SenderTObject);
  var l_module_indexInteger;
      l_memory_mapped_file_indexInteger;
  begin
    with process_listbox_ do
      if ItemIndex>= 0
        then
          with c_process(Items.Objects[ItemIndex]), process_memo_.Lines do
          begin
            Clear;
            // display_module_list;
            Add(m_name);
            Add('  'm_process_path);
            Add('  id       'IntToStr(m_process_id));
            Add('  priority 'm_process_priority);
            Add('  time k, c, u 'm_kernel_time' '
                + m_cpu_time' 'm_user_time);

            module_count_label_.Caption:= IntToStr(m_module_count);

            module_listbox_.Items.Clear;
            module_listbox_.Sorted:= sort_dll_.Checked;

            for l_module_index:= 0 to f_module_count- 1 do
              with f_c_module(l_module_indexdo
                module_listbox_.Items.AddObject(m_namef_c_self);

            memory_mapped_file_listbox_.Items.Clear;
            for l_memory_mapped_file_index:= 0 to f_memory_mapped_file_count- 1 do
              with f_c_memory_mapped_file(l_memory_mapped_file_indexdo
                memory_mapped_file_listbox_.Items.AddObject(m_namef_c_self);
          end;
  end// process_listbox_Click



2.4 - Module and DLL viewer Snapshot

Here is a snapshot after the creation of the process list:

image

and when we click on the p_dll_process_viewer exe and the PSAPI.dll:

image




3 - Improvements and Comments

Many improvements are possible
  • instead of 2 tListBoxes, we could have used a tTreeView
  • we could turn this analyzer into a monitor by adding a timer and refreshing the display upon each timer tick. In this case, the DLL list, and certainly the Memory Mapped File analysis should be disconnected because of the construction time
  • in our version, we also added a snapshot saving and a delta computations to find out which processes or DLLs were loaded between two requests
In addition, here are a couple of comments:
  • in addition to the module list, you can get the driver lists
  • it seems that the WMI (Windows Instrumentation API) would bring back more information about processes then PsAPI, but we did not try this route



4 - Download the Sources

Here are the source code files: The .ZIP file(s) contain:
  • the main program (.DPR, .DOF, .RES), the main form (.PAS, .DFM), and any other auxiliary form
  • any .TXT for parameters, samples, test data
  • all units (.PAS) for units
Those .ZIP
  • are self-contained: you will not need any other product (unless expressly mentioned).
  • for Delphi 6 projects, can be used from any folder (the pathes are RELATIVE)
  • will not modify your PC in any way beyond the path where you placed the .ZIP (no registry changes, no path creation etc).
To use the .ZIP:
  • create or select any folder of your choice
  • unzip the downloaded file
  • using Delphi, compile and execute
To remove the .ZIP simply delete the folder.

The Pascal code uses the Alsacian notation, which prefixes identifier by program area: K_onstant, T_ype, G_lobal, L_ocal, P_arametre, F_unction, C_lass etc. This notation is presented in the Alsacian Notation paper.



As usual:

  • please tell us at fcolibri@felix-colibri.com if you found some errors, mistakes, bugs, broken links or had some problem downloading the file. Resulting corrections will be helpful for other readers
  • we welcome any comment, criticism, enhancement, other sources or reference suggestion. Just send an e-mail to fcolibri@felix-colibri.com.
  • or more simply, enter your (anonymous or with your e-mail if you want an answer) comments below and clic the "send" button
    Name :
    E-mail :
    Comments * :
     

  • and if you liked this article, talk about this site to your fellow developpers, add a link to your links page ou mention our articles in your blog or newsgroup posts when relevant. That's the way we operate: the more traffic and Google references we get, the more articles we will write.



5 - References

The most useful information came from:
  • The Delphi 5 Developer's Guide - Steve TEIXEIRA and Xavier PACHECO
    Sams 2000 - ISBN 0-672-31781-8
    One of the best Delphi 5 book, as all books written by those authors
  • many files found using Google with code snippets and a component (ggProcessViewer) from Guido GEURTS (even some on Tamaracka)
  • Colin Wilson's Delphi 5 process viewer



6 - The author

Felix John COLIBRI works at the Pascal Institute. Starting with Pascal in 1979, he then became involved with Object Oriented Programming, Delphi, Sql, Tcp/Ip, Html, UML. Currently, he is mainly active in the area of custom software development (new projects, maintenance, audits, BDE migration, Delphi Xe_n migrations, refactoring), Delphi Consulting and Delph training. His web site features tutorials, technical papers about programming with full downloadable source code, and the description and calendar of forthcoming Delphi, FireBird, Tcp/IP, Web Services, OOP  /  UML, Design Patterns, Unit Testing training sessions.
Created: apr-07. Last updated: mar-2017 - 94 articles, 229 .ZIP sources, 1183 figures
Copyright © Felix J. Colibri   http://www.felix-colibri.com 2004 - 2017. All rigths reserved
Back:    Home  Papers  Training  Delphi developments  Links  Download
the Pascal Institute

Felix J COLIBRI

+ Home
  + articles_with_sources
    + database
    + web_internet_sockets
    + oop_components
    + uml_design_patterns
    + debug_and_test
    + graphic
    + controls
    + colibri_utilities
      – delphi_net_bdsproj
      – dccil_bat_generator
      – coliget_search_engine
      – dfm_parser
      – dfm_binary_to_text
      – component_to_code
      – exe_dll_pe_explorer
      – dll_process_viewer
      – the_alsacian_notation
      – html_help_viewer
      – cooking_the_code
      – events_record_playback
      – colibri_hiragana_quiz
    + colibri_helpers
    + delphi
    + firemonkey
    + compilers
    + vcl
  + delphi_training
  + delphi_developments
  + sweet_home
  – download_zip_sources
  + links
Contacts
Site Map
– search :

RSS feed  
Blog