调用lmdif1签名

本文关键字:签名 lmdif1 调用 | 更新日期: 2023-09-27 18:06:58

我试图从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不是很好,以前从来没有真正做过,所以任何帮助将是伟大的。我怀疑我可能需要编组一些东西,但我已经尝试将整数编组为I4和U4,它仍然做同样的事情。

谢谢你的帮助。

编辑:下面是描述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. */

调用lmdif1签名

我认为这里的根本问题是您没有真正的方法来告诉本机代码实际上是什么。要做到这一点,您需要了解所有的宏。

我是这么做的。我下载了这个库,并通过一个C预处理器传递lmdif1.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接收到一个额外的void指针。由于函数指针类型cminpack_func_mn也接收一个具有相同非描述性名称的void指针,我准备打赌您传递给lmdif1的指针被传递回回调函数fcn_mn。此机制通常用于允许库的使用者编写一个可以访问额外状态的回调函数。

如果你不需要它,你肯定不需要c#委托,你可以传递IntPtr.Zero并忽略回调中的值。

要解决这个问题,你需要做三个改变:

  1. lmdif1的c#声明添加void指针。
  2. 将void指针添加到回调委托的c#声明中,以及实现回调的函数。
  3. IntPtr.Zero传递给lmdif1的额外void指针参数

我不知道为什么你声明你的回调参数为IntPtr。您可以在这里使用委托类型,也可以使用UnmanagedFunctionPointer属性来强制执行Cdecl

Update:为了完整起见,我深入研究了lmdif1的实现,并研究了如何调用回调。是的,void指针p如上所述被传递回回调。

添加到@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)}");