2

Looking for a way to access individual array elements (continuation of this question)

$CSharpCode = @"
using System;
namespace TestStructure
{
    public struct TestStructure
    {
       public byte Field1;
       public unsafe fixed byte Field2[4];
    }
}
"@

$cp = New-Object System.CodeDom.Compiler.CompilerParameters
$cp.CompilerOptions = '/unsafe'
Add-Type -TypeDefinition $CSharpCode -CompilerParameters $cp

function ConvertTo-Struct
{
  # Only accept struct types (sealed value types that are neither primitive nor enum)
  param(
    [Parameter(Mandatory = $true)]
    [ValidateScript({ $_.IsValueType -and $_.IsSealed -and -not($_.IsPrimitive -or $_.IsEnum) })]
    [Type]$TargetType,

    [Parameter(Mandatory = $true)]
    [byte[]]$BinaryData
  )

  # Start by calculating minimum size of the underlying memory allocation for the new struct
  $memSize = [System.Runtime.InteropServices.Marshal]::SizeOf([type]$TargetType)

  # Make sure user actually passed enough data to initialize struct
  if($memSize -gt $BinaryData.Length){
    Write-Error "Not enough binary data to create an instance of [$($TargetType.FullName)]"
    return
  }

  # now we just need some unmanaged memory in which to create our struct instance
  $memPtr = [IntPtr]::Zero
  try {
    $memPtr = [System.Runtime.InteropServices.Marshal]::AllocHGlobal($memSize)

    # copy byte array to allocated unmanaged memory from previous step
    [System.Runtime.InteropServices.Marshal]::Copy($BinaryData, 0, $memPtr, $memSize)

    # then marshal the new memory region as a struct and return
    return [System.Runtime.InteropServices.Marshal]::PtrToStructure($memPtr, [type]$TargetType)
  }
  finally {
    # and finally remember to clean up the allocated memory
    if($memPtr -ne [IntPtr]::Zero){
      [System.Runtime.InteropServices.Marshal]::FreeHGlobal($memPtr)
    }
  }
}

$testStructure = ConvertTo-Struct -TargetType ([TestStructure.TestStructure]) -BinaryData (1..100 -as [byte[]])
$testStructure.Field1
$testStructure.Field2

this produces the following output:

1

FixedElementField
-----------------
                2
  • only the first array element of $Field2 is visible, can't access others using $testStructure.Field2[x]

Looking for a way to iterate over FixedBuffer of known type / size

$testStructure.Field2.GetType() says <Field2>e__FixedBuffer0 $testStructure.Field2.FixedElementField.GetType() is byte

  • can't see a way to access other elements of the array.

1 Answer 1

4

PowerShell's type adapter doesn't really have anything in place to handle unsafe fixed byte[] fields, effectively raw pointers from the underlying type system's point of view.

The declared size of the underlying memory allocation is only stored in metadata, which you can locate as follows:

# Locate appropriate field metadata
$fieldInfo = $testStructure.GetType().GetField('Field2')

# Discover fixed buffer attribute
$fixedBufferAttribute = $fieldInfo.CustomAttributes.Where({$_.AttributeType -eq [System.Runtime.CompilerServices.FixedBufferAttribute]}) |Select -First 1

Now we can figure out the size and which array element type is expected:

if($fixedBufferAttribute)
{
    # Grab array element type + size from FixedBuffer attribute
    $elemType,$size = $fixedBufferAttribute.ConstructorArguments

    # Create array of appropriate size
    $values = $elemType.MakeArrayType()::new($size)

    # Copy values from fixed buffer pointer to managed array
    try {
        $fixedBufferHandle = [System.Runtime.InteropServices.GCHandle]::Alloc($TestStructure.Field2, 'Pinned')
        [System.Runtime.InteropServices.Marshal]::Copy($fixedBufferHandle.AddrOfPinnedObject(), $values, 0, $size)

        return $values
    }
    finally {
        $fixedBufferHandle.Free()
    }
}

You'll find $values now contains the expected byte values 2, 3, 4 and 5

Sign up to request clarification or add additional context in comments.

2 Comments

Weirdly this didn't work for me - $values = $elemType.MakeArrayType()::new($size) fails with [System.Reflection.CustomAttributeTypedArgument] does not contain a method named 'MakeArrayType'. Changed it to $values = $elemType.Value.MakeArrayType()::new($size.Value) but now Copy() fails with Cannot find an overload for "Copy" and the argument count: "4". - it doesn't like the types?
Ah, got it - there's a typo in your code $TestStructure2.Field2 should be $TestStructure.Field2

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.