C#とFortran（DLL）を連携させる方法

C#からFortran(DLL)を呼び出すテスト用PGを作成したので、備忘録として残しておきます。Intel Fortranでコンパイルする場合、エントリポイントの指定の仕方が下記と異なるので注意してください。
C#⇔Fortranを行う際、文字列は半角空白埋め、構造体をそれぞれ定義して値の受け渡しを行っています。

# 環境
- CPU：CoreTM i5-6400（2.70GHz）
- 物理メモリー：8GB
- Windows 10 Pro（64bit），MS Office 2016（32bit）

# コンパイラ
- gfortran (gcc version 8.1.0)
- Visual Studio Express 2015

# Fortran(fortranLib.f03)のコード
Fortran側で作成した関数は以下の通り

| 関数名 | 説明 |
|:-:|:-:|
| TranslateViaTypes  |  構造体を作成し、module内のtestDataという構造体に値を格納する |
| TranslateViaInputFile  | 引数として入力ファイルの絶対パスを渡すと、対象ファイルの内容を読み込みmodule内の構造体に保存する  |
| GetData  | 入力引数として指定した行位置での構造体を取得し、C#側に出力引数として渡す  |

```fortran:FortranLib.f03
module FortranLib
    implicit none

    !構造体
    type, bind(c) :: PointType
        integer :: Id
        real(8) :: Coords(3)
    end type

    !ここに書いておけばPublic/Privateに使えるので、固定値や関数とか書いておく
    !省略だとpublic
    !USE側でreadオンリーで使用等も指定可能
    integer, parameter :: STRC_SIZE = 10
    type(PointType),target,save:: testdata(STRC_SIZE)

```

# gfortranでのコンパイルオプション
static未指定では、gfortranの環境がないと動かない

-static-libgfortran -static-libgcc だけでは他環境で動かなかった

```
gfortran -c -fno-underscoring -Wall -Wc-binding-type -Wuninitialized FortranLib.f03 -o FortranLib.o

gfortran -shared -mrtd -fno-underscoring -"Wl,--kill-at" -fdump-core -fbacktrace  -w -Wall -static -Wc-binding-type FortranLib.o -o FortranLib.dll
```
# Fortranでreadするファイルの内容

※実際にはヘッダーはなし

```text:test.txt
id,{x,y,z}
500,11,12,13
501,21,22,23
502,31,32,33
503,41,42,43
504,51,52,53
505,61,62,63
506,71,72,73
507,81,82,83
508,91,92,93
509,101,102,103
```
# C# （FortranLib.cs）

```c#:FortranLib.cs
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading.Tasks;

namespace test
{
    [StructLayout(LayoutKind.Sequential, Pack = 8)]
    public struct Point
    {
        public int Id;
        [MarshalAs(UnmanagedType.ByValArray, SizeConst = 3)]
        public double[] Coords;

        public Point(int id, double x, double y, double z)
        {
            this.Id = id;
            this.Coords = new double[3] { x, y, z };
        }
    }
    
    public static class FortranLib
    {
        private const string _dllName = @".\FortranLib.dll";

        public const int PathLength = 256;

        //moduleの内部手続きとして定義した場合
        //entrypointでの名前がモジュールの内部手続き扱いになっている
        //[DllImport(_dllName, CharSet = CharSet.Ansi, CallingConvention = CallingConvention.Cdecl, EntryPoint = "__fortranlib_MOD_translateviaarrays")]
        //public static extern void TranslateViaArrays([In] double[] delta, ref int n, [In, Out] double[,] coords);

        //module共有（module test）で完全別手続きにした
        [DllImport(_dllName, CharSet = CharSet.Ansi, CallingConvention = CallingConvention.Cdecl, EntryPoint = "translateviaarrays")]
        public static extern void TranslateViaArrays([In] double[] delta, ref int n, [In, Out] double[,] coords);
        
        [DllImport(_dllName, CharSet = CharSet.Ansi, CallingConvention = CallingConvention.Cdecl, EntryPoint = "translateviainputfile")]
        public static extern bool TranslateViaInputfile([In] char[] inputPath);

        [DllImport(_dllName, CharSet = CharSet.Ansi, CallingConvention = CallingConvention.Cdecl, EntryPoint = "translateviatypes")]
        public static extern Boolean TranslateViaTypes();

        [DllImport(_dllName, CharSet = CharSet.Ansi, CallingConvention = CallingConvention.Cdecl, EntryPoint = "getdata")]
        public static extern Boolean GetData(ref int n, [In, Out] Point[] outputData);
        
        /// <summary>
        /// Fortran用半角空白文字埋め
        /// </summary>
        /// <param name="source">文字列</param>
        /// <param name="length">文字列の長さ</param>
        /// <returns>指定した長さに合わせて半角空白埋めをした文字列</returns>
        public static char[] ToCharacterArrayFortran(this string source, int length)
        {
            var chars = new char[length];
            int sourceLength = source.Length;
            for (int i = 0; i < length; i++)
            {
                if (i < sourceLength)
                    chars[i] = source[i];
                else
                    chars[i] = ' '; 
            }
            return chars;
        }
    }
}
        
```

# C#で作成した画面用PG

| Formに配置するコントロール | 説明 |
|:-:|:-:|
|  rowData | 行数入力用のNumericUpDown  |
|  OpenData | 配列(構造体)作成ボタン  |
|  readData | ファイル読み込みボタン  |
|  openDataButton | データ表示ボタン  |

```c#:Form.cs
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;

namespace test
{
    public partial class Main : Form
    {
        public Main()
        {
            InitializeComponent();
        }

        /// <summary>
        /// データ表示ボタン押下時のイベント
        /// </summary>
        /// <param name="sender">イベントのオブジェクト</param>
        /// <param name="e">イベントのプロパティ</param>
        private void OpenData_Click(object sender, EventArgs e)
        {
            //FortranCall テスト1 C#から渡された配列内の値をFortran内で変更して戻す
            RunTranslateViaTypes();
        }

        /// <summary>
        /// ファイル読み込みボタン
        /// </summary>
        /// <param name="sender">イベントのオブジェクト</param>
        /// <param name="e">イベントのプロパティ</param>
        private void readData_Click(object sender, EventArgs e)
        {
            RunTranslateViaInputFile();
        }

        /// <summary>
        /// 画面読み込み
        /// </summary>
        /// <param name="sender">イベントのオブジェクト</param>
        /// <param name="e">イベントのプロパティ</param>
        private void Main_Load(object sender, EventArgs e)
        {
            //画面の大きさの変更はなし
            this.FormBorderStyle = FormBorderStyle.FixedSingle;

            //フォームの最大化ボタンの表示、非表示を切り替える
            MaximizeBox = false;

            //フォームの最小化ボタンの表示、非表示を切り替える
            MinimizeBox = false;

        }

        /// <summary>
        /// データ表示ボタン
        /// </summary>
        /// <param name="sender">イベントのオブジェクト</param>
        /// <param name="e">イベントのプロパティ</param>
        private void openDataButton_Click(object sender, EventArgs e)
        {
            int rowValue = (int)rowData.Value;
                GetData(rowValue);

        }

        /// <summary>
        /// Fortran内関数呼び出しテスト用
        /// </summary>
        private static void RunTranslateViaArrays()
        {
            var delta = new[] { 1.0, 2.0, 3.0 };
            int n = 10;
            //fortran側へ渡す＆変更される配列
            var coords = new double[n, 3];
            for (int i = 0; i < n; i++)
            {
                coords[i, 0] = i;
                coords[i, 1] = i + 1;
                coords[i, 2] = -i;
            }

            FortranLib.TranslateViaArrays(delta, ref n, coords);
            //Call後coords内の値が変更されていることがわかる

            //コンソールに出力し確認
            for (int i = 0; i < n; i++)
                Console.WriteLine("Point {0}: ({1}, {2}, {3})", i + 1, coords[i, 0], coords[i, 1], coords[i, 2]);
        }

        /// <summary>
        /// Fortran内関数呼び出しテスト2
        /// </summary>
        private static void RunTranslateViaInputFile()
        {
            string inputPath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "test.txt");
            // 一端文字列をFortran用に渡す（未使用部分半角空白埋め）
            char[] inputPathChars = inputPath.ToCharacterArrayFortran(FortranLib.PathLength);
            long returnValue=0;
            //文字列をFortranへ渡す
            bool resultValue = FortranLib.TranslateViaInputfile(inputPathChars);
            if (resultValue)
            {
                MessageBox.Show("ファイルを読み込みました", "確認",
                    MessageBoxButtons.OK,
                    MessageBoxIcon.Information);
            }
            else
            {
                MessageBox.Show("読み込みが失敗しました", "エラー",
                    MessageBoxButtons.OK,
                    MessageBoxIcon.Warning);
            }
        }
        
        /// <summary>
        /// 指定した格納領域にFortranで格納した構造体を受け取る
        /// </summary>
        private static void RunTranslateViaTypes()
        {
            int n = 10;//行数

            //Fortran内関数を呼ぶ 第二引数で指定したメモリ領域に値を格納
            bool resultValue = FortranLib.TranslateViaTypes();
            //上記実行後、pointにはFortran側で格納した値が入っている
            if (resultValue)
            {
                MessageBox.Show("配列を作成しました", "確認",
                    MessageBoxButtons.OK,
                    MessageBoxIcon.Information);
            }
            else
            {
                MessageBox.Show("配列作成が失敗しました", "エラー",
                    MessageBoxButtons.OK,
                    MessageBoxIcon.Warning);
            }
        }


        /// <summary>
        /// 指定された行数に該当するデータを取得する
        /// </summary>
        /// <param name="rowValue">取得する行数</param>
        private static void GetData(int rowData)
        {
            Point[] outputData = new Point[1];
            bool resultValue = FortranLib.GetData(ref rowData, outputData);
            if (resultValue)
            {
                MessageBox.Show("配列を取得しました" + Environment.NewLine +
                    $@"ID:{outputData[0].Id} x:{outputData[0].Coords[0]} y:{outputData[0].Coords[1]} z:{outputData[0].Coords[2]}", "確認",
                    MessageBoxButtons.OK,
                    MessageBoxIcon.Information);
            }
            else
            {
                MessageBox.Show("配列取得が失敗しました", "エラー",
                    MessageBoxButtons.OK,
                    MessageBoxIcon.Warning);
            }
        }
    }
}
```
# 参考
- [How to Call Fortran DLLs from C#](http://www.luckingtechnotes.com/calling-fortran-dll-from-csharp/)