Recent

Author Topic: WinAPI - examples - post 4  (Read 1938 times)

440bx

  • Hero Member
  • *****
  • Posts: 4014
WinAPI - examples - post 4
« on: January 24, 2021, 08:59:41 am »
This post includes examples for the following APIs :

WinApi - CreateStatusWindow (example is named StatusBar)

This example shows the use of the obsolete CreateStatusWindow API and a "quirk" in the number of characters a status bar displays.   CreateWindow or CreateWindowEx should be used instead of CreateStatusWindow.  The fact that the API is obsolete and that the example demonstrates a quirk in the status bar is the reason the example is not named after the API.

The sample program attempts to display a string that is longer than 137 characters in the status bar.  The string to be displayed consists of a sentence that is repeated three times.  If the status bar does not display the same sentence three times then truncation occurred.

To show where the truncation took place, the text from the status bar is retrieved using SB_GETTEXT and subsequently displayed in the listbox.  If the listbox displays the sentence three times (as expected), it indicates that the status bar did save the entire string but displayed only part of it.

In spite of the fact that the status bar will only display 137 characters, it does keep a full copy of the string that was sent to it using SB_SETTEXT.

Under build 950 of Windows 95 and later versions as well, the status bar control will not display more than 137 characters.  It is not known whether this limit is intentional or not but, it does not seem to be documented anywhere.



WinAPI - GetEnvironmentStrings

This example shows how to call the GetEnvironmentStrings to, as its name indicates, obtain the environment strings.  It also shows how to parse the block of memory where the strings reside.  As each individual string is identified it loads it into a listbox.

NOTE: this example takes a "shortcut" when it comes to determining the listbox horizontal extent.  In production code, the horizontal extent should be calculated as the width of the longest string added into the listbox.  This simple program uses a hard coded estimate which in many cases will result in the "PATH" string not being fully visible.

Future examples will show how to calculate the horizontal extent properly.

Also, this example doesn't even bother to free the memory block created by GetEnvironmentStrings which should be done to avoid a potential memory leak. See the FreeEnvironmentStrings example below.

This example also shows how to create a status bar at the top of a window as well as at its bottom (the more common case.)  However, note that the API CreateStatusWindow is now obsolete.  CreateWindow or CreateWindowEx should be prefered.  Anything CreateStatusWindow can do, those APIs can do just as well.

Also, this example does not show how to workaround a bug in the listbox control.  The bug _sometimes_ causes the contents of the listbox to be garbled when the window is resized and the horizontal scroll thumb is not at the origin, i.e, horizontal scrolling has taken place.  The reason the workaround code is not present is because the window isn't resizable. See the StatusBar example, which uses a resizable window, for the workaround.  Note, that the presence of this bug is likely dependent on the version of Windows and isn't triggered consistently.




WinAPI - FreeEnvironmentStrings

This example calls the GetEnvironmentStrings API immediately followed by a call to FreeEnvironmentStrings.

In Windows 9x the FreeEnvironmentStrings function does not free the environment strings.  It verifies that the parameter passed does indeed point to the process' environment strings.  If it does, the function returns TRUE otherwise it returns FALSE but it never frees the block.

In other versions of Windows, FreeEnvironmentStrings does free a memory block that contains the environment strings.



WinAPI - ExpandEnvironmentStrings

This example shows how to retrieve the value of a known environment string.



WinAPI - GetModuleHandle

This example shows how to call GetModuleHandle to retrieve the load address of modules in the process including the .exe itself.

It also serves as an example for the WinAPI - ModifyMenu.  The items under the "GetModuleHandle" menu change depending on the bitness of the program.  The change is done using ModifyMenu.  Notice that the items in the menu are _not_ those declared in the .rc resource file and differ depending on the program's bitness.

It can also serve as an example for the WinAPI - GetMenu and WinAPI - GetSubMenu.  In order to modify a specific menu item, first the handle to the menu must be obtained using GetMenu and after that the handle to the popup menu associated with the menu item must be obtained using GetSubMenu.  Once the submenu handle is known then ModifyMenu can be called to modify an item in the popup menu.

Note unrelated to any Windows API: the width and precision stated in the StrFmt function is ignored by StrFmt if necessary (it is when the program is 64 bit.)



WinAPI - GetModuleHandleEx

This example shows how to call GetModuleHandleEx to determine a module's load address based on any address that resides in the module.  For instance, knowing that some dll function resides at a specific address, GetModuleHandleEx when given that address can return the Dll's load address.

This ability can often be useful to debuggers.

This example also demonstrates how to "pin" a dll in memory, that is, ensuring that the dll is not unloaded at an undesirable time.

There are, at least, two cases when that capability is useful/required.

The first is in a multi-threaded environment where one thread obtains a dll's module handle while another thread _may_ unload the dll thus invalidating the handle obtained by the first thread.  Note, this situation quite likely indicates very poor programming, a module should _not_ be loaded by one thread and unloaded in another.  IOW, the thread that loads a dll should be the only thread responsible for unloading it.

The second case is not as common as the first one described above.  When a program injects a dll into another process and the injected dll in turn traps functions in application specific dlls then, it is not only conceivable but, even likely that the application may decide to unload one of it dlls that have functions trapped by the injected dll.  When this happens the injected dll will call into the void since the dll functions it was trapping are gone.

Pinning down a dll prevents this very undesirable situation from taking place.

To see the effects of pinning down a dll use the "Module32First" example (source for it will be given in a future post) and follow these steps :

1. After starting the GetModuleHandleEx example, start the Module32First program.
2. Search and select "GetModuleHandleEx.exe" on the left hand side pane of Module32First
3. Note the values of "USG" and "usg" for the "GetModuleHandleExDll" (they should both be two (2)

4. Select "Pin and attempt to unload the dll" from the "GetModuleHandleEx" menu.

5. Select "Update snapshot" in the Module32First program.

6. Repeat steps 2 and 3 above.  The values under "USG" and "usg" should read 65535 indicating that the dll is pinned.

Every time the "Pin and attempt to unload the dll" option is executed, the program attempts to (unsuccessfully) unload the library 100 times.  What should be noted is that 100 is _not_ subtracted from the "USG" and "usg" fields. Once those fields are set to high(word) they are no longer reference counted.

The interesting code portions of this program are:
Code: Pascal  [Select][+][-]
  1.   case Msg of
  2.     WM_CREATE:
  3.     begin
  4.       { load the supporting dll                                               }
  5.  
  6.       ModuleAddressA := LoadLibrary(DllToLoad);
  7.  
  8.       FunctionAddress := GetProcAddress(ModuleAddressA,
  9.                                         pchar(DLL_FUNCTION_INDEX));
  10.  
  11.       { get the module address again but this time using the function address }
  12.  
  13.       if not GetModuleHandleEx(GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS,
  14.                                FunctionAddress,
  15.                                ModuleAddressB) then
  16.       begin
  17.         LastError := GetLastError();
  18.  
  19.         MessageBox(Wnd,
  20.                   'GetModuleHandleEx call failed in WM_CREATE',
  21.                   'Main Window',
  22.                    MB_ICONERROR or MB_OK);
  23.  
  24.         WndProc := -1;
  25.  
  26.         exit;
  27.       end;
  28.  
  29.       { format and save the values obtained                                   }
  30.  
  31.       StrFmt(Values[1], ' %p', [FunctionAddress]);
  32.  
  33.       { the next two addresses should be the same                             }
  34.  
  35.       StrFmt(Values[2], ' %p', [pointer(ModuleAddressA)]);
  36.       StrFmt(Values[3], ' %p', [pointer(ModuleAddressB)]);
  37.  
  38.       exit;
  39.     end;
  40.  
On line 13, GetModuleHandleEx is used to obtain the dll's load address using the address of one of the functions it contains.  During WM_PAINT (not shown here) both addresses, the one obtained this way and the one obtained using GetModuleHandleEx are output, thereby making it easy to verify that both addresses are the same (as they should be)

The other interesting part is :
Code: Pascal  [Select][+][-]
  1.       case LOWORD(wParam) of
  2.         IDM_BYADDRESSANDPIN:
  3.         begin
  4.           if not GetModuleHandleEx(GET_MODULE_HANDLE_EX_FLAG_PIN,
  5.                                    DllToLoad,
  6.                                    ModuleAddressB) then
  7.           begin
  8.             LastError := GetLastError();
  9.  
  10.             MessageBox(Wnd,
  11.                       'GetModuleHandleEx call failed in IDM_BYADDRESSANDPIN',
  12.                       'Main Window',
  13.                        MB_ICONERROR or MB_OK);
  14.  
  15.             WndProc := -1;
  16.  
  17.             exit;
  18.           end;
  19.  
  20.           for i := 1 to 100 do FreeLibrary(ModuleAddressB);
  21.  
  22.         end;
  23.  
On line 4, GetModuleHandleEx is used to pin down the dll in memory then in line 20 FreeLibrary is called 100 times to unload the dll.  Use Module32First to verify that the dll is not freed/unloaded.



WinAPI - Module32First

For the time being, this example is only included to help visualize what GetModuleHandleEx does.  Its source code will be included in a future example.



Enjoy!




ETA

In version 3.0.4 of FPC, GetModuleHandleEx and the constants associated with it are missing.
« Last Edit: January 24, 2021, 10:41:11 am by 440bx »
(FPC v3.0.4 and Lazarus 1.8.2) or (FPC v3.2.2 and Lazarus v3.2) on Windows 7 SP1 64bit.

 

TinyPortal © 2005-2018