Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Ugly syntax of definition structures and enums #15

Open
gregzakh opened this issue Mar 18, 2020 · 0 comments
Open

Ugly syntax of definition structures and enums #15

gregzakh opened this issue Mar 18, 2020 · 0 comments

Comments

@gregzakh
Copy link

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 }
  }
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant