Modules in the current process

It is possible to list all modules (exe, dll, ocx, drv) that are in use by a particular process. This example lists all modules loaded by the current process, which is of course the running Progress process.
The resulting list can be useful during development, to check if a certain DLL or OCX really got released, but can also be useful for support engineers to check if a customer site has the appropriate module versions.
Unfortunately the procedure for Windows NT4 is very different compared to 95/98/2000.

   DEFINE TEMP-TABLE module 
      FIELD hModule        AS INTEGER  FORMAT "->>>>>>>>>>>9"
      FIELD cntUsage       AS INTEGER
      FIELD ModuleName     AS CHARACTER     FORMAT "x(20)"
      FIELD ModulePath     AS CHARACTER     FORMAT "x(150)"
      FIELD FileVersion    AS CHARACTER     FORMAT "x(15)"
      FIELD ProductVersion AS CHARACTER     FORMAT "x(15)"
      INDEX key_name       IS PRIMARY ModuleName.
 
   RUN FindModules. 
 
   /* assuming you want to display the contents of the 
      module temp-table in a browse widget: */
   {&OPEN-BROWSERS-IN-QUERY-DEFAULT-FRAME}
PROCEDURE FindModules :
 
    FOR EACH module :
        DELETE module.
    END.
 
    IF RunningWindowsNT4() THEN 
       RUN FindModules_NT4.
    ELSE
       RUN FindModules_notNT4.
 
    FOR EACH module :
        RUN GetProductVersion(module.modulePath,
                              OUTPUT module.ProductVersion,
                              OUTPUT module.FileVersion).
    END.
 
END PROCEDURE.

Windows 9x and Windows 2000 support the fairly new toolhelp procedures for finding process information.

PROCEDURE FindModules_notNT4 :
 
    DEFINE VARIABLE hSnapShot   AS INTEGER   NO-UNDO.
    DEFINE VARIABLE lpME        AS MEMPTR    NO-UNDO. /* MODULEENTRY32 structure */
    DEFINE VARIABLE ReturnValue AS INTEGER   NO-UNDO.
 
    FOR EACH module : 
        DELETE module.
    END.
 
    IF RunningWindowsNT4() THEN DO:
       MESSAGE "Sorry, this procedure does not work with NT4"
               VIEW-AS ALERT-BOX.
       RETURN.
    END.
 
    /* Create and open SnapShot-list */
    RUN CreateToolhelp32Snapshot({&TH32CS_SNAPMODULE}, 
                                 0, 
                                 OUTPUT hSnapShot).
    IF hSnapShot = -1 THEN RETURN.
 
    /* init buffer for lpPE */
    SET-SIZE(lpME)    = 32 + 256 + 260.
    PUT-LONG(lpME, 1) = GET-SIZE(lpME).
 
    /* Cycle thru process-records */
    RUN Module32First(hSnapShot, 
                      lpME,
                      OUTPUT ReturnValue).
    DO WHILE ReturnValue NE 0:
 
       CREATE module.
       ASSIGN module.moduleName = GET-STRING(lpME, 33)
              module.modulePath = GET-STRING(lpME, 33 + 256)
              module.cntUsage   = GET-LONG(lpME, 17)
              module.hModule    = GET-LONG(lpME, 29).
 
       RUN Module32Next(hSnapShot, 
                        lpME,
                        OUTPUT ReturnValue).
    END.
 
    /* Close SnapShot-list */
    RUN CloseHandle(hSnapShot, OUTPUT ReturnValue).
 
END PROCEDURE.

In NT 4 the only way to find process information is by reading the registry in the HK_PERFORMANCE_DATA key. Interpreting the data in this registry interface is very complicated but there is a library, PSAPI.DLL, which contains a couple of higher-level procedures and reads the registry interface for you. PSAPI.DLL does not reveal every possible info from the registry but enough for this purpose.

PROCEDURE FindModules_NT4 :
    DEFINE VARIABLE ReturnValue AS INTEGER   NO-UNDO.
 
    DEFINE VARIABLE ProcessId      AS INTEGER NO-UNDO.
    DEFINE VARIABLE hProcess       AS INTEGER NO-UNDO.
    DEFINE VARIABLE lphMod         AS MEMPTR  NO-UNDO.
    DEFINE VARIABLE hModule        AS INTEGER NO-UNDO.
    DEFINE VARIABLE cbNeeded       AS INTEGER NO-UNDO.
    DEFINE VARIABLE szModuleName   AS CHARACTER    NO-UNDO.
    DEFINE VARIABLE szModuleNameEx AS CHARACTER    NO-UNDO.
    DEFINE VARIABLE i              AS INTEGER NO-UNDO.
 
    RUN GetCurrentProcessId (OUTPUT ProcessId).
    RUN OpenProcess ( {&PROCESS_QUERY_INFORMATION} + {&PROCESS_VM_READ},
                      0,
                      ProcessID,
                      OUTPUT hProcess).
 
    /* if process handle for the current process is found, then: */
    IF hProcess NE 0 THEN DO:
       SET-SIZE (lphMod) = 4 * 1024. /* should be more than enough */
 
       RUN EnumProcessModules ( hProcess,
                                GET-POINTER-VALUE(lphMod),
                                GET-SIZE(lphMod),
                                OUTPUT cbNeeded,
                                OUTPUT ReturnValue).
       IF ReturnValue NE 0 THEN DO:
 
          DO i=1 TO cbNeeded / 4 :
            hModule = GET-LONG(lphMod, (i - 1) * 4 + 1).
            szModuleName = "" + FILL(" ", {&MAX_PATH}).
            RUN GetModuleBaseNameA (hProcess,
                                    hModule,
                                    OUTPUT szModuleName,
                                    LENGTH(szModuleName),
                                    OUTPUT ReturnValue).
            /* ReturnValue is the number of returned bytes (chars): */
            szModuleName = TRIM(SUBSTRING(szModuleName,1,ReturnValue)).
 
            szModuleNameEx = "" + FILL(" ", {&MAX_PATH}).
            RUN GetModuleFileNameExA (hProcess,
                                      hModule,
                                      OUTPUT szModuleNameEx,
                                      LENGTH(szModuleNameEx),
                                      OUTPUT ReturnValue).
            /* ReturnValue is the number of returned bytes (chars): */
            szModuleNameEx = TRIM(SUBSTRING(szModuleNameEx,1,ReturnValue)).
 
            CREATE module.
            ASSIGN module.moduleName = szModuleName
                   module.modulePath = szModuleNameEx
                   module.cntUsage   = ?
                   module.hModule    = hModule.
 
          END.
 
          SET-SIZE (lphMod) = 0.
       END.
       RUN CloseHandle(hProcess, OUTPUT ReturnValue).
    END.
END PROCEDURE.

Definitions used in this procedure, not listed in windows.p :

&GLOB TH32CS_SNAPMODULE 8
&GLOB PROCESS_QUERY_INFORMATION 1024
&GLOB PROCESS_VM_READ 16
 
PROCEDURE CreateToolhelp32Snapshot EXTERNAL "kernel32.dll" :
  DEFINE INPUT  PARAMETER dwFlags        AS LONG.
  DEFINE INPUT  PARAMETER th32ProcessId  AS LONG.
  DEFINE RETURN PARAMETER hSnapShot      AS LONG.
END PROCEDURE.
 
PROCEDURE Module32First EXTERNAL "kernel32.dll" :
  DEFINE INPUT  PARAMETER hSnapShot        AS LONG.
  DEFINE INPUT  PARAMETER lpModuleEntry32  AS MEMPTR.
  DEFINE RETURN PARAMETER ReturnValue      AS LONG.
END PROCEDURE.
 
PROCEDURE Module32Next EXTERNAL "kernel32.dll" :
  DEFINE INPUT  PARAMETER hSnapShot        AS LONG.
  DEFINE INPUT  PARAMETER lpModuleEntry32  AS MEMPTR.
  DEFINE RETURN PARAMETER ReturnValue      AS LONG.
END PROCEDURE.
 
PROCEDURE EnumProcessModules EXTERNAL "psapi.dll" :
  DEFINE INPUT  PARAMETER hProcess    AS LONG.
  DEFINE INPUT  PARAMETER lphModule   AS LONG.  /* lp to array of module handles */
  DEFINE INPUT  PARAMETER cb          AS LONG.
  DEFINE OUTPUT PARAMETER cbNeeded    AS LONG.
  DEFINE RETURN PARAMETER ReturnValue AS LONG.
END PROCEDURE.
 
PROCEDURE GetModuleBaseNameA EXTERNAL "psapi.dll" :
  DEFINE INPUT  PARAMETER hProcess      AS LONG.
  DEFINE INPUT  PARAMETER hModule       AS LONG.
  DEFINE OUTPUT PARAMETER lpBaseName    AS CHARACTER.
  DEFINE INPUT  PARAMETER nSize         AS LONG.
  DEFINE RETURN PARAMETER nReturnedSize AS LONG.
END PROCEDURE.
 
PROCEDURE GetModuleFileNameExA EXTERNAL "psapi.dll" :
  DEFINE INPUT  PARAMETER hProcess      AS LONG.
  DEFINE INPUT  PARAMETER hModule       AS LONG.
  DEFINE OUTPUT PARAMETER lpFileName    AS CHARACTER.
  DEFINE INPUT  PARAMETER nSize         AS LONG.
  DEFINE RETURN PARAMETER nReturnedSize AS LONG.
END PROCEDURE.

Notes:

Function RunningWindowsNT4( ) is covered on page which version of Windows is running.

Procedure GetProductVersion(..) is covered on page File version information.

If you only want to find the path and name of the the current Progress executable module ("prowin32.exe") it is much more convenient to call GetModuleFileName.