反向 Pinvoke:传递数组长度,而不为大小提供显式函数参数

本文关键字:参数 函数 Pinvoke 数组 反向 | 更新日期: 2023-09-27 18:35:22

我正在向C++库编写一个 C# API,该库采用具有此签名的函数指针:

typedef int MyCallback(
  int n,
  int m,
  const double * x, 
  const double * l,
  double * c);

数组 x 的大小为 n ,数组c的大小为 m,数组l的大小为 m + n

无法更改C++ MyCallback函数的签名。

问题:

我已经使用 C# 传递了 MarshalAs(UnmanagedType.LPArray, SizeParamIndex = <approriate_index>) 的数组。 这对xc很好。当我没有明确包含其大小的参数时,如何封送l并跟踪其大小?

我知道的唯一有效的解决方案是从 C# 传递IntPtr并使用 unsafeToPointer() .有没有其他解决方案?

我正在尝试做的一个例子如下:

C# 代码:

using System.Runtime.InteropServices;
namespace PInvokeTest
{
    public class Program
    {
        [UnmanagedFunctionPointer(CallingConvention.Cdecl)]
        private delegate double MyCallback(
            int n,
            int m,
            [In, MarshalAs(UnmanagedType.LPArray, SizeParamIndex = 0)] double[] x,
            [In, MarshalAs(UnmanagedType.LPArray)] double[] l,
            [Out, MarshalAs(UnmanagedType.LPArray, SizeParamIndex = 1)] double[] c);
        private static double CallbackFunction(
            int n,
            int m,
            [In, MarshalAs(UnmanagedType.LPArray, SizeParamIndex = 0)] double[] x,
            [In, MarshalAs(UnmanagedType.LPArray)] double[] l,
            [Out, MarshalAs(UnmanagedType.LPArray, SizeParamIndex = 1)] double[] c)
        {
            // THIS IS WILL NOT WORK, l HAS SIZE 1.
            // In this example, only l[0] and l[1] are used, but in general
            // the size of l is n + m.
            c[0] = x[0] + x[1] + x[2] + l[0]*l[1];
            c[1] = x[0] * x[1] * x[2];
            return c[0] + c[1];
        }
        private static MyCallback _myCallback;
        [DllImport("NativeLib", CallingConvention = CallingConvention.StdCall)]
        private static extern int f(MyCallback cf);
        private static void Main()
        {
            _myCallback = CallbackFunction;
            f(_myCallback);
        }
    }
}

头文件:

#ifndef _NATIVELIB_H_
#define _NATIVELIB_H_
#ifndef MYAPI
  #define MYAPI 
#endif
#ifdef __cplusplus
extern "C"
{
#endif
  typedef int MyCallback(
    int n,
    int m,
    const double * x, 
    const double * l,
    double * c);
  MYAPI int f(MyCallback * fnPtr);
#ifdef __cplusplus
}
#endif
#endif // _NATIVELIB_H_

C++来源:

#include "NativeLib.h"
#include <stdio.h>
#include <malloc.h>
MYAPI int f(MyCallback * fnPtr)
{
  int n = 3;
  int m = 2;
  double x[] = { 1.0, 2.0, 3.0 };
  double l[] = { 1.0, 1.0, 1.0, 1.0, 1.0 };
  double c[] = { 0.0, 0.0};
  printf("%e'n", fnPtr(n, m, x, l, c));
  printf("the value of c after the function call:'n");
  printf("%e %e'n", c[0], c[1]);
  return 0;
}

反向 Pinvoke:传递数组长度,而不为大小提供显式函数参数

您需要为此回调自定义编组器。 但是没有什么意义,您可以自己简单地封送数组:

private static double CallbackFunction(..., IntPtr lptr, ...) {
    var l = new double[n + m];
    Marshal.Copy(lptr, l, 0, l.Length);
    // etc...
}

更新委托签名以匹配。

请注意,所有数组都被复制了。 这是非常昂贵的,您可能更喜欢使用指针,因此不需要复制:

[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
private unsafe delegate double MyCallback(int n, int m, double* x, double* y, double* c);

更新 CallbackFunction() 以匹配和使用项目 + 属性,构建选项卡,勾选"允许不安全的代码"。 这往往会调用 Eek!响应,如果您的代码片段与真实代码匹配良好,那么没有那么多方法可以摸索代码并超出数组边界进行编写。 为这些参数选择好的名字以避免其他 eek。