Created
October 21, 2023 06:23
-
-
Save anonhostpi/56b3da943138bcb0307d701654c5c9a1 to your computer and use it in GitHub Desktop.
Avalonia.Threading.Dispatcher Testing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<# | |
$Dispatcher = { | |
$App = @{} | |
& { | |
$builder = [Avalonia.AppBuilder]::Configure[Avalonia.Application]() | |
$builder = [Avalonia.AppBuilderDesktopExtensions]::UsePlatformDetect( $builder ) | |
$App.Lifetime = [Avalonia.Controls.ApplicationLifetimes.ClassicDesktopStyleApplicationLifetime]::new() | |
$App.Lifetime.ShutdownMode = [Avalonia.Controls.ShutdownMode]::OnExplicitShutdown | |
$builder = $builder.SetupWithLifetime( $App.Lifetime ) | |
# Return early | |
$App.Instance = $builder.Instance | |
} | |
# Return the UI thread dispatcher | |
[Avalonia.Threading.Dispatcher]::UIThread | |
$App.TokenSource = [System.Threading.CancellationTokenSource]::new() | |
[Avalonia.Controls.DesktopApplicationExtensions]::Run( $App.Instance, $App.TokenSource.Token ) | Out-Null | |
} | |
#> | |
Import-Package Avalonia.Win32 | |
$Dispatcher = & { | |
# Win32 API Window Procedure (WndProc) which we will use to process Win32 API messages | |
$MessageProcessor = & { | |
# Reasons for chunking up the body of $MessageProcessor: | |
# - Allow us to inject the ManagedThreadId into the body | |
$body = @( | |
{ | |
param( | |
[System.IntPtr] $hWnd, | |
[uint] $msg, | |
[System.IntPtr] $wParam, | |
[System.IntPtr] $lParam | |
) | |
}.ToString(), | |
# Used to get the dispatcher and dispatcherImpl | |
"`$id = $($ThreadController.Thread.ManagedThreadId)", | |
# $_dispatcher and $_dispatcherImpl | |
{ | |
$_threads = if( $Threads ){ | |
$Threads | |
} else { | |
(Get-Module -Name New-DispatchThread).Invoke({ $threads }) | |
} | |
$_dispatcher = ($_threads.GetEnumerator() | Where-Object { | |
$_.Value.Thread.ManagedThreadId -eq $id | |
}).Value.Dispatcher | |
$_dispatcherImpl = &{ | |
$_prop = $_dispatcher.GetType().GetProperty( | |
"_controlledImpl", | |
[System.Reflection.BindingFlags]::NonPublic ` | |
-bor [System.Reflection.BindingFlags]::Instance | |
) | |
$_prop.GetValue( $_dispatcher ) | |
} | |
}.ToString(), | |
# $win32 | |
{ | |
$win32 = @{ | |
"assembly" = [Avalonia.Win32PlatformOptions].Assembly | |
} | |
$win32.unmanaged = $win32.assembly.GetTypes() | | |
Where-Object { $_.Name -eq "UnmanagedMethods" } | |
$win32.WindowsMessage = $win32.unmanaged.GetMember("WindowsMessage") | |
$win32.DispatcherImpl = $_dispatcherImpl.GetType() | |
# $win32.assembly.GetTypes() | | |
# Where-Object { $_.Name -eq "Win32DispatcherImpl" } | |
$win32.Platform = $win32.assembly.GetTypes() | | |
Where-Object { $_.Name -eq "Win32Platform" } | |
$signals = @{ | |
"WNDCLASSEX" = [Activator]::CreateInstance( | |
$win32.unmanaged.GetMember("WNDCLASSEX") | |
) | |
"WM_DISPATCH_WORK_ITEM" = ($win32.WindowsMessage.GetEnumValues() | | |
Where-Object { $_.ToString() -eq "WM_USER" })[0].value__ | |
"WM_QUERYENDSESSION" = ($win32.WindowsMessage.GetEnumValues() | | |
Where-Object { $_.ToString() -eq "WM_QUERYENDSESSION" })[0].value__ | |
"WM_SETTINGCHANGE" = ($win32.WindowsMessage.GetEnumValues() | | |
Where-Object { $_.ToString() -eq "WM_SETTINGCHANGE" })[0].value__ | |
"WM_TIMER" = ($win32.WindowsMessage.GetEnumValues() | | |
Where-Object { $_.ToString() -eq "WM_TIMER" })[0].value__ | |
"SignalW" = $win32.DispatcherImpl.GetField( | |
"SignalW", | |
[System.Reflection.BindingFlags]::NonPublic ` | |
-bor [System.Reflection.BindingFlags]::Static | |
).GetValue($null) # is 0xDEADBEAF | |
"SignalL" = $win32.DispatcherImpl.GetField( | |
"SignalL", | |
[System.Reflection.BindingFlags]::NonPublic ` | |
-bor [System.Reflection.BindingFlags]::Static | |
).GetValue($null) # is 0x12345678 | |
"TIMERID_DISPATCHER" = $win32.Platform.GetField( | |
"TIMERID_DISPATCHER", | |
[System.Reflection.BindingFlags]::NonPublic ` | |
-bor [System.Reflection.BindingFlags]::Static | |
).GetValue($null) # is 1 | |
} | |
}.ToString(), | |
# Actual body | |
{ | |
if ($msg -eq $signals.WM_DISPATCH_WORK_ITEM ` | |
-and $wParam.ToInt64() -eq $signals.SignalW ` | |
-and $lParam.ToInt64() -eq $signals.SignalL | |
){ | |
$_dispatcherImpl?.DispatchWorkItem() | |
} | |
<# | |
# Lifecycle control for Avalonia.Platform. Not super applicable to dispatcher | |
# - WM_QUERYENDSESSION means there is a request to close the application or shutdown the computer | |
# - Unlike WM_ENDSESSION, WM_QUERYENDSESSION is a request and can be denied | |
if ($msg -eq $signals.WM_QUERYENDSESSION) | |
{ | |
if ($ShutdownRequested -ne $null) | |
{ | |
$e = [ShutdownRequestedEventArgs]::new() | |
$ShutdownRequested.Invoke($null, $e) | |
if($e.Cancel) | |
{ | |
# If we deny the shutdown, return C-equivalent of false | |
return [System.IntPtr]::Zero | |
} | |
} | |
} | |
#> | |
<# | |
# System Settings Changed detection for Avalonia.Platform. Not super applicable to dispatcher | |
# - Is emitted whenever SystemParametersInfoA changes a system-wide setting | |
# - see: https://learn.microsoft.com/en-us/windows/win32/winmsg/wm-settingchange | |
# - see: https://learn.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-systemparametersinfoa | |
if( $msg -eq $signals.WM_SETTINGCHANGE ` | |
-and $PlatformSettings -is [Win32PlatformSettings] $win32PlatformSettings | |
){ | |
$changedSetting = [System.Runtime.InteropServices.Marshal]::PtrToStringAuto($lParam) | |
if ($changedSetting -eq "ImmersiveColorSet" # dark/light mode | |
-or $changedSetting -eq "WindowsThemeElement") # high contrast mode | |
{ | |
$win32PlatformSettings.OnColorValuesChanged() | |
} | |
} | |
#> | |
if( $msg -eq $signals.WM_TIMER ) | |
{ | |
if( $wParam -eq $signals.TIMERID_DISPATCHER ) | |
{ | |
if( $_dispatcherImpl -ne $null ) | |
{ | |
$_dispatcherImpl.FireTimer() | |
} | |
} | |
} | |
# Handle tray icon messages | |
# TrayIconImpl.ProcWnd(hWnd, msg, wParam, lParam); | |
# if we don't return anything, the message is passed to the default message processor | |
return $win32.unmanaged::DefWindowProc($hWnd, $msg, $wParam, $lParam); | |
}.ToString() | |
) -join "`n" | |
$body = { | |
param( | |
[System.IntPtr] $hWnd, | |
[uint] $msg, | |
[System.IntPtr] $wParam, | |
[System.IntPtr] $lParam | |
) | |
Write-Host "MessageProcessor:" $hWnd $msg $wParam $lParam | |
} | |
[scriptblock]::create($body) | |
} | |
$constructor = [Avalonia.Threading.Dispatcher]. | |
GetConstructor( | |
[System.Reflection.BindingFlags]::Instance -bor ` | |
[System.Reflection.BindingFlags]::NonPublic, | |
$null, | |
[type[]]@( | |
[Avalonia.Threading.IDispatcherImpl] | |
), | |
$null | |
) | |
$win32 = @{ | |
"assembly" = [Avalonia.Win32PlatformOptions].Assembly | |
} | |
$win32.unmanaged = $win32.assembly.GetTypes() | | |
Where-Object { $_.Name -eq "UnmanagedMethods" } | |
$win32.WNDCLASSEX = $win32.assembly.GetTypes() | | |
Where-Object { $_.Name -eq "WNDCLASSEX" } | |
$win32.DispatcherImpl = $win32.assembly.GetTypes() | | |
Where-Object { $_.Name -eq "Win32DispatcherImpl" } | |
# Avalonia.Win32.Interop.UnmanagedMethods+WndProc | |
$win32.WndProc = $win32.assembly.GetTypes() | | |
Where-Object { $_.Name -eq "WndProc" } | |
$win32.MainHandle = $win32.unmanaged::GetModuleHandle($null) | |
$win32.MesageHandle = & { | |
$wnd_class_ex = [Activator]::CreateInstance($win32.WNDCLASSEX) | |
$wnd_class_ex.cbSize = [System.Runtime.InteropServices.Marshal]::SizeOf($wnd_class_ex) | |
$wnd_class_ex.lpfnWndProc = $MessageProcessor -As $win32.WndProc | |
$wnd_class_ex.hInstance = $win32.MainHandle | |
$wnd_class_ex.lpszClassName = "AvaloniaMessageWindow " + [System.Guid]::NewGuid().ToString() | |
$atom = $win32.unmanaged::RegisterClassEx([ref]$wnd_class_ex) | |
if( $atom -eq 0 ) | |
{ | |
$global:_error = [System.ComponentModel.Win32Exception]::new() | |
throw $_error | |
} | |
$win32.unmanaged::CreateWindowEx( | |
0, # dwExStyle | |
$atom, # lpClassName | |
[System.IntPtr]::Zero, # lpWindowName | |
0, # dwStyle | |
0, # x | |
0, # y | |
0, # nWidth | |
0, # nHeight | |
[System.IntPtr]::Zero, # hWndParent | |
[System.IntPtr]::Zero, # hMenu | |
[System.IntPtr]::Zero, # hInstance | |
[System.IntPtr]::Zero # lpParam | |
) | |
} | |
$dispatcher_impl = $win32.DispatcherImpl.GetConstructor([System.IntPtr]).Invoke(@($win32.MesageHandle)) | |
$constructor.Invoke(@($dispatcher_impl)) | |
} | |
$win32 = @{ | |
"assembly" = [Avalonia.Win32PlatformOptions].Assembly | |
} | |
$win32.unmanaged = $win32.assembly.GetTypes() | | |
Where-Object { $_.Name -eq "UnmanagedMethods" } | |
$win32.WNDCLASSEX = $win32.assembly.GetTypes() | | |
Where-Object { $_.Name -eq "WNDCLASSEX" } | |
$win32.DispatcherImpl = $win32.assembly.GetTypes() | | |
Where-Object { $_.Name -eq "Win32DispatcherImpl" } | |
# Avalonia.Win32.Interop.UnmanagedMethods+WndProc | |
$win32.WndProc = $win32.assembly.GetTypes() | | |
Where-Object { $_.Name -eq "WndProc" } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment