Search code examples
c#fortranpinvokemarshallingaccess-violation

AccessViolationException calling Fortran from C#


I'm trying to wrap the glmnet library (http://cran.r-project.org/web/packages/glmnet/index.html) so I can solve models sparse general linear models in C#. However, the original function has a somewhat 20 parameters, so I started (completely new to Fortran) with a tiny subroutine for testing how to pass data. Unfortunately I always get an AccessViolationException.

Here's the code:

The Fortran subroutine. I compile it into a dll using the gfortran compiler that comes with Rtools (http://cran.r-project.org/bin/windows/Rtools/), using the -m64 option (yes, 64bit is neccessary since I handle quite big chunks of data). Yes, the use of i could lead to out-of-bounds... but this is just for testing.

subroutine testaaa  (f,i,fa,ia)
real fa(i)                                                      
integer ia(i)
ia(1) = 1337
ia(i) = 666
fa(1) = 8.15
fa(i) = 333
end subroutine testaaa

The C# PInvoke code:

[DllImport("ftest.dll", EntryPoint = "testaaa_", CallingConvention = CallingConvention.StdCall)]
public static extern void Test(
    [MarshalAs(UnmanagedType.R4)] float f,
    [MarshalAs(UnmanagedType.I4)] int i,
    IntPtr fa,
    IntPtr ia);

And here is how it's called:

var fa = new float[4];
var ia = new int[4];
IntPtr faPtr = Marshal.AllocHGlobal(fa.Length * sizeof(float));
Marshal.Copy(fa, 0, faPtr, fa.Length);

IntPtr iaPtr = Marshal.AllocHGlobal(ia.Length * sizeof(float));
Marshal.Copy(ia, 0, iaPtr, ia.Length);

GlmnetDllWrapper.Test(0.4f, 4,faPtr,iaPtr);

I also tried passing the arrays directly and giving them the [MarshalAs(UnmanagedType.LPArray)] attribute. Nothing worked for me.

Do you have any suggestions where to start or what to change?

Update 1: Even passing only the float and the int already causes the exception:

subroutine testbbb  (f,i)
i = 815
f = 8.15
return
end subroutine testbbb

C# Pinvoke and call are changed accordingly. What am I doing wrong?


Solution

  • The main problem is that your Fortran library expects the scalar parameters to be passed by reference. So you need to declare your p/invoke to match.

    The array parameters can be passed quite simply as arrays and the p/invoke marshaller will pin them for you.

    So, your p/invoke declaration should be like this:

    [DllImport("ftest.dll", EntryPoint = "testaaa_")]
    public static extern void Test(
        ref float f,
        ref int i,
        [In, Out] float[] fa,
        [In, Out] int[] ia
    );
    

    You can adjust the [In, Out] attributes to meet your needs.