Asynchronous catching of WM_PAINT window message, can it be done ?

Discussion forum for all Windows batch related topics.

Moderator: DosItHelp

Post Reply
Message
Author
shodan
Posts: 46
Joined: 01 May 2023 01:49

Asynchronous catching of WM_PAINT window message, can it be done ?

#1 Post by shodan » 26 Sep 2023 03:13

Hi,

Today I tried to do probably the impossible.
I feel burned out, let me tell you the story.

I was playing around with powershell hybrid functions

I made one that gets the console's hWND and draws a red circle on it. (using GetConsoleWindow, GetDC, CreateSolidBrush, SelectObject, Ellipse)

And that changes the command window to a "shape". (using GetWindowRect, CreateEllipticRgn, SetWindowRgn)

Draw a red circle
2023-09-26 04_32_15-Command Prompt.png
2023-09-26 04_32_15-Command Prompt.png (19.7 KiB) Viewed 5431 times

Code: Select all

for /f "tokens=*" %%a in ('powershell -command "Add-Type -TypeDefinition 'using System; using System.Runtime.InteropServices; public class NativeMethods { [DllImport(\"kernel32.dll\")] public static extern IntPtr GetConsoleWindow(); [DllImport(\"user32.dll\", SetLastError = true)] public static extern IntPtr GetDC(IntPtr hWnd); [DllImport(\"user32.dll\")] public static extern bool ReleaseDC(IntPtr hWnd, IntPtr hDC); [DllImport(\"gdi32.dll\")] public static extern IntPtr CreateSolidBrush(int crColor); [DllImport(\"gdi32.dll\")] public static extern bool Ellipse(IntPtr hdc, int nLeftRect, int nTopRect, int nRightRect, int nBottomRect); [DllImport(\"gdi32.dll\")] public static extern IntPtr SelectObject(IntPtr hdc, IntPtr h); [DllImport(\"gdi32.dll\", SetLastError = true)] public static extern bool DeleteObject(IntPtr ho); }'; $hWndConsole = [NativeMethods]::GetConsoleWindow(); $hDC = [NativeMethods]::GetDC($hWndConsole); $redBrush = [NativeMethods]::CreateSolidBrush(0x0000FF); $oldBrush = [NativeMethods]::SelectObject($hDC, $redBrush); [NativeMethods]::Ellipse($hDC, 0, 0, 100, 100); [NativeMethods]::SelectObject($hDC, $oldBrush); [NativeMethods]::DeleteObject($redBrush); [NativeMethods]::ReleaseDC([IntPtr]::Zero, $hDC);"') do ( break ) 
Change window region
2023-09-26 04_33_25-.png
2023-09-26 04_33_25-.png (23.75 KiB) Viewed 5431 times

Code: Select all

for /f "tokens=*" %%i in ('powershell -command "Add-Type -TypeDefinition 'using System; using System.Runtime.InteropServices; [StructLayout(LayoutKind.Sequential)] public struct RECT { public int left; public int top; public int right; public int bottom; } public class WindowShape { [DllImport(\"user32.dll\")] public static extern bool SetWindowRgn(IntPtr hWnd, IntPtr hRgn, bool bRedraw); [DllImport(\"gdi32.dll\")] public static extern IntPtr CreateEllipticRgn(int nLeftRect, int nTopRect, int nRightRect, int nBottomRect); [DllImport(\"gdi32.dll\")] public static extern bool DeleteObject(IntPtr hObject); [DllImport(\"user32.dll\")] public static extern bool GetWindowRect(IntPtr hWnd, out RECT lpRect); }'; $rect = New-Object RECT; [WindowShape]::GetWindowRect([IntPtr]%~1, [ref]$rect); $hRgn = [WindowShape]::CreateEllipticRgn($rect.left, $rect.top, $rect.right, $rect.bottom); [WindowShape]::SetWindowRgn([IntPtr]%~1, $hRgn, $true); [WindowShape]::DeleteObject($hRgn);"') do ( break )
With these experiment, I see the entire win32 api is easily accessible to batch via powershell, although it is slow to load the first time.

I quickly noticed that painting on the console hWND is very impermanent.

The drawn pixels will get mangled if the user scroll and will entirely disappears if the user resizes the window.

So I figured, while I am painting, I need to listen for the WM_PAINT messages to know when to redraw the whole window.

This may be impossible.

It all comes down to one of these function calls

Code: Select all

$hookId = [NativeMethods]::SetWindowsHookEx([NativeMethods]::WH_CALLWNDPROC, $global:windowProc, $hModule, $threadId)
$hookId = [NativeMethods]::SetWindowsHookEx([NativeMethods]::WH_GETMESSAGE, $global:windowProc, [IntPtr]::Zero, 0)
$hookId = [NativeMethods]::SetWindowsHookEx([NativeMethods]::WH_CALLWNDPROCRET, $global:windowProc, [IntPtr]::Zero, 0)
WH_GETMESSAGE and WH_CALLWNDPROCRET only seem to work in global more, you need to leave those two last parameter to null/0 or else they will give you error 1429 (must be global).
Likewise WH_CALLWNDPROC will give you error 1428 (can' t be global).

There was another complication with that $threadId !

I don't know how to automatically obtain the correct threadID.
I could only obtain it manually with the software https://github.com/learn-more/WindowsHookEx

This application gave me the thread ID and I could see the WM_PAINT messages I wanted to catch.

From that threadID, I back tracked to the process ID with this powershell code

Code: Select all

$mymanuallyknownthreadId = 8100
$threadHandle = [NativeMethods]::OpenThread([NativeMethods]::THREAD_QUERY_INFORMATION, $false, $mythreadId)
if ($threadHandle -eq [IntPtr]::Zero) {
    throw "Failed to open thread."
}
try {
    $myprocessId = [NativeMethods]::GetProcessIdOfThread($threadHandle)
} finally {
    [void][NativeMethods]::CloseHandle($threadHandle)
}
Then using the task manager, I discovered that this was conhost.exe.


Using GetConsoleWindow, I found my hWND, from my hWND I found my process ID and thread ID, they are not the same as conhost.exe

Code: Select all

$hWndConsole = [NativeMethods]::GetConsoleWindow()
$processId = 0
$threadId = [NativeMethods]::GetWindowThreadProcessId($hWndConsole, [ref]$processId)
Worse, there were multiple conhost.exe running on my system. I have failed to discover, which conhost.exe is associated with the PID/TID obtained from GetConsoleWindow

In the end, I did have the correct ThreadID obtained from WindowsHookEx

Code: Select all

$hookId = [NativeMethods]::SetWindowsHookEx([NativeMethods]::WH_CALLWNDPROC, $global:windowProc, $hModule, $threadId)
Would always return error 5 (0x5)/Access is denied./ERROR_INVALID_HANDLE
Even though I was sure of all the values and I tried everything running administrator or user, same thing

For the other two

Code: Select all

$hookId = [NativeMethods]::SetWindowsHookEx([NativeMethods]::WH_GETMESSAGE, $global:windowProc, [IntPtr]::Zero, 0)
$hookId = [NativeMethods]::SetWindowsHookEx([NativeMethods]::WH_CALLWNDPROCRET, $global:windowProc, [IntPtr]::Zero, 0)
[/code]

Nothing would happen, until I moved the mouse. I could see in WindowsHookEx, it would do nothing, until there was a message, when it would crash the console to desktop.

https://stackoverflow.com/questions/661 ... 32api-call
https://hinchley.net/articles/creating- ... powershell
https://www.betaarchive.com/wiki/index. ... ive/318804

It is said in many places, that this cannot be done (with managed memory languages). That a DLL has to be injected into.... the intercepted software ? user32.dll ? unclear

At this point I sort of lost where I was going with this !

Two massive, possibly impassable problems.

Anyway, I'm going to make many hybrid batch/powershell functions out of all this, to create simple to use batch functions to call win32 api.

Code: Select all

What I most would like to know now is, 

How do you find the thread ID that handles the console's messages ?

Aacini
Expert
Posts: 1885
Joined: 06 Dec 2011 22:15
Location: México City, México
Contact:

Re: Asynchronous catching of WM_PAINT window message, can it be done ?

#2 Post by Aacini » 26 Sep 2023 21:23

OK. Although this reply doesn't answer your question, I think you'll find it interesting because the topic is similar: drawing graphics on the screen and writing Batch hybrid scripts with both PowerShell and JScript languages.


A method to Draw pixel resolution graphics in text mode using a 1x1 pixels size font and use it to show standard graphics (like the Mandelbrot Set), and even use it to show text like the OS internal routines, that is, read a font definition file and use it to show the individual pixels that forms each character.


A Batch-PowerShell hybrid script to Read arrow keys and show color text in an efficient way. This method is fast enough to write animated games in Batch, like Tetris in Color, or VIBORAS.bat: A multi-player version in color of Snake game.


A method to combine Batch code, JScript code and HTA (HTML) code in a tri-hybrid script to create separate windows that show graphics via the "canvas" tag of the HTML5 specification, under control of the Batch code. Two examples of the use of this technique are Batch-BGI graphics library for Batch files, and "Turtle Graphics" in Batch files. An interesting application of Turtle Graphics (among others) is the drawing of Open Recursive Curves.


Finally, I invite you to review a very brief autobiography where I tell some interesting points about my own developments.

Best regards

Antonio

mdev123
Posts: 2
Joined: 27 Sep 2023 15:44

Re: Asynchronous catching of WM_PAINT window message, can it be done ?

#3 Post by mdev123 » 27 Sep 2023 15:49

Even though I have (a very ugly and hacky indeed) method of grabbing the associated conhost, I have never managed to override WM_PAINT, or any window message for that matter. Perhaps I'd need to inject a dll into the conhost and do work from there. Or there may be another way, which i haven't found/tested

Post Reply