1

This is kind of a follow up to this question.

I have a user defined type (TYPE(contactside)) that contains some values and some fixed size arrays, as well as an exported function that uses this structure as input arguments. Now I want to call this function from . I am able to create fixed sized arrays in a struct (ContactSide) and it works just fine. I can pass the arrays to fortran

My question is, is it possible to do this without resorting to unsafe code? With just regular dynamic C# arrays double[] radius instead of fixed double radius[MaxSize]. When I tried it without any [MarshalAs()] specifications, I get garbage:

fixed array dynamic array

Fortan

module contacts
use ISO_C_BINDING
implicit none

    integer, parameter :: max_size = 3600;

    type, bind(c) :: contactside
        INTEGER(c_int) :: n
        REAL(c_double) :: elasticity, poisson
        REAL(c_double) :: radius(max_size), crown(max_size)
    end type

    contains

    !  FUNCTIONS/SUBROUTINES exported from dll:
    subroutine Hertz(load, side1, side2) bind(c)
    implicit none
    !DEC$ ATTRIBUTES DLLEXPORT::Hertz
    ! Arguments
    REAL(c_double),value,intent(in) :: load
    TYPE(contactside),intent(in)    :: side1, side2

    ! Implementation omitted
    end subroutine Hertz

end module

C#

public unsafe struct ContactSide
{
    const int MaxSize = 3600;
    int size;
    double elasticity, poisson;
    fixed double radius[MaxSize], crown[MaxSize];
    public ContactSide(double radius, double crown) : this(new double[] { radius }, crown) { }
    public ContactSide(double[] radius, double crown)
    {
        this.size=radius.Length;
        fixed (double* ptr1 = this.radius, ptr2 = this.crown)
        {
            for(int i = 0; i<size; i++)
            {
                ptr1[i]=radius[i];
                ptr2[i]=crown;
            }
        }
        this.elasticity=DefaultElasticity;
        this.poisson=DefaultPoisson;
    }

    public static double DefaultElasticity = 200000;
    public static double DefaultPoisson = 0.3;
}

class Program
{
    [DllImport("FortranContacts.dll", EntryPoint = "hertz", CallingConvention = CallingConvention.Cdecl)]
    public extern static void Hertz(double load, ref ContactSide side1, ref ContactSide side2);

    static void Main(string[] args)
    {
        ContactSide side1 = new ContactSide(8.0, 1200);
        ContactSide side2 = new ContactSide(new double[] { 22, 22.2, 22.8, 24.6, 25.8, 29.3 }, 10000);

        Hertz(1000, ref side1, ref side2);

    }
}

and here is the failed attempt at a plain (not unsafe) structure:

public struct ContactSide
{
    int size;
    double elasticity, poisson;
    double[] radius, crown;

    public ContactSide(double radius, double crown) : this(new double[] { radius }, crown) { }
    public ContactSide(double[] radius, double crown)
    {
        this.size=radius.Length;
        this.radius=new double[size];
        this.crown=new double[size];
        radius.CopyTo(this.radius, 0);
        this.crown= Enumerable.Repeat(crown, size).ToArray();
        this.elasticity=DefaultElasticity;
        this.poisson=DefaultPoisson;
    }

    public static double DefaultElasticity = 200000;
    public static double DefaultPoisson = 0.3;
}

NOTES: I am using VS2015 with Intel Fortran XE on Win7-64bit

15
  • After doing that you might be able to pass the derived type by value as well. But I can't say anything about the C# side. Commented May 4, 2016 at 13:54
  • I added use ISO_C_BINDING and declared my types as INTEGER(c_int) and REAL(c_double), but nothing changed. Oh, I also changed the declaration to type, bind(c) :: contactside. Commented May 4, 2016 at 14:02
  • [MarshalAs(UnmanagedType.ByvalArray, SizeConst = 3600)] is the alternative. It is a lot more expensive since the pinvoke marshaller must now copy the structure and the arrays. Safety and efficiency are often opposing goals. Commented May 4, 2016 at 14:03
  • @HansPassant I tried that, but I got an Exception. My goal is specify a dynamic array < 3600 in size and have the marshaler fill in the values on the fortran side. "An unhandled exception of type 'System.ArgumentException' occurred in ConsoleDriver.exe Additional information: Type could not be marshaled because the length of an embedded array instance does not match the declared length in the layout." Commented May 4, 2016 at 14:06
  • 1
    I installed mono on my computer and tried combining C# with gfortran (by googling a lot :) If I define a struct containing bare C-pointers and pass the struct to Fortran routines, I could read/modify the values of the arrays using c_f_pointer(). But if I include double[] etc in the struct, my mono failed with segfault (even if I include bare-C pointer together with dynamic C# arrays). So, the compiler seems unhappy with double[] in the struct... Also, if I use bare C-pointers, the code needs to be specified unsafe. Commented May 7, 2016 at 18:35

1 Answer 1

1

[This is based on ignorance of C# and is more a formatted extended comment. And wholly untested.]

There is no Fortran derived type which is C interoperable with a C struct type which has a flexible array member. I'm going to assume that that's essentially what is happening with the C# code.

In particular, the definition

type, bind(c) :: contactside
    INTEGER(c_int) :: n
    REAL(c_double) :: elasticity, poisson
    REAL(c_double) :: radius(max_size), crown(max_size)
end type

doesn't necessarily allow for arbitrary memory placement of those array components.

What could be possible, depending on other uses in your code of that derived type, is to use pointer components. Not standard Fortran pointers, but type(c_ptr)s:

type, bind(c) :: contactside
    INTEGER(c_int) :: n
    REAL(c_double) :: elasticity, poisson
    type(c_ptr) :: radius, crown
end type

Later on, those components can be dereferenced in the traditional C interoperability way

real(c_double), pointer, dimension(:) :: local_radius, local_crown
...
call c_f_pointer(side1%radius, local_radius, [max_size])
call c_f_pointer(side1%crown, local_crown, [max_size])
Sign up to request clarification or add additional context in comments.

10 Comments

Could you use the actual size contactside%n in assigning the local arrays?
error #6285: There is no matching specific subroutine for this generic subroutine call. [C_F_POINTER]
For that generic, the arguments should be: a type(c_ptr); a rank-1 array with the pointer attribute; a rank-1 extent-1 array of sizes. In case I wrote something wrong, could you check those match (or write here the call)?
And when that c_f_pointer does work, yes, the size could be that n. It'll need to be specified as call c_f_pointer(..., ..., [side1%n]), though (array as the last argument).
So the critical point is whether double[] radius, crown; represents bare C-pointers in the struct ContactSide (in the memory)...? I'm wondering whether they are implemented internally so or as array descriptors (like allocatable arrays in Fortran). Is there any possibility of getting bare C-pointers of allocated regions in the C# side...? (I have no experience with C# either so not sure though...)
|

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.