In this post, I will describe how to access the C interface functions of the IPOPT library from C# using unsafe code.
The C interface to IPOPT is declared in the IpStdCInterface.h header file and can be viewed here.
The arguments in the C interface functions are all primitive types or pointers to primitive types, except for a pointer to an internal structure holding the IPOPT problem C++ object and associated data for simplified access from within the C interface implementation. The use of primitive types makes the access from C# relatively straightforward.
For example, the IpoptSolve function in the C interface has the following signature:
enum ApplicationReturnStatus IpoptSolve(struct IpoptProblemInfo* ipopt_problem, double* x, double* g, double* obj_val, double* mult_g, double* mult_x_L, double* mult_x_U, void* user_data);
In the C# interface, I translate this to:
unsafe public static extern int IpoptSolve(IntPtr ipopt_problem, double* x, double* g, double* obj_val, double* mult_g, double* mult_x_L, double* mult_x_U, void* user_data);
Thus, very small differences between the C and the C# signature; the enum in C is represented with an int in C#, and the pointer to the IpoptProblemInfo struct is represented with an IntPtr. (I have not tested it, but void* would probably work just as well as IntPtr in this context.) Otherwise, argument types are the same in both languages.
The DllImport attribute and the extern keyword in the C# declaration are required to indicate that the implementation of the IpoptSolve method is available in the unmanaged csipopt.dll library.
Here is another example:
int AddIpoptIntOption(struct IpoptProblemInfo* ipopt_problem, char* keyword, int val);
public static extern int AddIpoptIntOption(IntPtr ipopt_problem, string keyword, int val);
Again, fairly straightforward. Conveniently, it is possible to represent the C char* argument with a C# string. The return value of the C function is actually limited to 1 (TRUE) and 0 (false). Since it is in practice declared as an int (due to the lack of a boolean type in C), I have maintained the int type in the C# "translation" as well.
The functions in the C interface are overall easy to "translate" into C#. The only function that became some sort of a challenge was the CreateIpoptProblem function:
struct IpoptProblemInfo* CreateIpoptProblem(int n, double* x_L, double* x_U, int m, double* g_L, double* g_U, int nele_jac, int nele_hess, int index_style, Eval_F_CB eval_f, Eval_G_CB eval_g, Eval_Grad_F_CB eval_grad_f, Eval_Jac_G_CB eval_jac_g, Eval_H_CB eval_h);
where Eval_F_CB etc. are typedef:s for the callback functions that are used to calculate the objective and constraints functions, the objective function gradient, the Jacobian and potentially the Hessian of the Lagrangian function.
The main challenge is to declare the callback function signatures in C#. In C(++), Eval_F_CB is declared as follows:
typedef int (*Eval_F_CB)(int n, double* x, int new_x, double* obj_value, void* user_data);
In C#, the callback function will be declared as a delegate. When called from the C/C++ based library under Windows, this callback function will by default assume cdecl calling convention. On the other hand, C# uses the stdcall calling convention by default, so it becomes necessary to explicitly indicate to the C# compiler that the callback function will be called using the cdecl convention:
unsafe public delegate int Eval_F_CB(int n, double* x, int new_x, double* obj_value, void* user_data);
(Note! UnmanagedFunctionPointerAttribute was introduced in .NET Framework 2.0. The above C# code will not work under earlier versions of .NET.)
And that is basically it. Provided that the other callback functions are declared in a similar fashion, the C# declaration of the CreateIpoptProblem function can now be written as follows:
unsafe public static extern IntPtr CreateIpoptProblem(int n, double* x_L, double* x_U, int m, double* g_L, double* g_U, int nele_jac, int nele_hess, int index_style, Eval_F_CB eval_f, Eval_G_CB eval_g, Eval_Grad_F_CB eval_grad_f, Eval_Jac_G_CB eval_jac_g, Eval_H_CB eval_h);
The complete C# interface to IPOPT via the C functions can be found here.
So with the approach described above, it is thus relatively easy to access IPOPT provided that so-called unsafe C# code is used. In a subsequent post, I will describe my attempts to "hide" the unsafe code, making the public C# interface "safe" and "re-objectified".