现代化的Fortran 之 撤离Fortran指南
所谓撤离指南, 其实是实在不想用Fortran的“光荣遗产”的时候该怎么办。在Fortran2003之前, 标准中并没有规定这一部分, 因此Fortran跟C的互动要么是通过读写文件, 要么是通过特定编译器生成binary的规则来进行的(这里要默哀一位师弟)。本文将介绍Fortran2003中加入的与C相互调用的规范。
Fortran引入标准的C接口目的有两个:
1. 使C可以调用Fortran, 明确Fortran“性能友好,数组友好”的定位, 将其他复杂逻辑交给其他语言实现。这方面典型的例子是BLAS/Lapack库,大多数都使用Fortran来承担底层计算密集部分的代码, 比如IBM的ESSL。
2.使Fortran可以通过C调用其他的库, 弥补Fortran用户群萎缩带来的第三方库短缺问题。比如下文将会介绍的Aotus为Fortran提供了lua 解释器, 还有比较insane的例子GTK-Fortran,用Fortran来实现GUI。
基础数据类型对应规则
要实现数据互通, 首先要让Fortran知晓C的数据类型, 跟C有关的数据类型包含在iso_c_binding module内, 调用的时候需要引入模块
在声明接口时, 对应的数据类型需要使用kind声明为C的数据类型, 比如:
数据类型的命名规范基本都是 C_。 Fortran和C数据类型的补集比如Fortran中的复数类型, 四精度类型, C中的枚举类型, 指针类型, 以及各种控制字符有相应的命名, 详见GCC Reference
用户定义数据类型
Fortran自定义的数据类型使用 bind(c) 与C 对应, 比如:
对应于C中的
函数调用
binary中最常见的一种互动模式是相互调用, 所以我们需要提供一个函数签名(Fortran中分为subroutine和function, 下文统称为函数), C里面自然是声明:
Fortran中用于指定签名的是interface结构, 比如:
这里插一句,一个在module中定义的函数编译器可以自动生成签名, 就是编译时候出来的一大堆.mod文件, 所以为了方便应尽量将函数放在module里面,可以在编译时发现参数传递错误。
不同于C, Fortran中规定函数的参数传递为地址传递,相当于压入栈中的是变量的地址而不是变量值, 因此对于一个Fortran函数:
对应的C函数声明其实是:
当然这是Fortran的默认行为, 在新标准中, 可以使用value参数将实参的值传进去, 比如上面的例子。可以将m声明为 real(kind=c_double), value :: m 将m的值压入栈, 对应的c声明就可以写成double m了。
通过上面的描述, 读者应该发现由C端的声明产生对应的Fortran interface或者反过来由Fortran的interface产生C端的.h文件相当机械, 可以用parser代替人工。 然而这需要解决: 1. C中的宏展开; 2. C中的typedef。 考虑到上述两个问题, 实现一个功能完善的parser并不简单, 现有的一些项目如GTK-Fortran使用了特定的parser用于生成GTK API对应的Fortran Interface, 但只能解析GTK中的数据类型。 而彻底解决这个问题有赖于将要完成的Fortran 2015标准, 其中进一步规定了如何跟C中的typedef和宏进行交互。
数组类型传递
在高维数组方面, Fortran/C最显著的不同就是Fortran是Column major, 而C是Row major。 简而言之对于一个二维数组, Fortran内部存贮规则是, 第一个指标变化最快。 而C则是最后一个指标变化最快。 而在Fortran95中标准中, 新添加的Pointer和Allocatable对数组进行了更多功能的封装, 在编译器实现中, 这个封装的结构体叫做Array Descriptor
, 比如自带数组形状信息, 或者指向的数据的形状信息, 非连续段访问信息等。 因此在二进制表示上可能无法与C互通, 因此在Fortran2003中, bind(c) Interface中的数组形参只可以是固定长度的数组或者假定长度的数组, 不过实参可以是任意类型(固定长度, 假定长度, 假定形状, 指针, 可动态分配, 数组片段和非连续访问)的数组, 当实参是非连续数组时,由编译器负责进行copy in 和copy out 操作。(在Fortran2003 的补充规范TS29113中, 放松了对形参的限定, 将成为Fortran2015标准)
从标准手册上来看, 从Fortran传递一个二维数组到C应该是传指针的指针, 但在实际测试中, intel 编译器和 GNU编译器都是将这个二维数组的头地址直接传递到C端。 此处存疑, 因此建议在实际操作中将高位数组直接用reshape函数或者pack函数拍扁为一维数组, 连同数组长度一并传过去, 出现问题的可能性较小。
字符串类型的传递
Fortran中的character类型更类似于C++中string类的精简版, 比如每一个character类型都有长度参量len,在传递给C的时候要格外小心。 目前的c binding标准中, C的字符数组(字符串)对应的fortran的形参类型是长度为1, 字符类型为c_char的一维数组, 但是实参可以是一个长度为len的character型变量。同时需要注意的是在传递字符数组给C的时候要符合C的规定,在结尾补一个C_NULL_CHAR。
Reference:
Modern Fortran Explained
C Interface on fortranwiki.orgFortran中如何使用C binding通过Unix API加载动态库-----------------------------------------------------------------------------
Fortran调用C示例:Aotus -- Fortran interface of Lua
Fortran自带的IO工具较弱, 为了方便用户快速实现一些简单的config文件功能,read语句自带有一个namelist功能, 即可以使用Fortran的语法和注释规范来读取配置文件。 但在实际操作中, 限制和缺陷很多。 因此这位德国的Harald Klimach大大将Lua的c接口移植到了Fortran2003里面, 使Fortran程序可以内嵌Lua解释器。给配置文件提供更大的灵活性。项目的网站:https://bitbucket.org/haraldkl/aotus/overview
Aotus的源文件文件分为两个文件夹, 一部分(LuaFortran)是直接从C 转换到Fortran的底层接口, 另一部分(source)是使用Fortran95 自定义类型和函数重载实现的高级API。LuaFortran内主要包含两个文件,lua_fif.f90内含一堆interface块, 对应于lua.h中定义的C API函数原型, flu_binding.f90包含一些Fortran函数, 用于将Fortran的数据类型显式转换为iso_c_binding中的数据类型, 然后调用C API。 所以, 在Fortran中调用C函数,需要两部分Fortran代码, 一部分用于指定.h文件中函数原型对应的interface, 另一部分为wrapper函数,用于将Native Fortran的数据类型转换为iso_c_binding数据类型。比如
Prototype in C
Interface in Fortran
wrapper function in Fortran
反过来, 在C中调用Fortran需要一个wrapper, 显式转换数据类型, interface可以由module自动生成。C端则需要一个跟module对应的.h文件。、
更多示例
Fortran中如何使用C binding通过Unix API加载动态库GTK-Fortran,使用Fortran编写简单的GUIFortran与其他语言的交互线代数值库 Trilinos 的 Fortran2003 OO Interface本站所有文章、数据、图片均来自互联网,一切版权均归源网站或源作者所有。
如果侵犯了你的权益请来信告知我们删除。邮箱:dacesmiling@qq.com
下一篇:撤离