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.
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.