1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

C#とFortran(DLL)を連携させる方法

Last updated at Posted at 2022-04-14

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#側に出力引数として渡す
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するファイルの内容

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

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)

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 データ表示ボタン
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);
            }
        }
    }
}

参考

1
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?