PSReflect icon indicating copy to clipboard operation
PSReflect copied to clipboard

Ugly syntax of definition structures and enums

Open gregzakh opened this issue 4 years ago • 0 comments

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 }
  }
}

gregzakh avatar Mar 18 '20 20:03 gregzakh