Intel® Fortran Compiler
Build applications that can scale for the future with optimized code designed for Intel® Xeon® and compatible processors.
28446 Discussions

Passing 2D array to C# app results in memory access violation

Bowman__Eric
Novice
1,285 Views

I have two fortran modules. 

This module is a struct that simply holds data: 

module LoadChartOutput_Type
    
    IMPLICIT NONE
    
    TYPE :: LoadChartOutput_Data
        
        sequence
              
        integer, Dimension(30,10)                     :: KWRCH
        integer, Dimension(30,10)                     :: KWRSCH
        integer, Dimension(30,10)                     :: KRCH
        integer, Dimension(30,10)                     :: KBACH
                
        CHARACTER (LEN=1), Dimension(30,10)           :: WRSYMB
    
    END TYPE LoadChartOutput_Data
    
end module

 

This Module provides a getter and setter to allow that data to be accessed from outside the dll: 

module LoadChartOutput_Mod
    
    use LoadChartOutput_Type

    implicit none
    
    TYPE :: LoadChartOutputs_Type
    
        type(LoadChartOutput_Data), Dimension(3)                            :: LoadCharts        
        
    END TYPE LoadChartOutputs_Type
    
    type(LoadChartOutputs_Type)                                             :: LoadChartOutputs
    
    contains
    
    SUBROUTINE GetLoadChartOutput(value)
        !DEC$ ATTRIBUTES DLLEXPORT :: GetLoadChartOutput
        !DEC$ ATTRIBUTES ALIAS: 'GetLoadChartOutput' :: GetLoadChartOutput
        !DEC$ ATTRIBUTES REFERENCE :: value
                    
        type(LoadChartOutput_Data), Dimension(3), INTENT(OUT)               :: value
        
        value = LoadChartOutputs%LoadCharts
        
    END SUBROUTINE GetLoadChartOutput
    SUBROUTINE SetLoadChartOutput(value)
        !DEC$ ATTRIBUTES DLLEXPORT :: SetLoadChartOutput
        !DEC$ ATTRIBUTES ALIAS: 'SetLoadChartOutput' :: SetLoadChartOutput
        !DEC$ ATTRIBUTES REFERENCE :: value
                    
        type(LoadChartOutput_Data), Dimension(3), INTENT(IN)                :: value
        
        LoadChartOutputs%LoadCharts = value
        
    END SUBROUTINE SetLoadChartOutput
    
end module

I then have two C# classes on the other side. 

 

This class imports the getter and setter from the fortran module: 

    [Serializable]
    public class LoadChartOutput_Data : ViewModelBase
    {
        #region Imported Fortran Getters/Setters
        // Fortran DLL interface.
        [DllImport("Libraries\\FP1_Library.dll", CallingConvention = CallingConvention.Cdecl)]
        private static extern void GetLoadChartOutput([MarshalAs(UnmanagedType.LPArray, SizeConst = 3), Out] LoadChartOutput[] val);
        [DllImport("Libraries\\FP1_Library.dll", CallingConvention = CallingConvention.Cdecl)]
        private static extern void SetLoadChartOutput([MarshalAs(UnmanagedType.LPArray, SizeConst = 3), In] LoadChartOutput[] val);

        #endregion

        #region Bindable Properties
        public ObservableCollection<LoadChartOutput> LoadChartOutputs
        {
            get;
            set;
        }
        #endregion

        public LoadChartOutput_Data()
        {            
        }
    
        public void PullFromFortran()
        {
            LoadChartOutput[] loadChartOutput = new LoadChartOutput[3];

            try
            {
                GetLoadChartOutput(loadChartOutput);
            }
            catch(Exception ex)
            {
                string stop = "here";
            }

            foreach (LoadChartOutput output in loadChartOutput)
            {
                LoadChartOutputs.Add(output);
            }
        }
    }

This class is a struct that matches that first fortran type that I listed. 

 

    [Serializable]
    [StructLayout(LayoutKind.Sequential, Pack = 8)]
    public struct LoadChartOutput
    {
        //integer, Dimension(30)                     :: KWRCH
        [MarshalAs(UnmanagedType.ByValArray, SizeConst = 300)]
        private int[] kwrch;
        public int[] KWRCH
        {
            get => kwrch;
            set => kwrch = value;
        }

        ////integer, Dimension(30,10)                     :: KWRSCH
        //public int[,] KWRSCH
        //{
        //    get => kwrsch;
        //    set => kwrsch = value;
        //}
        //private int[,] kwrsch;

        ////integer, Dimension(30,10)                     :: KRCH
        //public int[,] KRCH
        //{
        //    get => krch;
        //    set => krch = value;
        //}
        //private int[,] krch;

        ////integer, Dimension(30,10)                     :: KBACH
        //public int[,] KBACH
        //{
        //    get => kbach;
        //    set => kbach = value;
        //}
        //private int[,] kbach;

        //CHARACTER(LEN= 1), Dimension(30,10)           :: WRSYMB
        //public char[,][] WRSYMB
        //{
        //    get => wrsymb;
        //    set => wrsymb = value;
        //}
        //private char[,][] wrsymb;
    }
}

 
If I uncomment all of the properties in my class I get memory access violations when I call GetLoadChartOutput. I get memory access violations if I set SizeConst to anything greater than about 30 (it seems to vary from run to run). Sometimes it will crash without even throwing an exception when I call GetLoadChartOutput. Any tips on passing a 2D array from Fortran to C#?

 

Eric
 

0 Kudos
7 Replies
JohnNichols
Valued Contributor III
1,285 Views

This is a sample C# to Fortran progam - i used it several years ago - but gave up -- I know it works but you might have to play to set it up and get it running. 

If you are passing data - use a text file -- 

Just a suggestion 

 

0 Kudos
Bowman__Eric
Novice
1,285 Views

Thanks for the sample code. I got it working by creating a getter function that passes the 2D array to C#. For some reason I can do it that way without any trouble but passing a 2D array inside a complex type just wasn't working for me. 

Eric

0 Kudos
JohnNichols
Valued Contributor III
1,285 Views

No problem -- C# and Fortran are both good languages -- but mixing them is hard -- I tried and gave up

Interestingly it is quite simple to translate Fortran into C# and backwards

 

 

0 Kudos
FortranFan
Honored Contributor II
1,285 Views

Bowman, Eric wrote:

.. For some reason .. passing a 2D array inside a complex type just wasn't working for me. ..

My comment here is more for other readers who are looking for example code and are open to advice that the robust and extensible approach to interoperate Fortran with C# .NET involves following Microsoft's recommendations with P/Invoke layer to interop .NET with unmanaged code (per Microsoft terminology, usually code generated using C, C++, Fortran compilers on Windows).  And then on the Fortran side, to make use of standard features of interoperability with C in current standard Fortran.

With the former involving P/Invoke in .NET to interop with unmanaged code, Microsoft Docs is a good starting reference: https://docs.microsoft.com/en-us/dotnet/framework/interop.

Toward the latter i.e., modern Fortran and C interoperability, one can refer to the books in Dr Fortran blog: https://software.intel.com/en-us/blogs/2013/12/30/doctor-fortran-in-its-a-modern-fortran-world.  And review Intel Fortran documentation online: https://software.intel.com/en-us/fortran-compiler-developer-guide-and-reference-standard-fortran-and-c-interoperability

But now, with P/Invoke, Microsoft does the interoperation with "flattened" arrays i.e., rank-1 or 1D and it makes sense to use rank-1 arrays then in the .NET interop layer.

A simple example is shown below where the Fortran code works with rank-2 arrays (or higher rank) but the interop layer in .NET is rank-1.  And then with C# (or Visual Basic for that matter), one can easily define one's own 'class' or 'struct' that works with rank-2 (or higher) arrays which is all the consumers of said class/struct might see.

With 'char' data types and their arrays, the 'class' or 'struct' .NET might even work with StringBuilder class and the use the method in this class to manage the char array.   Alternately, one can use System.IntPtr as an opaque pointer to interop between C# and Fortran.  It's also possible to use a thin C layer to make use of extended interoperability features with C descriptors introduced in Fortran 2018.

Fortran:

module dat_m

   use, intrinsic :: iso_c_binding, only : c_int, c_char

   implicit none

   private

   integer, parameter :: M = 30, N = 10

   type, bind(C) :: dat_t
      integer(c_int) :: idat(M,N) = 0
      character(c_char) :: cdat(M,N) = c_char_"0"
   end type dat_t

   integer, parameter :: NDIM = 3
   
   type, bind(C) :: b_t
      type(dat_t) :: d(NDIM)
   end type b_t

   type(b_t), save :: dat

   public :: get_dat
   public :: set_dat

contains

   subroutine get_dat( vals, nval ) bind(C, name="get_dat")

      ! Argument list
      type(dat_t), intent(inout)        :: vals(*)
      integer(c_int), intent(in), value :: nval

      if (nval /= NDIM) then
         ! error handling elided
         return
      end if
       
      vals(1:nval) = dat%d
      print *, "In get_dat:"
      print *, "vals(1)%idat(1,1) = ", vals(1)%idat(1,1) 
      print *, "vals(1)%cdat(1,1) = ", vals(1)%cdat(1,1) 
      print *, "vals(3)%idat(M,N) = ", vals(1)%idat(M,N) 
      print *, "vals(3)%cdat(M,N) = ", vals(1)%cdat(M,N)
      print *
      
      return 

   end subroutine get_dat

   subroutine set_dat( vals, nval ) bind(C, name="set_dat")

      ! Argument list
      type(dat_t), intent(in)           :: vals(*)
      integer(c_int), intent(in), value :: nval

      if (nval /= NDIM) then
         ! error handling elided
         return
      end if
       
      dat%d = vals(1:nval)
      print *, "In set_dat:"
      print *, "vals(1)%idat(1,1) = ", vals(1)%idat(1,1) 
      print *, "vals(1)%cdat(1,1) = ", vals(1)%cdat(1,1) 
      print *, "vals(3)%idat(M,N) = ", vals(1)%idat(M,N) 
      print *, "vals(3)%cdat(M,N) = ", vals(1)%cdat(M,N)
      print *

      return 

   end subroutine set_dat

end module dat_m

C# .NET:

 

using System;
using System.Runtime.InteropServices;

namespace Fortran
{

   static class Fdll
   {

      internal const int N = 10;
      internal const int M = 30;
      internal const int NDIM = 3;

      [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)]
      internal struct dat_t
      {
         [MarshalAs(UnmanagedType.ByValArray, SizeConst = N*M)]
         internal int[] idat;
         [MarshalAs(UnmanagedType.ByValArray, SizeConst = N*M)]
         internal char[] cdat;
      }

      [DllImport("Fdll.dll", CallingConvention = CallingConvention.Cdecl)]
      internal static extern void set_dat([In] dat_t[] vals, int nval);

      [DllImport("Fdll.dll", CallingConvention = CallingConvention.Cdecl)]
      internal static extern void get_dat([In,Out] dat_t[] vals, int nval);

   }  

   static class TestFortran
   {
      static void Main()
      {

         Fdll.dat_t[] foo = new Fdll.dat_t[Fdll.NDIM];
         Fdll.dat_t[] bar = new Fdll.dat_t[Fdll.NDIM];

         try
         {

            int i;

            Console.WriteLine(" --- Test Fortran Program ---");

            for (i = 0; i < Fdll.NDIM; i++)
            {
               foo.idat = new int[Fdll.N*Fdll.M];
               foo.cdat = new char[Fdll.N*Fdll.M];
               for (int j = 0; j < Fdll.N; j++)
               {
                  for (int k = 0; k < Fdll.M; k++)
                  {
                     foo.idat[j * Fdll.M + k] = 42 + j * Fdll.M + k;
                     foo.cdat[j * Fdll.M + k] = char.Parse("$");
                  }
               }
            }
            Fdll.set_dat(foo, foo.Length);

            for (i = 0; i < Fdll.NDIM; i++)
            {
               bar.idat = new int[Fdll.N*Fdll.M];
               bar.cdat = new char[Fdll.N*Fdll.M];
               for (int j = 0; j < Fdll.N; j++)
               {
                  for (int k = 0; k < Fdll.M; k++)
                  {
                     bar.idat[j * Fdll.M + k] = -1;
                     bar.cdat[j * Fdll.M + k] = char.Parse("0");
                  }
               }
            }
            Fdll.get_dat(bar, bar.Length);
         }
         catch (Exception ex)
         {
            Console.WriteLine(ex.Message);
         }
         finally
         {
            Console.WriteLine("Back in C# main");
            Console.WriteLine("bar[0].idat[0] = " + bar[0].idat[0].ToString() + "; expected is " + foo[0].idat[0].ToString());
            Console.WriteLine("bar[0].cdat[0] = " + bar[0].cdat[0].ToString() + "; expected is " + foo[0].cdat[0].ToString());
            Console.WriteLine("bar[2].idat[last] = " + bar[2].idat[Fdll.N*Fdll.M-1].ToString() + "; expected is " + foo[2].idat[Fdll.N * Fdll.M - 1].ToString());
            Console.WriteLine("bar[2].cdat[last] = " + bar[2].cdat[Fdll.N * Fdll.M - 1].ToString() + "; expected is " + foo[2].cdat[Fdll.N * Fdll.M - 1].ToString());
         }

      }
   }
}

Upon execution, I get the following output which is only as expected:

 --- Test Fortran Program ---
 In set_dat:
 vals(1)%idat(1,1) =           42
 vals(1)%cdat(1,1) = $
 vals(3)%idat(M,N) =          341
 vals(3)%cdat(M,N) = $

 In get_dat:
 vals(1)%idat(1,1) =           42
 vals(1)%cdat(1,1) = $
 vals(3)%idat(M,N) =          341
 vals(3)%cdat(M,N) = $

Back in C# main
bar[0].idat[0] = 42; expected is 42
bar[0].cdat[0] = $; expected is $
bar[2].idat[last] = 341; expected is 341
bar[2].cdat[last] = $; expected is $

  

0 Kudos
MWind2
New Contributor III
969 Views

This does not compile(cs) as written on VS2022.

 

D:\c\vs2022\fdllcs\Fortran\Program.cs(50,29,50,33): error CS1061: 'Fdll.dat_t[]' does not contain a definition for 'idat' and no accessible extension method 'idat' accepting a first argument of type 'Fdll.dat_t[]' could be found (are you missing a using directive or an assembly reference?)
1>D:\c\vs2022\fdllcs\Fortran\Program.cs(51,29,51,33): error CS1061: 'Fdll.dat_t[]' does not contain a definition for 'cdat' and no accessible extension method 'cdat' accepting a first argument of type 'Fdll.dat_t[]' could be found (are you missing a using directive or an assembly reference?)
1>D:\c\vs2022\fdllcs\Fortran\Program.cs(56,37,56,41): error CS1061: 'Fdll.dat_t[]' does not contain a definition for 'idat' and no accessible extension method 'idat' accepting a first argument of type 'Fdll.dat_t[]' could be found (are you missing a using directive or an assembly reference?)
1>D:\c\vs2022\fdllcs\Fortran\Program.cs(57,37,57,41): error CS1061: 'Fdll.dat_t[]' does not contain a definition for 'cdat' and no accessible extension method 'cdat' accepting a first argument of type 'Fdll.dat_t[]' could be found (are you missing a using directive or an assembly reference?)
1>D:\c\vs2022\fdllcs\Fortran\Program.cs(65,29,65,33): error CS1061: 'Fdll.dat_t[]' does not contain a definition for 'idat' and no accessible extension method 'idat' accepting a first argument of type 'Fdll.dat_t[]' could be found (are you missing a using directive or an assembly reference?)
1>D:\c\vs2022\fdllcs\Fortran\Program.cs(66,29,66,33): error CS1061: 'Fdll.dat_t[]' does not contain a definition for 'cdat' and no accessible extension method 'cdat' accepting a first argument of type 'Fdll.dat_t[]' could be found (are you missing a using directive or an assembly reference?)
1>D:\c\vs2022\fdllcs\Fortran\Program.cs(71,37,71,41): error CS1061: 'Fdll.dat_t[]' does not contain a definition for 'idat' and no accessible extension method 'idat' accepting a first argument of type 'Fdll.dat_t[]' could be found (are you missing a using directive or an assembly reference?)
1>D:\c\vs2022\fdllcs\Fortran\Program.cs(72,37,72,41): error CS1061: 'Fdll.dat_t[]' does not contain a definition for 'cdat' and no accessible extension method 'cdat' accepting a first argument of type 'Fdll.dat_t[]' could be found (are you missing a using directive or an assembly reference?)

0 Kudos
MWind2
New Contributor III
1,015 Views

I had to change the c# code to get it to compile:

namespace Fortran
    {

        static class Fdll
        {

            internal const int N = 10;
            internal const int M = 30;
            internal const int NDIM = 3;

            [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)]
            internal struct dat_t
            {
                [MarshalAs(UnmanagedType.ByValArray, SizeConst = N * M)]
                internal int[] idat;
                [MarshalAs(UnmanagedType.ByValArray, SizeConst = N * M)]
                internal char[] cdat;
            }

            [DllImport("d:\\c\\vs2022\\fdllcs\\FDll1\\x64\\Debug\\FDll1.dll", CallingConvention = CallingConvention.Cdecl)]
            internal static extern void set_dat([In] dat_t[] vals, int nval);

            [DllImport("d:\\c\\vs2022\\fdllcs\\FDll1\\x64\\Debug\\FDll1.dll", CallingConvention = CallingConvention.Cdecl)]
            internal static extern void get_dat([In, Out] dat_t[] vals, int nval);

        }

        static class TestFortran
        {
            static void Main()
            {

                Fdll.dat_t[] foo = new Fdll.dat_t[Fdll.NDIM];
                Fdll.dat_t[] bar = new Fdll.dat_t[Fdll.NDIM];

                try
                {

                    int i;

                    Console.WriteLine(" --- Test Fortran Program ---");

                    for (i = 0; i < Fdll.NDIM; i++)
                    {
                        foo[i].idat= new int[Fdll.N * Fdll.M];
                        foo[i].cdat = new char[Fdll.N * Fdll.M];
                        for (int j = 0; j < Fdll.N; j++)
                        {
                            for (int k = 0; k < Fdll.M; k++)
                            {
                                foo[i].idat[j * Fdll.M + k] = 42 + j * Fdll.M + k;
                                foo[i].cdat[j * Fdll.M + k] = char.Parse("$");
                            }
                        }
                    }
                    Fdll.set_dat(foo, foo.Length);

                    for (i = 0; i < Fdll.NDIM; i++)
                    {
                        bar[i].idat = new int[Fdll.N * Fdll.M];
                        bar[i].cdat = new char[Fdll.N * Fdll.M];
                        for (int j = 0; j < Fdll.N; j++)
                        {
                            for (int k = 0; k < Fdll.M; k++)
                            {
                                bar[i].idat[j * Fdll.M + k] = -1;
                                bar[i].cdat[j * Fdll.M + k] = char.Parse("0");
                            }
                        }
                    }
                    Fdll.get_dat(bar, bar.Length);
                }
                catch (Exception ex)
                {
                    Console.WriteLine(ex.Message);
                }
                finally
                {
                    Console.WriteLine("Back in C# main");
                    Console.WriteLine("bar[0].idat[0] = " + bar[0].idat[0].ToString() + "; expected is " + foo[0].idat[0].ToString());
                    Console.WriteLine("bar[0].cdat[0] = " + bar[0].cdat[0].ToString() + "; expected is " + foo[0].cdat[0].ToString());
                    Console.WriteLine("bar[2].idat[last] = " + bar[2].idat[Fdll.N * Fdll.M - 1].ToString() + "; expected is " + foo[2].idat[Fdll.N * Fdll.M - 1].ToString());
                    Console.WriteLine("bar[2].cdat[last] = " + bar[2].cdat[Fdll.N * Fdll.M - 1].ToString() + "; expected is " + foo[2].cdat[Fdll.N * Fdll.M - 1].ToString());
                }

            }
        }
    }
0 Kudos
JohnNichols
Valued Contributor III
1,285 Views

Brilliant - thank you 

0 Kudos
Reply