PowerShell中如何用[ref]引用数组元素?P/Invoke调用报错求助
PowerShell Core P/Invoke调用失败的解决方案与测试方案
问题根源
在PowerShell中,$pbOut[0]会返回byte值类型的副本,而非原数组元素的引用。使用[ref]$pbOut[0]时,实际引用的是这个临时副本的内存地址,而非目标数组首元素的地址,导致DLL写入错误内存区域,触发AccessViolationException。这和C#中直接ref pbOut[0]可以获取数组元素引用的行为完全不同。
解决方案
方案1:修改C#绑定,新增数组参数重载
在绑定类中封装一个接收byte[]的方法,内部转换为数组元素引用调用原DLL函数,避免PowerShell处理ref的问题:
using System; using System.Runtime.InteropServices; public class Bindings { // 原DLL签名(私有,仅内部调用) [DllImport("MyEncoder.dll")] private static extern bool EncodeStream( byte[] pbIn, int cbIn, ref byte pbOut, out int cbOut); // 对外暴露的重载方法,直接接收输出数组 public static bool EncodeStream(byte[] pbIn, int cbIn, byte[] pbOut, out int cbOut) { if (pbOut == null) throw new ArgumentNullException(nameof(pbOut)); return EncodeStream(pbIn, cbIn, ref pbOut[0], out cbOut); } }
PowerShell调用代码(无需处理ref数组元素):
$Bindings = Add-Type -TypeDefinition $cs_code -PassThru [byte[]]$pbIn = [IO.File]::ReadAllBytes("src.txt") $cbIn = $pbIn.Length $pbOut = [byte[]]::new($cbIn) $cbOut = 0 # 直接调用重载方法,传递输出数组 $success = $Bindings::EncodeStream($pbIn, $cbIn, $pbOut, [ref]$cbOut)
方案2:改用IntPtr传递内存地址
修改绑定签名为IntPtr类型,直接传递数组首元素的内存地址,同时用GCHandle固定数组防止GC移动内存:
using System; using System.Runtime.InteropServices; public class Bindings { [DllImport("MyEncoder.dll")] public static extern bool EncodeStream( byte[] pbIn, int cbIn, IntPtr pbOut, out int cbOut); }
PowerShell调用代码:
$Bindings = Add-Type -TypeDefinition $cs_code -PassThru [byte[]]$pbIn = [IO.File]::ReadAllBytes("src.txt") $cbIn = $pbIn.Length $pbOut = [byte[]]::new($cbIn) $cbOut = 0 # 固定数组,防止GC移动内存导致地址失效 $handle = [System.Runtime.InteropServices.GCHandle]::Alloc($pbOut, [System.Runtime.InteropServices.GCHandleType]::Pinned) try { # 获取数组首元素的内存地址 $pbOutPtr = $handle.AddrOfPinnedObject() $success = $Bindings::EncodeStream($pbIn, $cbIn, $pbOutPtr, [ref]$cbOut) } finally { # 必须释放固定句柄 $handle.Free() }
测试方案
1. 内存地址一致性验证
验证PowerShell中临时对象与原数组的地址差异,确认问题根源:
$pbOut = [byte[]]::new(10) # 获取原数组首元素地址 $handle = [System.Runtime.InteropServices.GCHandle]::Alloc($pbOut, [System.Runtime.InteropServices.GCHandleType]::Pinned) $arrayAddr = $handle.AddrOfPinnedObject() $handle.Free() # 获取$pbOut[0]临时副本的地址 $temp = $pbOut[0] $tempRef = [ref]$temp $tempAddr = [System.Runtime.InteropServices.Marshal]::UnsafeAddrOfPinnedArrayElement([byte[]]@($temp), 0) # 对比两个地址(必然不同) Write-Host "原数组首地址: $arrayAddr" Write-Host "临时对象地址: $tempAddr"
2. 功能正确性测试
使用小数据输入验证调用结果:
# 构造测试输入 [byte[]]$pbIn = [byte[]]@(0x01, 0x02, 0x03) $cbIn = $pbIn.Length $pbOut = [byte[]]::new($cbIn * 2) # 预留足够空间 $cbOut = 0 # 调用方法(以方案1为例) $success = $Bindings::EncodeStream($pbIn, $cbIn, $pbOut, [ref]$cbOut) # 检查结果 Write-Host "调用成功: $success" Write-Host "输出长度: $cbOut" Write-Host "输出内容: $($pbOut[0..($cbOut-1)] -join ' ')"
3. 边界场景测试
- 空输入测试:传入长度为0的
pbIn,验证函数返回值和cbOut是否符合预期 - 最大长度测试:传入接近数组上限的输入,验证是否出现内存溢出或异常
- 输出数组不足测试:故意传入小于预期输出长度的
pbOut,验证函数是否能正确处理(如返回错误或截断)
内容的提问来源于stack exchange,提问作者Hsu Pu
相关产品推荐
相关产品推荐

