PInvoke lmdif1 签名

6

我想在c#中调用cminpack_dll.dll中的lmdif1方法,但遇到了一些奇怪的错误。

传递给lmdif1的第二个和第三个参数是整数,在我的测试中,按顺序的值分别为12和9。现在当它们在C代码中时,原来是12的值现在变成了9,而原来是9的值在308000和912000之间变化。不知道为什么。

首先,我想知道我使用的签名是否有效。

C#签名:

[DllImport("cminpack_dll.dll", CallingConvention = CallingConvention.Cdecl)]
public static extern int lmdif1(IntPtr fcn, int m, int n, double[] x, double[] fVec, double tol, int[] iwa, double[] wa, int lwa);

C语言的签名:
int __cminpack_func__(lmdif1)(__cminpack_decl_fcn_mn__ void *p, int m, int n, real *x, 
real *fvec, real tol, int *iwa, 
real *wa, int lwa)

我称之为:

//All of the variables passed in match the types in C# signature above.
var info =lmdif1(
            functionPointer,
            pointsetLength,
            initialGuessLength,
            initialGuess,
            fVec,
            tol,
            iwa,
            wa,
            lwa);

这是我第一次处理PInvoke,我的C语言水平不太好,以前从未真正接触过它,所以任何帮助都将不胜感激。我怀疑我可能需要进行Marshal操作,但我已经尝试将int作为I4和U4进行Marshal操作,但仍然出现相同的问题。

非常感谢您的帮助。

编辑: 以下是描述C函数的注释,如果有帮助:

/*     ********** */

/*     subroutine lmdif1 */

/*     the purpose of lmdif1 is to minimize the sum of the squares of */
/*     m nonlinear functions in n variables by a modification of the */
/*     levenberg-marquardt algorithm. this is done by using the more */
/*     general least-squares solver lmdif. the user must provide a */
/*     subroutine which calculates the functions. the jacobian is */
/*     then calculated by a forward-difference approximation. */

/*     the subroutine statement is */

/*       subroutine lmdif1(fcn,m,n,x,fvec,tol,info,iwa,wa,lwa) */

/*     where */

/*       fcn is the name of the user-supplied subroutine which */
/*         calculates the functions. fcn must be declared */
/*         in an external statement in the user calling */
/*         program, and should be written as follows. */

/*         subroutine fcn(m,n,x,fvec,iflag) */
/*         integer m,n,iflag */
/*         double precision x(n),fvec(m) */
/*         ---------- */
/*         calculate the functions at x and */
/*         return this vector in fvec. */
/*         ---------- */
/*         return */
/*         end */

/*         the value of iflag should not be changed by fcn unless */
/*         the user wants to terminate execution of lmdif1. */
/*         in this case set iflag to a negative integer. */

/*       m is a positive integer input variable set to the number */
/*         of functions. */

/*       n is a positive integer input variable set to the number */
/*         of variables. n must not exceed m. */

/*       x is an array of length n. on input x must contain */
/*         an initial estimate of the solution vector. on output x */
/*         contains the final estimate of the solution vector. */

/*       fvec is an output array of length m which contains */
/*         the functions evaluated at the output x. */

/*       tol is a nonnegative input variable. termination occurs */
/*         when the algorithm estimates either that the relative */
/*         error in the sum of squares is at most tol or that */
/*         the relative error between x and the solution is at */
/*         most tol. */

/*       info is an integer output variable. if the user has */
/*         terminated execution, info is set to the (negative) */
/*         value of iflag. see description of fcn. otherwise, */
/*         info is set as follows. */

/*         info = 0  improper input parameters. */

/*         info = 1  algorithm estimates that the relative error */
/*                   in the sum of squares is at most tol. */

/*         info = 2  algorithm estimates that the relative error */
/*                   between x and the solution is at most tol. */

/*         info = 3  conditions for info = 1 and info = 2 both hold. */

/*         info = 4  fvec is orthogonal to the columns of the */
/*                   jacobian to machine precision. */

/*         info = 5  number of calls to fcn has reached or */
/*                   exceeded 200*(n+1). */

/*         info = 6  tol is too small. no further reduction in */
/*                   the sum of squares is possible. */

/*         info = 7  tol is too small. no further improvement in */
/*                   the approximate solution x is possible. */

/*       iwa is an integer work array of length n. */

/*       wa is a work array of length lwa. */

/*       lwa is a positive integer input variable not less than */
/*         m*n+5*n+m. */

/*     subprograms called */

/*       user-supplied ...... fcn */

/*       minpack-supplied ... lmdif */

/*     argonne national laboratory. minpack project. march 1980. */
/*     burton s. garbow, kenneth e. hillstrom, jorge j. more */

/*     ********** */

/*     check the input parameters for errors. */

2
从Windows上的头文件来看,这个函数似乎有一个额外的第一个参数cminpack_func_nn fcn_nn,它是另一个IntPtr - GSerg
1
这可以解释ints不匹配的原因,因为不断变化的那个将是函数指针的内存地址。正如我上面所说,我的C语言水平不是很好,我不知道需要传递什么intptr。我会再做一些调查并让您知道。如果这是解决方案,您能否将此评论添加为答案,我会接受它。 - David Watts
2
似乎这是一个指向int的指针,而不是函数指针(我也不是C语言专业人士)。我不知道它应该指向什么,可能文档中有答案。 - GSerg
1
我已经更新了问题,并在评论中描述了我正在尝试访问的方法。如果你看到了我没发现的东西,请随时告诉我。 - David Watts
1
重新阅读您的评论@GSerg,这不使用fcn_nn,它说的是fcn_mn,并且没有额外的第一个参数。 - David Watts
显示剩余3条评论
2个回答

6

我认为这里的根本问题在于你并没有真正的方法来确定本地代码。你需要理解所有宏才能做到这一点。

所以,这就是我所做的事情。我下载了库,并将lmdif1.c文件通过C预处理器传递,从而展开了宏。我使用了我的mingw编译器中的一个。它生成了以下输出:

int __attribute__((__dllexport__)) lmdif1(cminpack_func_mn fcn_mn,
    void *p, int m, int n, double *x, double *fvec, double tol,
    int *iwa, double *wa, int lwa);

然后看一下cminpack_func_mn的定义,我们有:
typedef int (*cminpack_func_mn)(void *p, int m, int n, const double *x, 
    double *fvec, int iflag);

因此,lmdif1 接收到一个额外的空指针。由于函数指针类型 cminpack_func_mn 也接收带有相同不明确名称的空指针,我敢打赌你传递给 lmdif1 的指针会被传回到回调函数 fcn_mn。这种机制通常用于允许库的消费者编写具有访问额外状态的回调函数。
如果您不需要它(使用 C# 委托肯定不需要),可以传递 IntPtr.Zero 并忽略回调中的值。
要解决此问题,您需要进行三个更改:
  1. 将空指针添加到 lmdif1 的 C#声明中。
  2. 将空指针添加到回调委托和实现回调的函数的 C#声明中。
  3. IntPtr.Zero 传递给 lmdif1 的额外空指针参数。
我不确定为什么您将回调参数声明为 IntPtr。您可以在此处使用委托类型,并使用 UnmanagedFunctionPointer 属性强制执行 Cdecl更新: 为了完整起见,我深入研究了 lmdif1 的实现,并查看了回调的调用方式。是的,如上所述,空指针 p 会被传递回回调中。

1
你是指另一个不是p的额外void指针吗?我已经通过使用Marshal.GetFunctionPointerForDelegate获取了指向P的指针,但你说在p之前还需要另一个void指针?@DavidHeffernan 如果这些问题看起来很愚蠢,请原谅,就像我上面所说的,我不是C语言专家。 - David Watts
阅读我的答案中的代码。首先是函数指针,然后是一个额外的无用的void指针。按照我的答案中所写的计算函数参数的数量。那里的代码是宏展开后的代码。你可能需要学习一下宏来了解发生了什么。毫无疑问,我的答案是准确的。 - David Heffernan
关于函数指针回调,你现在采取的方式太繁琐了。应该将参数的类型设置为委托类型,而不是IntPtr。 - David Heffernan

0

在 @David Heffernan 的出色回答中,值得注意的是,在回调函数中,使用 double[] 作为 xfvec 数组可能不够,因为在过程中数组长度的信息会丢失。因此,C 函数和委托的最终签名可能如下所示:

[DllImport("cminpack_dll.dll", CallingConvention = CallingConvention.Cdecl)]
public static extern int lmdif1(CminpackFuncMn fcn, IntPtr p, int m, int n, double[] x,
    double[] fvec, double tol, int[] iwa, double[] wa, int lwa);

[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
public delegate int CminpackFuncMn(IntPtr p, int m, int n, IntPtr x, IntPtr fvec, int iflag);

使用示例如下,我们将二次方程拟合到一些测试数据中:

// Define some test data by 5i + 3i^2. The plan is to let cminpack figure out
// the values 5 and 3.
var data = Enumerable.Range(0, 20)
    .Select(i => 5 * i + 3 * Math.Pow(i, 2))
    .ToList();

CminpackFuncMn residuals = (p, m, n, x, fvec, iflag) =>
{
    unsafe
    {
        // Update fvec with the values of the residuals x[0]*i + x[1]*i^2 - data[i].
        var fvecPtr = (double*)fvec;
        var xPtr = (double*)x;
        for (var i = 0; i < m; i++)
            *(fvecPtr + i) = *xPtr * i + *(xPtr + 1) * Math.Pow(i, 2) - data[i];
    }
    return 0;
};

// Define an initial (bad, but not terrible) guess for the value of the parameters x.
double[] parameters = { 2d, 2d };
var numParameters = parameters.Length;
var numResiduals = data.Count;
var lwa = numResiduals * numParameters + 5 * numParameters + numResiduals;

// Call cminpack
var info = lmdif1(
    fcn: residuals,
    p: IntPtr.Zero,
    m: numResiduals,
    n: numParameters,
    x: parameters,
    fvec: new double[numResiduals],
    tol: 0.00001,
    iwa: new int[numParameters],
    wa: new double[lwa],
    lwa: lwa);

// info is now 2, and parameters are { 5, 3 }.
Console.WriteLine($"Return value: {info}, x: {string.Join(", ", parameters)}");

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接