Help us understand the problem. What is going on with this article?

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#側に出力引数として渡す
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)

end module FortranLib

!テスト1 C#側から渡された入力ファイルパスを受け取り、対象ファイルの内容を受け取る
!入力引数:対象ファイルの絶対パス
function TranslateViaInputFile(inputPath)
    use FortranLib
    implicit none
    !GCC$ ATTRIBUTES DLLEXPORT :: TranslateViaInputFile

    character(256), intent(in) :: inputPath
    integer :: ios
    integer :: i
    logical :: TranslateViaInputFile
    type(PointType), pointer :: point
    TranslateViaInputFile = .false.

    open(unit=10,file = Trim(inputPath), action='read', form='formatted', status='old')
    do i=1, STRC_SIZE, 1 
       point => testdata(i)
       read(10,*, iostat=ios) point%ID,point%Coords(:)
       if (ios < 0) exit 
    end do
    close(10)

    TranslateViaInputFile = .true.
end function TranslateViaInputFile

!テスト2 ポインターを使い、構造体に値を格納
Function TranslateViaTypes()
    use FortranLib
    implicit none
    !GCC$ ATTRIBUTES DLLEXPORT :: TranslateViaTypes
    logical ::TranslateViaTypes
    integer :: i
    type(PointType), pointer :: point
    !ポインタはmoduleに書いた通り、IDと数字三つでワンセット

    TranslateViaTypes = .false.
    do i = 1, STRC_SIZE
        point => testdata(i) !参照
        point%ID = i
        point%Coords(1) = i 
        point%Coords(2) = i + 1
        point%Coords(3) = i + 2
    end do    
    TranslateViaTypes = .true.!成功したらTrueを返す
end Function TranslateViaTypes

!テスト3 ポインターを使い、指定された行の構造体を返す
!入力引数 n:指定された行数
!出力引数 outPutData:n行目の構造体
!戻り値 bool 成功したか否か
Function GetData(n,outPutData)
    use FortranLib
    implicit none
    !GCC$ ATTRIBUTES DLLEXPORT :: GetData

    integer, intent(in) :: n
    logical ::GetData
    type(PointType), intent(inout),target:: outPutData(1)
    type(PointType), pointer :: point
    !初期化
    GetData = .false.

    !同じ値を参照するようにする
    point => outPutData(1)

    !inputdataと値は独立しているか確認
    !point%ID = inputData(n)%ID
    !point%Coords(:) = inputData(n)%Coords(:)+1

    point%ID = testdata(n)%ID
    point%Coords(:) = testdata(n)%Coords(:)

    GetData = .true.!成功したらTrueを返す
end Function

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);
            }
        }
    }
}

参考

hal_777777
よく言語を連携させている駆け出しSE。 C#↔python、C#↔Fortran(DLL)、python↔Fortran(勉強中)
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
No comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
ユーザーは見つかりませんでした