C# Fortran混合编程

基本目标 : 利用c#调用 fortran中的 souroutine 或者 function 方法。通过把fortran 代码编译成 .dll (windows)/.so(unix) 动态库,放到 c# 的执行路径下。再通过 c# 中的DllImport 方法导入库函数,实现在 c# 中调用 fortran 方法。

1. DllImport 方法

该方法 来自 System.Runtime.InteropServices , 是由.Net提供的方法。例如可以使用下面的代码调用user32.dll中的MessageBox方法。

using System;
using System.Runtime.InteropServices;

MessageBox (IntPtr.Zero,"Please do not press this again.", "Attention", 0);

[DllImport("user32.dll")]
static extern int MessageBox (IntPtr hWnd, string text, string caption,
int type);

这里在c# 中声明方法的时候需要注意以下几点:

  1. 函数应该声明为 static, extern 方法
  2. 函数的参数类型需要注意,这里仅仅以fortran为例。下文介绍了几种常见参数传递的对应情况。

DllImport方法有几个参数也需要注意:

  1. EntryPoint:这需要使用一些第三方工具从dll / so 文件中查找。win下使用vs 带的 dumpbin.exe方法,使用dumpbin /EXPORTS target.dll 命令查看。linux 下使用 nm -D target.so 查找定义的方法的entrypoint。
  2. CallingConvention: 主要使用 CallingConvention.Cdecl 或者 CallingConvention.StdCall, 具体差别可以google.

2. c# 方法 与 Fortran 方法 参数对应

这里以示例代码来说明

module one
	implicit none
	abstract interface 
	    subroutine Add(b, c, a)
			implicit none
			integer b
			real*8 a
			dimension a(*)
			real*8 c
		end subroutine
	end interface
contains
	subroutine CallfuntoAdd(f, b, c, a)
		!DEC$ ATTRIBUTES ALIAS:'CallfuntoAdd' :: CallfuntoAdd
        !DEC$ ATTRIBUTES DLLEXPORT :: CallfuntoAdd
        !DEC$ ATTRIBUTES reference :: f, a, b, c
		implicit none
		real*8 c
		integer b
		real*8 a
		dimension a(*)
		
        procedure(Add)::f
		Call f(b, c, a)
	    write(*,*) " c = ", c
		return
	end subroutine
end module one

在上面的Fortran代码里定义了一个CallfuntoAdd(f, b, c, a)方法,可以通过!DEC$被导出到 dll 文件中。这里f 是一个回调函数, b 是integer类型, c是real*8类型, a 是一个一维数组。

public class c1
    {
        [UnmanagedFunctionPointer(CallingConvention.Cdecl)]
        public delegate void AddFunc(ref int b, ref double c, [In] IntPtr a);

        public static void Add1(ref int b, ref double c, [In] IntPtr a)
        {
            double[] a1 = new double[b];
            Marshal.Copy(a, a1, 0, b);
            c = 0;
            for (int i = 0; i < b; i++)
            {
                c += a1[i];
            }
        }

        [DllImport("mod1.dll", EntryPoint = "__one_MOD_callfuntoadd", CallingConvention = CallingConvention.Cdecl)]
        public static extern void CallfuntoAdd([MarshalAs(UnmanagedType.FunctionPtr)] AddFunc f, ref int b, ref double c, double[] a);

        public static void Main()
        {
            double c;
            double[] a = new double[3] { 3.0, 2.0, 4.0 };
            int b = a.Length;
            c = 0;

            CallfuntoAdd(Add1, ref b, ref c, a);
            Console.WriteLine($" c = {c}");
        }
    }

上面的c# 代码定义了一个CallfuntoAdd函数用来表示 fortran中的CallfuntoAdd方法。这c# 的函数名可以通过fortran 中 !DEC$ ATTRIBUTES ALIAS:'CallfuntoAdd' :: CallfuntoAdd 重新定义。在c#中使用委托来表示fortran中的回调函数。可以看到c# 中的CallfuntoAdd函数参数与 fortran中的CallfuntoAdd 函数参数的对应关系。

fortran c#
integer ref int
real*4 ref float
real*8 ref double
一维数值 real*8, dimension(😃 :: a double[]
回调函数 delegate

c# 委托中有数组参数需要注意, 使用 IntPtr 表示数组,并且需要传入数组的大小,并且在函数中使用 Marshal.Copy 将参数复制到本地。

3. 将fortran 代码编译成动态库

这里使用 gfortran编译器。

gfortran -o test.o -c test.f90

gfortran -shared -o test.dll test.o -fPIC

posted @ 2024-03-20 17:00  hugowu  阅读(235)  评论(0)    收藏  举报