将 float[] 作为 ref float 传递给非托管代码是个好主意吗?

本文关键字:float 非托管代码 好主意 作为 ref | 更新日期: 2023-09-27 18:31:39

我想将 float[] 传递给 C 方法。C 签名如下所示:

EXTERN int process_raw(float *inBuffer, float *outBuffer);

在 C# 中,签名为:

public static extern int process_raw(ref float inBuffer, ref float outBuffer);

将带有 ref 的数组传递给第一个成员会有问题吗:

process_raw(ref someArray[0], ref anotherArray[0])

谢谢!

编辑:当然,重要的是要知道C代码如何处理浮点数:它将它们视为数组,并从inBuffer读取值,并将值写入outBuffer。如下所述,问题是在 PInvoke 调用期间是否会固定整个内存?

编辑2:另一条评论。我故意选择了 ref 浮动,因为我也想做这样的事情:

fixed(byte* outBuff = buffer)
{
    Process(ticks, ref aFloat, ref ((float*)outBuff)[0]);
}

在这种情况下,应该没有问题,因为指针无论如何都是固定的,但是上述正常数组的问题仍然存在。

将 float[] 作为 ref float 传递给非托管代码是个好主意吗?

p/Invoke 中不涉及自动固定。P/调用严格通过编组执行!(没有不安全的代码)封送意味着分配(非托管)内存和复制。在掩护下,复制期间可能有一个引脚,但在本机函数调用期间没有。

如果需要将包含 64 个浮点数的数组传入和传出本机函数,则有两种选择:

  1. 马歇尔通过。
  2. 使用不安全的代码直接固定和传递托管内存。

下面是封送方法:

[DllImport(...)]
private extern static int process_raw([In] float[] inBuffer, [Out] float[] outBuffer);

请注意,我添加了 [In] 和 [Out] 属性,因为它们告诉 Marshaller (In) 不要在出路时复制,(出)不要在进去时复制。在编写 p/invoke 声明时始终考虑这些属性是个好主意。

这是不安全的方法:

[DllImport(...)]
private extern static unsafe int process_raw(float * inBuffer, float * outbuffer);
public static unsafe int Process(float[] inBuffer, float[] outBuffer)
{
    // validate for null and Length < 64
    fixed (float * pin = inBuffer)
    fixed (float * pout = outBuffer)
        return process_raw(pin, pout);
}

扩展评论

我的理解是,Marshaller 能够"在某些情况下"选择固定托管内存,而不是分配非托管内存和复制。问题是:什么情况?

我不知道答案,但我有一个怀疑:当本机DLL是某些系统DLL时。这只是猜测。

这对你和我意味着什么很简单:始终从编组方法开始。如果遇到性能问题,并且探查器告诉您本机调用占用了大量时间,则可以尝试不安全的方法并再次对其进行分析。如果没有明显的改进,那么你唯一的希望就是优化本机调用。

你在这里要做的事情有点模棱两可。 本机代码可以将float*视为指向floatfloat值数组的指针。

如果本机代码认为它是指向单个float的指针,那么您的代码就可以了。 让float成为托管中的ref和本机中的指针将正确封送。

如果本机代码认为它是一个float值数组,那么您很可能遇到了问题。 这将在极端情况下工作,它将其视为长度为 1 的数组。 对于任何其他长度,尽管您需要在托管签名中使用实际数组

public static extern int process_raw(float[] inBuffer, float[] outBuffer);