PSReflect
PSReflect copied to clipboard
Ugly syntax of definition structures and enums
How about something like this?
$LARGE_INTEGER = New-Struct LARGE_INTEGER {
Int64 'QuadPart 0'
Int32 'LowPart 0'
UInt32 'HighPart 4'
} -Explicit
$FILE_DIRECTORY_INFORMATION = New-Struct FILE_DIRECTORY_INFORMATION {
UInt32 'NextEntryOffset'
UInt32 'FileIndex'
LARGE_INTEGER 'CreationTime'
LARGE_INTEGER 'LastAccessTime'
LARGE_INTEGER 'LastWriteTime'
LARGE_INTEGER 'ChangeTime'
LARGE_INTEGER 'EndOfFile'
LARGE_INTEGER 'AllocationSize'
UInt32 'FileAttributes'
UInt32 'FileNameLength'
Byte[] 'FileName ByValArray 2'
} -CharSet Unicode
It can be implemented with next (just an example because it can be done more pretty):
using namespace System.Reflection
using namespace System.Reflection.Emit
using namespace System.Management.Automation
using namespace System.Runtime.InteropServices
function Get-PSModuleBuilder {
process {
if (!($pmb = $ExecutionContext.SessionState.PSVariable.Get(
'PSModuleBuilder' # shouldn't be visible
).Value)) {
Set-Variable -Name PSModuleBuilder -Value ($pmb =
([AssemblyBuilder]::DefineDynamicAssembly(
([AssemblyName]::new('PSModuleBuilder')), 'Run'
)).DefineDynamicModule('PSModuleBuilder', $false)
) -Option Constant -Scope Global -Visibility Private
$pmb
} else { $pmb }
}
}
function New-Struct {
[CmdletBinding()]
[OutputType([Type])]
param(
[Parameter(Mandatory, Position=0)]
[ValidateNotNullOrEmpty()]
[String]$Name,
[Parameter(Mandatory, Position=1)]
[ValidateScript({![String]::IsNullOrEmpty($_.ToString())})]
[ScriptBlock]$Definition,
[Parameter()]
[PackingSize]$PackingSize = 'Unspecified',
[Parameter()]
[ValidateSet('Ansi', 'Auto', 'Unicode')]
[CharSet]$CharSet = 'Ansi',
[Parameter()]
[Switch]$Explicit
)
process {
[TypeAttributes]$attr = 'BeforeFieldInit, Class, Public, Sealed'
$type = switch ($Explicit) { $true {'Explicit'} $false {'Sequential'} }
$attr = $attr -bor [TypeAttributes]::"$($type)Layout"
$attr = $attr -bor [TypeAttributes]::"$($CharSet)Class"
if (!($struct = ($pmb = Get-PSModuleBuilder).GetType($Name))) {
$type = $pmb.DefineType($Name, $attr, [ValueType], $PackingSize)
$ctor = [MarshalAsAttribute].GetConstructor(
[BindingFlags]'Instance, Public', $null, [Type[]]@([UnmanagedType]), $null
)
$sc = @([MarshalAsAttribute].GetField('SizeConst'))
[PSParser]::Tokenize($Definition, [ref]$null).Where{
$_.Type -cmatch '\A(Command|String)\Z'
}.ForEach{
if ($_.Type -eq 'Command') {
$token = $_.Content # data type
$ft = switch (($def = $pmb.GetType($token)) -eq $null) {
$true { [Type]$token }
$false { $def } # perhaps type is defined in assembly
}
}
else {
$token = @($_.Content.Trim() -split '\s+') # field name with additional data
switch ($token.Length) {
1 { [void]$type.DefineField($token[0], $ft, 'Public') }
2 { switch ($Explicit) {
$true { [void]$type.DefineField($token[0], $ft, 'Public').SetOffset([Int32]$token[1]) }
$false {
$unm = [UnmanagedType]$token[1]
[void]$type.DefineField($token[0], $ft, 'Public, HasFieldMarshal').SetCustomAttribute(
[CustomAttributeBuilder]::new($ctor, [Object]@($unm))
)
}
}}
3 { $unm = [UnmanagedType]$token[1]
[void]$type.DefineField($token[0], $ft, 'Public, HasFieldMarshal').SetCustomAttribute(
[CustomAttributeBuilder]::new($ctor, $unm, $sc, @([Int32]$token[2]))
)
}
}
}
} # foreach
$GetSize = $type.DefineMethod('GetSize', 'Public, Static', [Int32], [Type[]]@())
$il = $GetSize.GetILGenerator()
$il.Emit([OpCodes]::ldtoken, $type)
$il.Emit([OpCodes]::call, [Type].GetMethod('GetTypeFromHandle'))
$il.Emit([OpCodes]::call, [Marshal].GetMethod('SizeOf', [Type[]]@([Type])))
$il.Emit([OpCodes]::ret)
$Implicit = $type.DefineMethod(
'op_Implicit', 'PrivateScope, Public, Static, HideBySig, SpecialName', $type, [Type[]]@([IntPtr])
)
$il = $Implicit.GetILGenerator()
$il.Emit([OpCodes]::ldarg_0)
$il.Emit([OpCodes]::ldtoken, $type)
$il.Emit([OpCodes]::call, [Type].GetMethod('GetTypeFromHandle'))
$il.Emit([OpCodes]::call, [Marshal].GetMethod('PtrToStructure', [Type[]]@([IntPtr], [Type])))
$il.Emit([OpCodes]::unbox_any, $type)
$il.Emit([OpCodes]::ret)
$type.CreateType()
}
else { $struct }
}
}