PowerShell:如何[引用]数组中的元素?

PowerShell: How to [ref] an element in array?

提问人:Hsu Pu 提问时间:11/1/2023 更新时间:11/1/2023 访问量:45

问:

我正在使用 PowerShell Core 编写 P/Invoke 代码,但它失败了。

using System;
using System.Runtime.InteropServices;

public class Bindings
{
    [DllImport("MyEncoder.dll")]
    public static extern bool EncodeStream(
        byte[] pbIn,
        int cbIn,
        ref byte pbOut,
        out int cbOut);
}

我的 C# 代码如下:

var pbOut = new byte[pbIn.Length];
int cbOut = 0;
Bindings.EncodeStream(pbIn, pbIn.Length, ref pbOut[0], out cbOut);

它有效。

我的 PowerShell 代码如下:

$Bindings = Add-Type -TypeDefinition $cs_code -PassThru

[byte[]]$pbIn = [IO.File]::ReadAllBytes("src.txt")
$cbIn = $pbIn.Length

$pbOut = [byte[]]::new($cbIn)
$cbOut = [int]0

# PowerShell 7.3.9
# Fatal error. System.AccessViolationException: Attempted to read or write protected memory. This is often an indication that other memory is corrupt.
$Bindings::EncodeStream($pbIn, $cbIn, [ref]$pbOut[0], [ref]$cbOut)

我尝试调试并发现返回了不同的地址。所以我想知道这是否是创造新值的点,而 PoewrShell 引用了临时值。GCHandle.AddrOfPinnedObject$pbOut$pbOut[0]$pbOut[0]

欢迎任何帮助和测试!

C# PowerShell pinvoke powershell-core

评论

0赞 Simon Mourier 11/1/2023
既然你已经有了 C# 代码,为什么不在 powershell 中使用内联 C#(就像这里 learn.microsoft.com/en-us/powershell/module/ 一样),我发现这比尝试理解 Powershell 时髦的 .NET 互操作语法要容易得多Add-Type -Language CSharp @"my c# code"
0赞 Charlieface 11/1/2023
ref byte pbOut无论如何都是错误的,您应该传递一个带有大小的数组缓冲区。
0赞 Hsu Pu 11/2/2023
@Charlieface我从dotPeek的反汇编代码中发现了这种用法,它确实适用于.是的,我们可以改用,我测试过。char*byte[]

答:

1赞 mklement0 11/1/2023 #1

PowerShell 的 [ref] 类型和 C# 的关键字功能非常不同,从根本上无法在 PowerShell 中获取对单个数组元素的引用。ref

若要解决此限制,请围绕 P/Invoke 方法创建一个 C# 包装方法,该方法:

  • 接受来自 PowerShell 的整个数组,并在 P/Invoke 调用内部使用。byte[]ref pbOut[0]
  • 以便更轻松地从 PowerShell 使用:
    • 返回cbOut
    • 如果发生错误,则引发异常

大致如下的内容(未经测试):

$Bindings = Add-Type -PassThru @'
using System;
using System.Runtime.InteropServices;

public class Bindings
{
    public static int EncodeStream(byte[] pbIn, byte[] pbOut)
    {
      int cbOut;
      if (!EncodeStream_Impl(pbIn, pbIn.Length, ref pbOut[0], out cbOut)) 
      {
        throw new Exception("Encoding failed.");
      }
      return cbOut;
    }

    [DllImport("MyEncoder.dll", EntryPoint="EncodeStream")]
    private static extern bool EncodeStream_Impl(
        byte[] pbIn,
        int cbIn,
        ref byte pbOut,
        out int cbOut);
}
'@

[byte[]]$pbIn = [IO.File]::ReadAllBytes("src.txt")

$pbOut = [byte[]]::new($pbIn.Length)

$cbOut = $Bindings::EncodeStream($pbIn, $pbOut)