Fortran 和 MariaDB

简介

Fortran(FORmula TRANslating System,公式翻译系统)是一种通用、指令式编程语言,尤其适合数值计算和科学计算。FORTRAN 的历史可以追溯到 1953 年末,当时 John W. Backus 向他在 IBM 的上级提交了一份提案。第一款 FORTRAN 编译器于 1957 年 4 月出现。

一些值得注意的历史里程碑包括:

  • 1958 年的 FORTRAN II
  • 1958 年的 FORTRAN III
  • 1962 年的 FORTRAN IV
  • 1966 年的 FORTRAN 66 或 X3.9-1966 成为第一个行业标准
  • 1978 年的 FORTRAN 77 或 X3.9-1978。这是我 1996 年学习的 Fortran 版本。
  • 1991 年发布了 Fortran 90(ISO/IEC 标准 1539:1991),1992 年成为 ANSI 标准
  • 1997 年发布了 Fortran 95(ISO/IEC 标准 1539-1:1997)
  • 2004 年发布了 Fortan 2003(ISO/IEC 1539-1:2004)
  • 2010 年发布了 Fortran 2008(ISO/IEC 1539-1:2010),这是最新的标准
  • Fortran 2015 计划于 2016 年末发布。

更全面的历史和简介可以参考,例如 https://en.wikipedia.org/wiki/Fortran

因此,Fortran 编程语言并未消亡!我在写这篇博客的同一天(2015 年 5 月 7 日)还在使用 Fortran。我决定学习 Fortran 是出于某些历史原因。在赫尔辛基大学计算机科学系,有一门名为“软件项目”的课程,学生需要设计、实现和测试大型应用程序。我参加了 1996 年的这门课程,我的应用程序是为赫尔辛基大学自然历史博物馆鸣禽中心开发的鸣禽软件。他们原来的软件使用磁带和 Fortran66/77 程序。我们的任务是将其转换为使用 Oracle 数据库和 UNIX。当时,我们决定使用 Fortran77(带有一些 Fortran90 扩展,主要是结构体)和 Oracle 提供的 ProFortran 预处理器。

编译器

有一个名为 GFortran 的 GNU Fortran 版本。GFortran 编译器完全兼容 Fortran 95 标准,并包含对遗留 F77 的支持。此外,还实现了一些 Fortran 2003 和 Fortran 2008 特性。

根据我的经验,GFortran 是一个非常好的编译器,确实包含了你需要的大部分遗留支持(以及很多我并不需要的新功能)。然而,我发现一个有用但不被支持的例子,即可变长度格式。考虑以下代码:

       cnt = max_year - min_year + 1
       WRITE (*, 20) (i, i = min_year, max_year)
   20  FORMAT ('Reng', (2X, I4), 2X, '  Total')

这里的格式 (2x, I4) 根据运行时值重复多次。这可以转换为:

       cnt = max_year - min_year + 1
       WRITE(fmt,'(A,I2,A,A)') '(A,',cnt,'(2X,I4)',',A)'
       WRITE (*, fmt) 'Reng', (i, i = min_year, max_year), ' Total'

这是因为格式可以是一个字符串变量,上面的代码会生成格式 (A,44(2X,I4),A)(假设年份为 1971 和 2014)。但是,在我看来,第一个版本更清晰、更简单。此外,我学会了使用 pre-Fortran90 的 STRUCTURE 和 RECORD 扩展,例如:

  STRUCTURE /TVERSION/
    CHARACTER *80  VERSION
  END STRUCTURE

  RECORD /TVERSION/ MARIADB

  MARIADB.VERSION = ''

这自然可以使用 TYPE 来表达:

  TYPE t_version
    CHARACTER *80  :: version
  END TYPE

  TYPE(t_version) mariadb
  mariadb%version = ' '

我主要使用 Fortran90 和自由格式(行长度比标准 Fortran77 允许的更长),但只使用了有限的新功能。因此,代码看起来大部分像 Fortran77。

   50  CONTINUE
   55  FORMAT(I10,1X, A1, 1X, A)
       READ (10, 55, END = 70, ERR=800, IOSTAT = readstat, IOMSG=emsg) pesaid, rlaani, rkunta
       plaani(pesaid) = rlaani
       pkunta(pesaid) = rkunta
       GOTO 50

当然,还有一些商业 Fortran 编译器,如 Intel Fortran https://software.intel.com/en-us/fortran-compilers 和 NAG http://www.nag.com/nagware/np.asp

Fortran 显然的一个缺点是隐式类型。如果变量未声明,Fortran 77 使用一套隐式规则来确定类型。这意味着所有以字母 i-n 开头的变量都是整数,其余的都是实数。许多旧的 Fortran 77 程序都使用这些隐式规则,但你不应该这样做!如果你不始终如一地声明变量,程序出错的可能性会急剧增加。因此,务必在 Fortran 程序的开头加上以下代码:

     PROGRAM myprogram

        IMPLICIT NONE  ! No implicit rules used, compiler error instead

SQL 和 Fortran

Fortran 本身不理解 SQL 语句,但你可以使用嵌入式 SQL 等方式。嵌入式 SQL 是主机语言(如 Fortran)中的 SQL 语句。让我们举个例子:

      EXEC SQL BEGIN DECLARE SECTION
       CHARACTER *24 HTODAY
      EXEC SQL END DECLARE SECTION
      EXEC SQL INCLUDE SQLCA
      EXEC ORACLE OPTION (ORACA = YES)
      EXEC SQL INCLUDE ORACA
      EXEC SQL CONNECT :UID1 IDENTIFIED BY :UID2
      EXEC SQL SELECT TO_CHAR(SYSDATE,'YYYYMMDD HH24:MI:SS')
     -      INTO :HTODAY
     -      FROM DUAL

当然,普通的 Fortran 编译器不会理解以 EXEC SQL 开头的语句。因此,你首先需要使用预处理器。预处理器会改变嵌入式 SQL 语句(上面的 include 语句会被复制到结果文件中),其他 SQL 语句则会被转换为对所提供的数据库服务器 API 调用的 CALL 语句。自然,这意味着你的软件只能针对预处理(然后编译)的数据库提供商工作。

目前,至少有 Oracle 和 DB2 数据库的预处理器(参见 https://en.wikipedia.org/wiki/Embedded_SQL)。然而,操作系统支持正在减少。例如,Oracle Fortran 预处理器在使用 Oracle >10g 时不再在 Linux 64 位系统上工作。在我看来,这很糟糕,因为将你的 Fortran 软件从 Oracle 移植到 DB2 等数据库并非易事,特别是当你的应用程序有 10 万行 Fortran 代码时。

根据我的经验,这一事实导致了这样的局面:部分系统使用 Java 重写,部分代码修改为纯 Fortran,以便从文件(使用纯 SQL 生成)读取输入,并删除所有嵌入式 SQL 语句。

Fortran 和 MariaDB

Fortran 没有针对 MariaDB/MySQL 的连接器。然而,你可以使用 ODBC,但免费的 ODBC 模块 FLIBSfodbc 在我的 64 位 Linux 系统上编译失败,经过对 fodbc 的一些修改,它并没有真正工作。当然,你可以自己为 MariaDB/MySQL 编写一个 fodbc,但我目前没有真正的需求或足够的空闲时间这样做。另一种方法是创建 Fortran 代码和 ODBC 驱动程序之间的 C 语言接口。

让我们举一个非常简单的例子,Fortran 程序连接到 MariaDB 数据库,选择一个版本,然后断开连接。

  PROGRAM myodbc_test

  INTEGER :: RC

  TYPE t_version
    CHARACTER *80  :: version
  END TYPE

  TYPE(t_version) mariadb

  RC = 0

  RC = connect()
  mariadb%version='select version()'//char(0)
  RC = version(mariadb)
  CALL mstrc2f(mariadb%version)
  WRITE (*,'(A)') mariadb%version
  RC = disconnect()

  STOP

  END PROGRAM

  SUBROUTINE mstrc2f(STR)

      IMPLICIT NONE

      CHARACTER *(*) STR
      INTEGER   *4 MAX
      INTEGER   *4 IND
      CHARACTER *1  EOS
      EOS  = CHAR(0)
      MAX  = LEN(STR)
      IND = MAX
  100 CONTINUE
      IF ( IND .GE. 1 ) THEN
          IF ( STR(IND:IND) .EQ. EOS) THEN
              GO TO 200
          ENDIF

          STR(IND:IND) = ' '
          IND = IND - 1

          GO TO 100
      ENDIF


  200 CONTINUE

      IF (IND .GE. 1) THEN
          STR(IND:IND) = ' '
      ENDIF

      RETURN

      END

正如你所注意到的,字符串变量需要特殊处理,因为 Fortran 有常量字符串。因此,在调用 C 例程之前,我们需要添加 C 字符串结束符,然后在再次在 Fortran 中使用字符串之前删除尾部。然后是一个简单的 C 接口(没有真正的错误处理):

#include 
#include 
#include 
#include 

SQLHENV env;
SQLHDBC dbc;

int connect_(void) {

  SQLHSTMT stmt;
  SQLRETURN ret;
  SQLCHAR outstr[1024];
  SQLSMALLINT outstrlen;

  SQLAllocHandle(SQL_HANDLE_ENV, SQL_NULL_HANDLE, &env);
  SQLSetEnvAttr(env, SQL_ATTR_ODBC_VERSION, (void *) SQL_OV_ODBC3, 0);
  SQLAllocHandle(SQL_HANDLE_DBC, env, &dbc);
  ret = SQLDriverConnect(dbc, NULL, "DSN=test;", SQL_NTS,
			 outstr, sizeof(outstr), &outstrlen,
			 SQL_DRIVER_COMPLETE);

  return ret;
}

int disconnect_(void) {
    SQLDisconnect(dbc);
    SQLFreeHandle(SQL_HANDLE_DBC, dbc);
    SQLFreeHandle(SQL_HANDLE_ENV, env);
    fprintf(stderr, "Disconnected...n");
}

typedef struct {
	char version[80];
} t_version;

int version_(t_version *version) {
	SQLHSTMT stmt;
	SQLRETURN ret;
	SQLSMALLINT columns;
	char buf[80];
	SQLLEN indicator;
	SQLAllocHandle(SQL_HANDLE_STMT, dbc, &stmt);

	fprintf(stderr, "Selecting version...n");

	ret = SQLPrepare(stmt,
           version->version, SQL_NTS);
	ret = SQLExecute(stmt);
	ret = SQLFetch(stmt);
	ret = SQLGetData(stmt, 1, SQL_C_CHAR, buf, sizeof(buf), &indicator);
	strcpy(version->version, buf);
	return ret;
}

而且,如果你编译这些代码并运行生成的可执行程序,你可能会看到类似以下的结果:

$ gcc myodbc.c -c -g -l myodbc5
$ gfortran myodbc_test.f90 myodbc.o -l myodbc5 -g
$ ./a.out
Selecting version...
10.0.18-MariaDB-debug                                                           
Disconnected...

Fortran 的未来?

显然存在对 Fortran 这样语言的需求。它有一些非常好的特性,如格式化 I/O 和数学函数。然而,学习 Fortran 可能取决于你自己,因为它在大多数大学或类似学校不作为第一(或第二)编程语言教授。因此,能够使用 Fortran 编程或教授它的人数正在迅速减少。然而,我的经验是,如果你至少掌握了一种编程语言,学习 Fortran 是简单的(好吧,我以前的学习中已经学过 C/C++、COBOL、PL/I、Basic)。所以你想学习 Fortran 吗?如果互联网资源不够,还有一些书。我用过的书已经过时了(Fortran 77 和芬兰语版本的 Fortran 90/95),但例如 http://www.amazon.com/Introduction-Programming-Fortran-With-Coverage/dp/0857292323 是一本不错的书。