【问题标题】:Pinvoke - callback from C++, arrays passed between functions have unexpected sizePinvoke - 来自 C++ 的回调,函数之间传递的数组具有意外的大小
【发布时间】:2025-11-25 18:35:01
【问题描述】:

编辑:我已经根据@Hans Passant 的评论和@David Heffernan 的回答中的建议更新了代码。

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

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

代码如下。

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

我无法更改 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;
}

【问题讨论】:

  • 委托需要 [UnmanagedFunctionPointer(CallingConvention.Cdecl)]。从 c 参数中删除 ref,给它 [In, Out]
  • 谁能评论为什么我的问题有-1票?也许这是一个愚蠢的问题(例如,解决方案很简单),但我很小心地包含了一个完整的小示例代码,并试图提出一个具体的问题。我很失望。
  • @user327301 可能是问题没有强调核心问题,一眼看不懂问题。

标签: c# pinvoke marshalling


【解决方案1】:

在您的数组参数上使用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 以匹配。

【讨论】:

  • 关于“CallingConvention.Cdecl”:我编辑了 C++ 代码以包含标题中的实际内容(我无法更改)。鉴于标题中的 __declspec(dllexport) __stdcall,它应该是 CallingConvention.Cdecl 吗?
  • 好吧,给定新信息,是的,DLL 导出的函数是 stdcall。这些信息是新的,显然我们只能根据提供的信息来回答。回调是cdecl。除非有更多你没有分享的信息。
  • 当然,在没有所有相关信息的情况下,我不能指望正确猜出答案。对不起。
  • 调用约定确实是一个附带问题。您可以查看实际代码并了解使用的约定。我觉得这里没什么好说的了。
【解决方案2】:

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

我将回调函数(以及要匹配的委托定义)更改为使用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];
  }

【讨论】:

  • 不需要使用不安全的。更好的是在额外的参数中传递数组的长度,以便它们可以编组为数组。
  • 我不明白该怎么做。当 CallbackFunction 将 double[] 作为参数时,CallbackFunction 中的 double[] x 的 x.Length = 1。无论我是否将数组长度作为额外参数传递,如果超出 x[0],我如何访问x的长度只有1?
  • 您将使用 MarshalAs(UnmanagedType.LPArray),然后使用 SizeParamIndex。我认为。这将让 pinvoke marshaller 编组往返阵列。我认为这适用于反向 p/invoke。
  • 谢谢!做到了。如果你回答我可以接受并删除这个错误的答案。
  • 我恢复了我原来的答案。够了吗?请随时修复我的代码中的任何错误。我是瞎写的。