我试图从c#中的pinminoke 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. */
我认为这里的根本问题是你没有真正的方法来判断本机代码究竟是什么。你需要了解所有的宏才能做到这一点。
所以,这就是我所做的。我下载了库并通过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
收到。由于函数指针类型cminpack_func_mn
也接收到具有相同非描述名称的void指针,我准备打赌你传递给lmdif1
的指针被传递回你的回调函数fcn_mn
。此机制通常用于允许库的使用者编写可以访问额外状态的回调函数。
如果您不需要它,并且您肯定不会使用C#委托,则可以传递IntPtr.Zero
并忽略回调中的值。
要解决此问题,您需要进行三项更改:
lmdif1
的C#声明中。IntPtr.Zero
传递给lmdif1
的额外void指针参数。我不确定你为什么把你的回调参数声明为IntPtr
。您可以在此处使用委托类型,也可以使用UnmanagedFunctionPointer
属性来强制执行Cdecl
。
更新:为了完整性,我深入研究了lmdif1
的实现,并研究了如何调用回调。是的,如上所述,void指针p
被传递回回调。
添加到@David Heffernan的优秀答案,值得注意的是,对于回调,使用double[]
作为x
和fvec
数组将不够,因为有关数组长度的信息在此过程中丢失。因此,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)}");