You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.07 05:20:12