Pinvoke - 来自C++的回调,函数之间传递的数组具有意外的大小

本文关键字:数组 意外 之间 来自 C++ 回调 函数 Pinvoke | 更新日期: 2023-09-27 18:35:14

编辑:我已经更新了代码,给出了@Hans Passant的评论中的建议,并@David Heffernan的答案。

参数c不再为空,但 xc 在传递回 CallbackFunction 时仍然具有 1 的长度。

我正在尝试编写将函数指针(使用委托)传递给C++函数的 C# 代码,该函数调用函数指针。

代码如下。

我遇到的问题是,当C++函数f调用 fnPtr(x,c) 时,在 C# 函数CallbackFunction中,x有一个元素(正确值为 1.0),c为 null。我不知道问题是什么。

我无法更改 MyCallback 的签名。

C# 代码:

using System.Runtime.InteropServices;
namespace PInvokeTest
{
    class Program
    {
        [UnmanagedFunctionPointer(CallingConvention.Cdecl)]
        private delegate double MyCallback(
            [In] double[] x,
            [Out] double[] c);
        private static double CallbackFunction(
            [In] double[] x,
            [Out] double[] c)
        {
            c[0] = x[0] + x[1] + x[2];
            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 = new MyCallback(CallbackFunction);
            f(_myCallback);
        }
    }
}

NativeLib.h:

#ifndef _NATIVELIB_H_
#define _NATIVELIB_H_
#ifndef MYAPI
  #define MYAPI 
#endif
#ifdef __cplusplus
extern "C"
{
#endif
#ifdef _WIN32
  #ifdef MAKE_MY_DLL
    #define MYAPI __declspec(dllexport) __stdcall
  #else
    #define MYAPI __stdcall
  #endif
#else
  #if __GNUC__ >= 4
    #define MYAPI __attribute__ ((visibility ("default")))
  #else
    #define MYAPI
  #endif
#endif
  typedef int MyCallback(const double * x,
    double * c);
  MYAPI int f(MyCallback * fnPtr);
#ifdef __cplusplus
}
#endif
#endif // _NATIVELIB_H_

NativeLib.cpp:

#include "NativeLib.h"
#include <stdio.h>
#include <malloc.h>
MYAPI int f(MyCallback * fnPtr)
{
  double x[] = { 1.0, 2.0, 3.0 };
  double c[] = { 0.0, 0.0, 0.0 };
  printf("%e'n", fnPtr(x, c));

  return 0;
}

Pinvoke - 来自C++的回调,函数之间传递的数组具有意外的大小

在数组参数上使用ref是错误的。这是一个虚假的额外间接级别。您还需要将数组长度作为参数传递,并让编组器知道这些长度。

委托人应为:

[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
private delegate double MyCallback(
    [MarshalAs(UnmanagedType.LPArray, SizeParamIndex=1)]
    [In]  double[] x,
    [In]  int lenx,
    [MarshalAs(UnmanagedType.LPArray, SizeParamIndex=3)]
    [Out] double[] c,
    [In]  int lenc
);

更改CallbackFunction以匹配。

我所做的"duh"假设是,当 C 只是传递指针时,它会以某种方式传递一些关于xc数组大小的信息。我找到的解决方案是模仿相关SO问题中的代码。(我不知道这是否是解决我问题的好方法,但它有效)。

我更改了回调函数(以及要匹配的委托定义)以使用IntPtr。一般来说,我显然需要传递有关 c 和 x 大小的信息。

unsafe private static double CallbackFunction(
  [In] IntPtr xp,
  [Out] IntPtr cp)
  {
    Double* c = (Double*) cp.ToPointer();
    Double* x = (Double*) xp.ToPointer();
    c[0] = x[0] + x[1] + x[2];
    c[1] = x[0] * x[1] * x[2];
    return c[0] + c[1];
  }