0

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?

Pieter Geerkens
  • 11,775
  • 2
  • 32
  • 52
Ahue
  • 817
  • 8
  • 13
  • 1
    Is is possible that your Fortan library receives the parameters by pointer? Also, why are you testing everything all at once. Why have you not simplified? Can you pass a single parameter of type `int`? Of type `float`? Then try an array. Don't test the most complicated thing first. How can you tell which part fails? – David Heffernan Mar 02 '13 at 09:19
  • 2
    OK, your update and my comment crossed. Well done for simplifying. Try passing as pointers: `public static extern void Test(ref float f, ref int i)` – David Heffernan Mar 02 '13 at 09:21
  • Once you get past the blockage on the simple types, we can show you how to do the arrays. And there's no need for `IntPtr`. We can get the pinvoke marshaller to pin the arrays and make the calling code very simple. – David Heffernan Mar 02 '13 at 09:37
  • @DavidHeffernan `ref` does the job. Passing the arrays without any attribute works. Adding the `LPArray` attribute is just as fine. Manipulating arrays is also fine. Trying a more complex szenario now. Will comment when there's more to tell. – Ahue Mar 02 '13 at 09:43

2 Answers2

2

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.

David Heffernan
  • 601,492
  • 42
  • 1,072
  • 1,490
  • You'd better put CalingConvention back, Fortran is almost always stdcall. More about gfortran [here](http://gcc.gnu.org/onlinedocs/gcc-4.7.0/gfortran/Interoperability-with-C.html#Interoperability-with-C) – Hans Passant Mar 02 '13 at 12:12
  • @HansPassant `StdCall` is the default which is why I removed it. Or am I misinterpreting you? – David Heffernan Mar 02 '13 at 14:18
  • Yes, you're right, I fumbled that, sorry. The link I left should give the OP hints what calling convention and whether or not *ref* is required from the Fortran source code. The VALUE and USE statements matter. – Hans Passant Mar 02 '13 at 16:57
  • Yep, I think that's it! Thanks a lot for your help! The glmnet library is working... now I have to deal with its output. However, I left out the `In` and `Out` arguments. It works with and without them. Maybe adding them back makes the code more readable - or this there more to this? – Ahue Mar 03 '13 at 10:15
  • 1
    There is a litte more. Read @Hans's answer to this question: http://stackoverflow.com/questions/14366066/are-p-invoke-in-out-attributes-optional-for-marshaling-arrays – David Heffernan Mar 03 '13 at 11:35
-1

Please have a look at http://msdn.microsoft.com/en-en/library/chfa2zb8%28v=VS.80%29.aspx (unsafe code) and play around with your project settings Project/Properties/Build: allow unsafe code. But be aware of the consequences. :)

Update: Do not "play around" - I meant: "Check out the "unsafe" features". "Unsafe" doesn't mean "dangerous".

Michael
  • 621
  • 5
  • 17
  • 1
    No, please do not do this. There's no need for unsafe code. And "play around with your settings" does not an answer make. – David Heffernan Mar 02 '13 at 09:32
  • Yes, sorry. I meant: "Try wrapping the area with unsafe code and allow unsafe code once" to see if the issue is located there. It's a way to handle pointers especially while working with ext. non-.Net assemblies, if "ref" doesn't work. But you're right. It's no need, because a simple "ref" works. – Michael Mar 02 '13 at 09:50