- 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