在 PowerShell 中从 C# 访问固定大小的数组元素
Accessing fixed size array elements from C# in PowerShell
寻找访问单个数组元素的方法( 的延续)
$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
这会产生以下输出:
1
FixedElementField
-----------------
2
- 只有
$Field2
的第一个数组元素可见,无法使用$testStructure.Field2[x]
访问其他数组元素
寻找迭代已知类型/大小的 FixedBuffer 的方法
$testStructure.Field2.GetType() 说 <Field2>e__FixedBuffer0
$testStructure.Field2.FixedElementField.GetType() 是字节
- 看不到访问数组其他元素的方法。
PowerShell 的类型适配器实际上没有任何东西可以处理 unsafe fixed byte[]
字段,从底层类型系统的角度来看,实际上是原始指针。
底层内存分配的声明大小仅存储在元数据中,您可以按如下方式定位:
# 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
现在我们可以计算出大小和预期的数组元素类型:
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()
}
}
您会发现 $values
现在包含预期的字节值 2、3、4 和 5
寻找访问单个数组元素的方法(
$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
这会产生以下输出:
1
FixedElementField
-----------------
2
- 只有
$Field2
的第一个数组元素可见,无法使用$testStructure.Field2[x]
访问其他数组元素
寻找迭代已知类型/大小的 FixedBuffer 的方法
$testStructure.Field2.GetType() 说 <Field2>e__FixedBuffer0
$testStructure.Field2.FixedElementField.GetType() 是字节
- 看不到访问数组其他元素的方法。
PowerShell 的类型适配器实际上没有任何东西可以处理 unsafe fixed byte[]
字段,从底层类型系统的角度来看,实际上是原始指针。
底层内存分配的声明大小仅存储在元数据中,您可以按如下方式定位:
# 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
现在我们可以计算出大小和预期的数组元素类型:
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()
}
}
您会发现 $values
现在包含预期的字节值 2、3、4 和 5