Search code examples
c#fortranpinvokegfortran

How do you pass Fortran's complex type to C#?


Suppose I have the following Fortran code

subroutine COMPLEX_PASSING(r, i, c)
    !DEC$ ATTRIBUTES DLLEXPORT::COMPLEX_PASSING

    REAL*8 :: r, i
    COMPLEX*8 :: c

   c = cmplx((r * 2), (i * 2))

return
end

Fortran code was compiled with

gfortran -c complex_passing.f90
gfortran -fPIC -shared -o complex_passing.dll complex_passing.o

How would I call this subroutine in C#? I have tried the following code:

using System;
using System.Runtime.InteropServices;

namespace FortranCalling {
    class Program {
        static void main(string[] args) {
            double real = 4;
            double imaginary = 10;
            COMPLEX c = new COMPLEX();

            complex_passing( ref real, ref imaginary, ref c);
            Console.WriteLine("Real: {0}\nImaginary: {1}", c.real, c.imaginary);
            Console.ReadLine();
        }

        [StructLayout(LayoutKind.Sequential)]
        struct COMPLEX {
            public double real;
            public double imaginary;
        }

        [DllImport("complex_passing.dll", EntryPoint = "complex_passing_", CallingConvention = CallingConvention.Cdecl)]
        static extern void complex_passing(ref double r, ref double i, ref COMPLEX c);
    }
}
            

With little success - my COMPLEX struct seems to be returning garbage data:

Real: 134217760.5
Imaginary: 0

When I would expect the real part to be 8 and the imaginary part to be 20.


Solution

  • gfortran treats the non-standard COMPLEX*8 as a complex of size 8 bytes, with real and imaginary components 4 bytes each. You instead require a complex of 16 bytes, with real and imaginary components of 8 bytes each (COMPLEX*16) or you should change the C# side accordingly.

    The effect of this is visible with the following under gfortran:

    complex*8 :: c8 = (8d0, 20d0)
    complex*16 :: c16 = 0
    
    c16%re = TRANSFER(c8,c16)
    
    print*, c8, c16 
    
    end
    

    Of course, you shouldn't be using complex* at all. The argument mismatch can be seen using complex(kind=..).

    Consider the following "Fortran" source:

    subroutine s(r, i, c)
      real(kind(0d0)) :: r, i
      complex(kind(0e0)) :: c
      c = cmplx((r*2),(i*2))
    end subroutine s
    
    interface ! Interface block required to lie to some versions of gfortran 
    subroutine s(r, i, c)
      real(kind(0d0)) :: r, i
      complex(kind(0d0)) :: c
    end subroutine s
    end interface
    
    complex(kind(0d0)) c
    call s(4d0, 10d0, c)
    print*, c%re
    
    end
    

    and compare it with the Fortran source:

    subroutine s(r, i, c)
      real(kind(0d0)) :: r, i
      complex(kind(0d0)) :: c
      c = cmplx((r*2),(i*2))
    end subroutine s
    
    complex(kind(0d0)) c
    call s(4d0, 10d0, c)
    print*, c%re
    
    end
    

    Further, rather than using kind(0d0) etc., there are the various C interoperability constants and storage-size constants of iso_fortran_env.