lib/delegate.ps1
#requires -version 6 using namespace System.Reflection using namespace System.Reflection.Emit using namespace System.Linq.Expressions using namespace System.Runtime.InteropServices function Get-ProcAddress { [OutputType([Hashtable])] [CmdletBinding()] param( [Parameter(Mandatory, Position=0)] [ValidateNotNullOrEmpty()] [String]$Module, [Parameter(Mandatory, Position=1)] [ValidateNotNullOrEmpty()] [String[]]$Function ) process { $kernel32 = @{} [Assembly]::LoadFile("$( [RuntimeEnvironment]::GetRuntimeDirectory() )Microsoft.Win32.SystemEvents.dll").GetType('Interop').GetNestedType( 'Kernel32', [BindingFlags]'NonPublic' ).GetMethods([BindingFlags]'NonPublic, Static, Public').Where{ $_.Name -cmatch '\AGet(Proc|Mod)' }.ForEach{ $kernel32[$_.Name] = $_ } if (( $mod = $kernel32.GetModuleHandle.Invoke($null, @($Module)) ) -eq [IntPtr]::Zero) { throw [DllNotFoundException]::new() } $funcs = @{} $Function.ForEach{ if (( $$ = $kernel32.GetProcAddress.Invoke($null, @($mod, $_)) ) -ne [IntPtr]::Zero) { $funcs.$_ = $$ } } $funcs } } function Set-Delegate { [OutputType([Type])] [CmdletBinding(DefaultParameterSetName='Prototype')] param( [Parameter(Mandatory, ParameterSetName='Prototype', Position=0)] [ValidateNotNull()] [Alias('p')] [Type]$Prototype, [Parameter(Mandatory, ParameterSetName='PrototypeAsTypeArray', Position=0)] [ValidateNotNullOrEmpty()] [Alias('pa')] [Type[]]$PrototypeAsTypeArray, [Parameter(Mandatory, Position=1)] [ValidateScript({$_ -ne [IntPtr]::Zero})] [IntPtr]$Address, [Parameter(Position=2)] [ValidateNotNullOrEmpty()] [CallingConvention]$CallingConvention = 'StdCall' ) process { switch ($PSCmdlet.ParameterSetName) { 'Prototype' { $method = $Prototype.GetMethod('Invoke') $returntype, $paramtypes = $method.ReturnType, $method.GetParameters().ParameterType $paramtypes = ($paramtypes, $null)[!$paramtypes] $il, $sz = ($holder = [DynamicMethod]::new( 'Invoke', $returntype, $paramtypes, $Prototype )).GetILGenerator(), [IntPtr]::Size if ($paramtypes) { (0..($paramtypes.Length - 1)).ForEach{ $il.Emit([OpCodes]::ldarg, $_) } } $il.Emit([OpCodes]::"ldc_i$sz", $Address."ToInt$((32, 64)[$sz / 4 - 1])"()) $il.EmitCalli([OpCodes]::calli, $CallingConvention, $returntype, $paramtypes) $il.Emit([OpCodes]::ret) $holder.CreateDelegate($Prototype) } 'PrototypeAsTypeArray' { [Marshal]::GetDelegateForFunctionPointer( $Address, [Delegate]::CreateDelegate( [Func[[Type[]], Type]], [Expression].Assembly.GetType( 'System.Linq.Expressions.Compiler.DelegateHelpers' ).GetMethod( 'MakeNewCustomDelegate', [BindingFlags]'NonPublic, Static' ) ).Invoke($PrototypeAsTypeArray) ) } } # switch } } function New-Delegate { [OutputType([Hashtable])] [CmdletBinding()] param( [Parameter(Mandatory, Position=0)] [ValidateNotNullOrEmpty()] [String]$Module, [Parameter(Mandatory, Position=1)] [ValidateNotNull()] [Hashtable]$Signature ) process { $funcs, $addr = @{}, (Get-ProcAddress -Module $Module -Function $Signature.Keys) $addr.Keys.ForEach{ $ptr, $sig = $addr.$_, $Signature.$_ if (!$sig) { throw [InvalidOperationException]::new() } $funcs.$_ = switch -Regex ($sig.Name) { '\A(Action|Func)' { Set-Delegate -Address $ptr -Prototype $sig } default { Set-Delegate -Address $ptr -PrototypeAsTypeArray $sig } } } $funcs } } |