December 13, 2019

How to Migrate from IMSL.NET to IMSL C

Embedded Analytics

As IMSL.NET (C#) Numerical Library comes to an end, we've seen an increase in requests for how to transition to IMSL C (CNL) while still using C#. This blog provides an introduction on how to pass a C# function to a CNL routine, and how to handle arrays that will be seen and modified by both managed and unmanaged code using Platform Invoke (P/Invoke) to solve these issues.

What Is P/Invoke?

Official documentation describes P/Invoke as:

"A technology that allows you to access structs, callbacks, and functions in unmanaged libraries from your managed code. Most of the P/Invoke API is contained in two namespaces: System and System.Runtime.InteropServices. Using these two namespaces gives you the tools to describe how you want to communicate with the native component."

Check out more detailed information on P/Invoke.

Calling a CNL Routine from Managed Code

Let's look at how to perform a simple call to a CNL routine from C#. Many CNL routines return results as pointers to arrays or other data structures that must be freed in order to avoid memory leaks. If a CNL routine, called from a managed environment, returns a result in this manner, then the CNL routine imsl_free must be used to free this memory.

The following code will call the CMath routine imsl_free, which takes a pointer generated previously by CNL, and frees the associated memory. More details on the imsl_free routine can be found in CNL documentation.

First, we use DllImport to load the managed library imslcmath_imsl_dll.dll and then define a managed method that will call the unmanaged function imsl_free.

[DllImport("imslcmath_imsl_dll.dll", EntryPoint = "imsl_free",
CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Unicode)]
static extern void Free(IntPtr pointer);

In this example, the CMath shared library imslcmath_imsl_dll.dll, as well as its relative path, is provided to DllImport (more information on DllImport default search locations). EntryPoint is specified as 'imsl_free', since that is the routine that we want to import into managed code. If the function declared in managed code uses the exact name as the function in unmanaged code, EntryPoint can be eliminated. CallingConvention.Cdecl is used. Specifying Cdecl as the calling convention means that the caller cleans the stack rather than the callee function. Unicode is specified as the character set.

The CMath function imsl_free can now be called from C#.

public static void Main()
    // Do work, CNL returns solution as a pointer, result

Passing a C# Function to CNL

This example will show how to pass a C# function to a CNL routine. It uses the CMATH routine min_uncon (more details on min_uncon routine). Type double min_uncon has the following signature:

double imsl_d_min_uncon (double fcn(), double a, double b, …, 0)

where fcn() is a user-supplied function. Since CNL will need to call fcn(), a C# function in managed code, one or more times during the routine, we can set up a callback to allow this. DllImport is used once again to import the unmanaged library, and a function imsl_d_min_uncon is defined, which will call the CNL function of the same name. Note that since the managed function is given the same name as the unmanaged routine to be called, the EntryPoint does not need to be specified.

     internal delegate double Callback(double F);
     CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Unicode)]
static extern double imsl_d_min_uncon(Callback callback, double a, double b, 0);

A simple use case may look like:

public static void Main()
    double fcn(double x)
        return Math.Exp(x) - 5.0 * x;
    double a = -100.0;
    double b = 100.0;
    double result = 0;
    double fx;
    result = imsl_d_min_uncon(fcn, a, b);
    fx = fcn(result);
    Console.WriteLine("Minimum is " + result);
    Console.WriteLine("The function evaluated at the solution is: " + fx +   "\n");   

Handling Arrays With Marshaling (Safe Code)

Here is another example of passing a C# function to a CNL routine. It uses the CMATH routine nonlin_least_squares. nonlin_least_squares has the following signature:

double* imsl_d_nonlin_least_squares (void fcn(), int m, int n, …, 0);

where fcn is a user-supplied function:

void fcn (int m, int n, double x[], double f[])

Again, DllImport loads the CNL library and a C# method to call the CNL routine of interest is defined.

internal delegate void NonLinearLeastSquaresCallback(int m, int n, IntPtr x, IntPtr f);
[DllImport(DLL_NAME, EntryPoint = "imsl_d_nonlin_least_squares",
CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Unicode)]
internal static extern IntPtr NonLinearLeastSquares(NonLinearLeastSquaresCallback callback, int m, int n, int termination = 0);

Next, we need to define fcn().

private static void fcn(int m, int n, IntPtr x, IntPtr f)
    double[] y = { 2.0, 4.0, 3.0 };
    double[] t = { 1.0, 2.0, 3.0};
    double[] xValues = new double[n];
    Marshal.Copy(x, xValues, 0, n);
    double[] values = new double[m];
    Marshal.Copy(f, values, 0, m);
    for (int i = 0; i < m; i++)
        values[i] = Math.Exp(xValues[0] * t[i]) - y[i];
    Marshal.Copy(values, 0, f, m);

We avoid the use of unsafe code via the use of marshaling. In this example, CNL provides pointers to two arrays, x and f, as arguments to the C# function. We use marshaling to copy the data from unmanaged arrays to managed arrays. C# can now operate on the data. After modification, marshaling is used again to copy the data back to the unmanaged array so CNL can continue to operate on the data as needed.

A call to imsl_d_nonlin_least_squares from CSNL would look like:

int m = 3;
int n = 1;
IntPtr result = NonLinearLeastSquares(fcn, m, n);

Note that since the result of NonLinearLeastSquares was a pointer pointing to memory allocated by CNL, the memory would need to be freed via the imsl_free function (as seen in the first example above)

Handling Arrays With Unsafe Code

Now let’s look at calling the same CNL function, but this time providing a function with unsafe code. Rather than use marshaling to copy data between managed and unmanaged code, unsafe code operates on arrays (in this example) via pointers. For this example, we will use the same CNL routine imsl_d_nonlin_least_squares as above, but using a new version of fcn with unsafe code.

For more information on unsafe code, consult Microsoft documentation.

private static void UnsafeFcn(int m, int n, IntPtr xPointer, IntPtr fPointer)
        double* y = stackalloc double[] { 2.0, 4.0, 3.0 };
        double* t = stackalloc double[] { 1.0, 2.0, 3.0 };
        double* x = (double*)xPointer;
        double* f = (double*)fPointer;
        for (int i = 0; i < m; i++)
            f[i] = Math.Exp(x[0] * t[i]) - y[i];

Need Help Making the Move?

Customers can move their IMSL.NET environment to any other IMSL Library for free.

You can review this Cross Reference Guide for details on how to find the right CNL function from its CSNL equivaliant. Or if you need support for the migration, contact us.