- Mark as New
- Bookmark
- Subscribe
- Mute
- Subscribe to RSS Feed
- Permalink
- Report Inappropriate Content
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
Link Copied
- Mark as New
- Bookmark
- Subscribe
- Mute
- Subscribe to RSS Feed
- Permalink
- Report Inappropriate Content
- Mark as New
- Bookmark
- Subscribe
- Mute
- Subscribe to RSS Feed
- Permalink
- Report Inappropriate Content
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
- Mark as New
- Bookmark
- Subscribe
- Mute
- Subscribe to RSS Feed
- Permalink
- Report Inappropriate Content
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
- Mark as New
- Bookmark
- Subscribe
- Mute
- Subscribe to RSS Feed
- Permalink
- Report Inappropriate Content
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 $
- Mark as New
- Bookmark
- Subscribe
- Mute
- Subscribe to RSS Feed
- Permalink
- Report Inappropriate Content
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?)
- Mark as New
- Bookmark
- Subscribe
- Mute
- Subscribe to RSS Feed
- Permalink
- Report Inappropriate Content
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());
}
}
}
}
- Mark as New
- Bookmark
- Subscribe
- Mute
- Subscribe to RSS Feed
- Permalink
- Report Inappropriate Content
Brilliant - thank you

- Subscribe to RSS Feed
- Mark Topic as New
- Mark Topic as Read
- Float this Topic for Current User
- Bookmark
- Subscribe
- Printer Friendly Page