Forum > Windows

Windows API test programs / Examples

<< < (4/12) > >>

440bx:
Hello,

Attached to this post are six (6) "teaser" API programs that give an idea of what these programs/examples are about.  In addition, there is a "Windows Bitness" program that is more an illustration of how to get the Windows bitness than how to use a specific API.

The teaser examples do _not_ include source at this time.  I will include the source when the time comes to give an example of the API they demonstrate.  The "Windows Bitness" includes full source to give an idea of what the source code for a typical example looks like. 

I will first give a quick description of the purpose of the "teaser" programs before discussing details of the "Windows Bitness" program.  The teaser program/examples are:

ExpandEnvironmentStrings

This is an example of the many trivial API functions in Windows.  These are APIs that don't have much to say about them.  IOW, you read its documentation in MSDN, code the call and you're done.

Roughly (iow, an estimate) 50% of the examples fall in that category.  Their main reason to exist was to test their performance and/or behavior in unusual cases.  (those were my personal reasons.)


SetConsoleWindowInfo

The description of SetConsoleWindowInfo in MSDN isn't particularly enlightening and the MSDN example's purpose seems to be to confuse the reader (just in case you understood the questionable description that preceded it.) 

The teaser example is a visual representation of how that API works.  It shows the parameters before the call, the parameters after the call and the effects of the call.

Roughly, 10% of the examples fall in that category (API operation visualization).

I won't go into a detailed explanation at this time.  It will be included along with the program source when it is its turn.


ExtTextOut (two (2) examples)
InvalidateRect
SelectClipRgn

These examples, in addition to demonstrating what the API does and how it works are related by one common goal which is, reducing or eliminating _flicker_ when redrawing.

The InvalidateRect example flickers when the mouse coordinates are updated in the area dedicated to them.  Selecting "Invalidate entire area" from the InvalidateRect menu shows how the size of the invalid area makes the flicker more noticeable.


ExtTextOut (v1)

That example is almost identical to the InvalidateRect example but, it demonstrates how to use ExtTextOut to significantly reduce flicker.  In the case of the example, to a level that is almost imperceptible.


ExtTextOut (v0)

This example also demonstrates how to significantly reduce flicker but in a larger area.  It also demonstrates the clipping abilities of ExtTextOut.   The example also shows how to slow a GUI program down by using a console and, how to use the console to help in debugging.


SelectClipRgn

This example shows one the ways available to totally eliminate flicker (zero flicker without using double buffering.)  Resize the window horizontally and/or vertically.  Control flicker/no flicker with the menu options under "Flicker".


Window Bitness

Consider a program, let's call it program "A" that can be compiled as a 32 bit and 64 bit versions.

The 64 bit version of the program has it easy.  All it has to do is determine that it is itself a 64 bit program, once it knows that, it must be running on a 64 bit version of Windows (until MS releases a 128 bit version of Windows - won't happen tomorrow.)  To determine if it is itself a 64 bit program, all it has to do is check the size of a pointer, i.e, sizeof(pointer) and if the size is 8 then it knows that it is a 64 bit program and since it is running, it must be running under a 64 bit version of Windows.  Easy as pie.

The 32 bit version cannot use that "trick" since its pointer size will be four (4) whether or not it is running under a 32 bit or 64 bit version of Windows.

MS would have the programmer use the IsWowProcess() or IsWowProcess2() API to make the determination but, those two APIs have a few annoyances associated with them.  First, they may not be present (IsWowProcess() requires XP SP2 or higher) and, even when present, the user running the program must have some privileges, that while not high, he/she may not have.

There is a way to determine the bitness without using either or these APIs that do not require the user to have any "special" privileges.  The "trick" resides in two details.  The first one is that the Windows server is what determines the Windows bitness, the second one is that LoadLibraryEx will load almost anything you want as long as you don't intend to run it.

Using the first observation, if Windows is 32 bit then its csrss.exe (client service runtime subsystem) will also be 32 bit and, it will always be 64 bit in a 64 bit version of Windows.

Using the second observation, we can simply ask LoadLibraryEx to load csrss.exe as a datafile and unless the user is totally "privilege destitute", LoadLibraryEx will serve csrss.exe in a sliver platter. 

All that's left is for the program to rummage in csrss.exe to determine its bitness and whatever its bitness is, that's the Windows bitness too :)  Done!

That's what the program below does (full source in the attachment)
--- Code: Pascal  [+][-]window.onload = function(){var x1 = document.getElementById("main_content_section"); if (x1) { var x = document.getElementsByClassName("geshi");for (var i = 0; i < x.length; i++) { x[i].style.maxHeight='none'; x[i].style.height = Math.min(x[i].clientHeight+15,306)+'px'; x[i].style.resize = "vertical";}};} ---{$define         DEBUG}     { to cause a breakpoint in WM_CREATE handler      } {$APPTYPE        GUI} {$LONGSTRINGS    OFF}{$WRITEABLECONST ON}{$DESCRIPTION    'Win32 API function - WindowsBitness example'} {$R WindowsBitness.Res} program _WindowsBitness;  { Win32 technique - WindowsBitness example                                  } uses Windows,     Messages,     Resource     ; const  AppNameBase  = 'WindowsBitness';   {$ifdef WIN64}                         { heading for 64 bit                 }    Bitness64  = ' - 64bit';    AppName    = AppNameBase + Bitness64;  {$else}                                { heading for 32 bit                 }    Bitness32  = ' - 32bit';    AppName    = AppNameBase + Bitness32;  {$endif}   AboutBox     = 'AboutBox';  APPICON      = 'APPICON';  APPMENU      = 'APPMENU'; {-----------------------------------------------------------------------------} {$ifdef VER90} { Delphi 2.0 }type  ptrint  = longint;  ptruint = dword;{$endif} {$include ImageHeaders.inc} function IsDebuggerPresent                       { missing in Delphi 2        }         : BOOL; stdcall; external 'kernel32';  {-----------------------------------------------------------------------------} function About(DlgWnd : hWnd; Msg : UINT; wParam, lParam : ptrint)         : ptrint; stdcall;begin  About := ord(TRUE);   case Msg of     WM_INITDIALOG: exit;     WM_COMMAND:    begin      if (LOWORD(wParam) = IDOK) or (LOWORD(wParam) = IDCANCEL) then      begin        EndDialog(DlgWnd, ord(TRUE));         exit;      end;    end;  end;   About := ord(FALSE);end; {-----------------------------------------------------------------------------} function GetModuleBitness(Module : HMODULE) : integer;var  DosHeader      : PIMAGE_DOS_HEADER;  NtHeader       : PIMAGE_NT_HEADERS;  OptionalHeader : PIMAGE_OPTIONAL_HEADER; begin  result := 0;   HMODULE(DosHeader) := Module;   { ensure we got a valid PE file                                             }   if IsBadReadPtr(DosHeader, sizeof(DosHeader^))           then exit;  if DosHeader^.Signature <> IMAGE_DOS_SIGNATURE           then exit;   pointer(NtHeader) := pchar(DosHeader) + DosHeader^.OffsetToNewExecutable;   if IsBadReadPtr(NtHeader, sizeof(NtHeader^))             then exit;   OptionalHeader := @NtHeader^.OptionalHeader;   if IsBadReadPtr(OptionalHeader, sizeof(OptionalHeader^)) then exit;   case OptionalHeader^.Magic of    IMAGE_NT_OPTIONAL_HDR32_MAGIC : result := 32;    IMAGE_NT_OPTIONAL_HDR64_MAGIC : result := 64;     { otherwise leave it at zero                                              }  end;end; {-----------------------------------------------------------------------------} function WndProc (Wnd : HWND; Msg : UINT; wParam, lParam : ptrint)         : ptrint; stdcall;  { main application/window handler function                                  }const  WindowBitness_Call     = 'Windows bitness';   Bitness32              = 'This is a 32 bit Windows installation'#0;  Bitness64              = 'This is a 64 bit Windows installation'#0;   BitnessUnknown         = 'failed to determine this Windows installation '   +                           'bitness'#0;   { initialize to "unknown" until we determine the Windows bitness            }   Bitness                : pchar = BitnessUnknown;   CSRSS                  = 'csrss.exe';  BACKSLASH              = '\';   _64BIT_POINTER_SIZE    = 8; var  ps                     : TPAINTSTRUCT;  ClientRect             : TRECT;   TextSize               : TSIZE;   CsrssFullpath : packed array[0..511] of char;   i                      : DWORD;  CsrssLoadAddress       : HMODULE;  LoadAddress            : HMODULE; begin  WndProc := 0;   case Msg of    WM_CREATE:    begin      {$ifdef DEBUG}        if IsDebuggerPresent() then DebugBreak();      {$endif}        { when using FPC and compiling for 32 bit, ignore the unreachable code  }      { warning.                                                              }       if sizeof(pointer) = _64BIT_POINTER_SIZE then      begin        { we are a running 64bit program, therefore the Windows installation  }        { must be 64bit.  Not much to do in this case.                        }         Bitness := Bitness64;         exit;                               { we are done!                    }      end;       { if we are a 32bit program, we need to find out if this is a 32bit or  }      { 64bit windows installation.                                           }       ZeroMemory(@CsrssFullpath, sizeof(CsrssFullpath));      GetSystemDirectory(CsrssFullpath,                         sizeof(CsrssFullpath));       { append CSRSS to the system directory and a backslash if needed        }       i := lstrlen(CsrssFullpath);       if CsrssFullpath[i - 1] <> BACKSLASH then      begin        { append the backslash                                                }         lstrcat(CsrssFullpath, BACKSLASH);      end;       lstrcat(CsrssFullpath, CSRSS);             { path to CSRSS              }       { load CSRSS as a data file. At this time, Windows will not apply file  }      { system redirection when the call to LoadLibraryEx is to load a file   }      { as a data file.  Therefore csrss.exe should have been found, if it    }      { wasn't then we are dealing with an unexpected situation in which case }      { we declare the attempt to determine the O/S as having failed.         }       CsrssLoadAddress := LoadLibraryEx(CsrssFullpath,                                        0,                                        LOAD_LIBRARY_AS_DATAFILE);       if CsrssLoadAddress = 0 then      begin         exit;      end;       { because we specified LOAD_LIBRARY_AS_DATAFILE the load address        }      { returned isn't "quite right".  When LOAD_LIBRARY_AS_DATAFILE is used  }      { the address points one (1) byte past the actual load address.  We     }      { need the "real" address, therefore we subtract one (1) from the       }      { address returned in this case.                                        }       LoadAddress := CsrssLoadAddress;      if LoadAddress and $1 <> 0 then      begin        dec(LoadAddress);      end;       { presuming there are only two versions of Windows, a 32 bit version    }      { and a 64 bit version, the module bitness should be 32 bit, if it      }      { isn't then we must be running under an unexpected version that is     }      { neither 32 nor 64 bit.  In that case, the Bitness is left as "unknown"}       case GetModuleBitness(LoadAddress) of        32 : Bitness := Bitness32;        64 : Bitness := Bitness64;      end;       FreeLibrary(CsrssLoadAddress);  { we no longer need it                  }       exit;    end;     WM_PAINT:    begin      BeginPaint(Wnd, ps);       { set up the dc                                                         }       GetClientRect(Wnd, ClientRect);       SelectObject(ps.hdc, GetStockObject(DEFAULT_GUI_FONT));      SetBkMode(ps.hdc, TRANSPARENT);      SetTextAlign(ps.hdc, TA_CENTER or TA_BOTTOM);       GetTextExtentPoint32(ps.hdc,                           Bitness,                           lstrlen(Bitness),                           TextSize);       {-----------------------------------------------------------------------}      { output the bitness of this Windows installation as determined during  }      { WM_CREATE.                                                            }       TextOut(ps.hdc,              ClientRect.Right  div 2,              ClientRect.Bottom div 2,              Bitness,              lstrlen(Bitness));        {-----------------------------------------------------------------------}      { draw the function call                                                }       TextOut(ps.hdc,              ClientRect.Right  div 2,              ClientRect.Bottom - TextSize.cy,              WindowBitness_Call,              lstrlen(WindowBitness_Call));       {-----------------------------------------------------------------------}      { we're done painting                                                   }       EndPaint(Wnd, ps);       exit;    end;     WM_COMMAND:    begin      case LOWORD(wParam) of        IDM_ABOUT:        begin          DialogBox(hInstance, ABOUTBOX, Wnd, @About);           exit;        end; { IDM_ABOUT }         IDM_EXIT:        begin          DestroyWindow(Wnd);           exit;        end; { IDM_EXIT }      end; { case LOWORD(wParam) }    end; { WM_COMMAND }     WM_DESTROY:    begin      PostQuitMessage(0);       exit;    end; { WM_DESTROY }  end; { case msg }   WndProc := DefWindowProc (Wnd, Msg, wParam, lParam);end; {-----------------------------------------------------------------------------} function InitAppClass: WordBool;  { registers the application's window classes                                }var  cls : TWndClassEx; begin  cls.cbSize          := sizeof(TWndClassEx);           { must be initialized }   if not GetClassInfoEx (hInstance, AppName, cls) then  begin    with cls do    begin      { cbSize has already been initialized as required above                 }       style           := CS_BYTEALIGNCLIENT;      lpfnWndProc     := @WndProc;                    { window class handler  }      cbClsExtra      := 0;      cbWndExtra      := 0;      hInstance       := system.hInstance;      hIcon           := LoadIcon (hInstance, APPICON);      hCursor         := LoadCursor(0, IDC_ARROW);      hbrBackground   := GetSysColorBrush(COLOR_WINDOW);      lpszMenuName    := APPMENU;                     { Menu name             }      lpszClassName   := AppName;                     { Window Class name     }      hIconSm         := LoadImage(hInstance,                                   APPICON,                                   IMAGE_ICON,                                   16,                                   16,                                   LR_DEFAULTCOLOR);    end; { with }     InitAppClass := WordBool(RegisterClassEx(cls));  end  else InitAppClass := TRUE;end; {-----------------------------------------------------------------------------} function WinMain : integer;  { application entry point                                                   }var  Wnd : hWnd;  Msg : TMsg; begin  if not InitAppClass then Halt (255);  { register application's class        }   { Create the main application window                                        }   Wnd := CreateWindowEx(WS_EX_CLIENTEDGE,                        AppName,                { class name                  }                        AppName,                { window caption text         }                        ws_Overlapped       or  { window style                }                        ws_SysMenu          or                        ws_MinimizeBox      or                        ws_ClipSiblings     or                        ws_ClipChildren     or  { don't affect children       }                        ws_Visible,             { make showwindow unnecessary }                        20,                     { x pos on screen             }                        20,                     { y pos on screen             }                        400,                    { window width                }                        200,                    { window height               }                        0,                      { parent window handle        }                        0,                      { menu handle 0 = use class   }                        hInstance,              { instance handle             }                        nil);                   { parameter sent to WM_CREATE }   { a message box indicating failure and the reason for it would be more      }  { desirable.                                                                }   if Wnd = 0 then Halt;                         { could not create the window }   while GetMessage (Msg, 0, 0, 0) do            { wait for message            }  begin    TranslateMessage (Msg);                     { key conversions             }    DispatchMessage  (Msg);                     { send to window procedure    }  end;   WinMain := Msg.wParam;                        { terminate with return code  }end; begin  WinMain;end. On line 192, the program uses LoadLibraryEx to load csrss.exe.  On line 211, it checks the address returned by LoadLibraryEx, which fiddles with the LSB to indicate that it was loaded as a data file and adjusts the address to turn it into the real load address. 

On line 219, using the real load address, it determines the module bitness (see GetModuleBitness on line 75.)  If anything does not go according to plan, the program states it could not determine the Windows bitness, otherwise it outputs it.  Easy as 32 bit pie. :)


My intention is to start with the simplest most trivial API and build from there.  This way those who follow the examples can gain the necessary knowledge to understand more subtle and complex use the API.

NOTE:  There is an example dedicated to LoadLibraryEx by itself which shows how it behaves under various circumstances.


About 1% (maybe slightly more) of the examples are like the one above, where one API along with "peripheral" knowledge can be used to extract information out of Windows.

I suggest creating a "WinAPI" directory and under that directory, create directories named "Techniques" (which the above example would go into), ntdll, kernel32, gdi32 and user32.  Of course, you're free the arrange the examples however you like but, I have found that way to make the large number of programs easier to manage and find.



All comments and feedback welcome.

PascalDragon:

--- Quote from: 440bx on January 14, 2021, 06:14:21 pm ---MS would have the programmer use the IsWowProcess() or IsWowProcess2() API to make the determination but, those two APIs have a few annoyances associated with them.  First, they may not be present (IsWowProcess() requires XP SP2 or higher) and, even when present, the user running the program must have some privileges, that while not high, he/she may not have.

There is a way to determine the bitness without using either or these APIs that do not require the user to have any "special" privileges.  The "trick" resides in two details.  The first one is that the Windows server is what determines the Windows bitness, the second one is that LoadLibraryEx will load almost anything you want as long as you don't intend to run it.

Using the first observation, if Windows is 32 bit then its csrss.exe (client service runtime subsystem) will also be 32 bit and, it will always be 64 bit in a 64 bit version of Windows.

Using the second observation, we can simply ask LoadLibraryEx to load csrss.exe as a datafile and unless the user is totally "privilege destitute", LoadLibraryEx will serve csrss.exe in a sliver platter. 

All that's left is for the program to rummage in csrss.exe to determine its bitness and whatever its bitness is, that's the Windows bitness too :)  Done!
--- End quote ---

Wouldn't it be better to check for WoW64.dll? If you're running on a 32-bit system that library won't exist/load and on a 64-bit system it will be always loaded into a 32-bit process already thus avoiding the need to map csrss.exe into the address space.

440bx:

--- Quote from: PascalDragon on January 15, 2021, 09:35:25 am ---Wouldn't it be better to check for WoW64.dll? If you're running on a 32-bit system that library won't exist/load and on a 64-bit system it will be always loaded into a 32-bit process already thus avoiding the need to map csrss.exe into the address space.

--- End quote ---
That was my first thought too and ended up being more cumbersome than going after csrss.exe.   

Going after Wow64.dll, the first thing I tried was using GetModuleHandle, it failed.  Tried using LoadLibrary and it also failed, most likely because a 32 bit app is not allowed to load a 64bit dll.  It would work with LoadLibraryEx specifying "load as datafile" but that dll is 238K whereas csrss.exe is only 8K.  Better (faster) to load the smaller one.

Another way that works is to use any of the module enumeration API (toolhelp32 or EnumProcessModules) but that requires more code and it's slower.

After trying a number of ways, going after csrss.exe with LoadLibraryEx ended up being the solution that required the least amount of code, least in required user privileges and still reliable.


ETA

The call to GetTextExtentPoint32 on lines 241 through 244 is superfluous and can be removed.  It cannot be removed because the height of the font is used to calculate the Y coordinate used in TextOut. 

marcov:
Best to use the MS advised way, dynload it if you have to, and only use hackish ways for XP. That avoids the chance that some future way breaks the hackish way for non XP systems.

But I think this goes a bit far for the infinitesimally small percentage of users that still run XP 64-bit, a number that was never very big to being with.   

So dynloading iswowprocess(2) and returning 32-bit if that fails is IMHO more than good enough. 

440bx:
@marcov

Honestly, I would rather depend on checking the bitness of csrss.exe than use either IsWowProcess or IsWowProcess2. 

csrss.exe isn't going to go away anytime soon and loading it as a data file requires no privileges.  Additionally, the IsWow64Process() documentation states the following:
--- Quote ---If the process is a 32-bit application running under 64-bit Windows 10 on ARM, the value is set to FALSE.
--- End quote ---
which means that calling IsWow64Process on an ARM platform would tell the 32 bit program that it is _not_ running on a 64 bit Windows even though it is. 

IOW, I avoid APIs that are "moody".  I prefer methods that return unadulterated facts.  Checking csrss.exe's bitness is much more reliable.

Navigation

[0] Message Index

[#] Next page

[*] Previous page

Go to full version