目录
前言
一、转换Fortran77到Fortran90
二、寻找Fortran图形库
三、制作Fortran图形库
四、调用图形库
五、配置VScode的编译task
前言
因为要维护一些古老的代码,才开始接触Frotran。
代码是Fortran77固定格式的,编写风格相当废柴,各种复杂,超级难读。突然发现现在的编码规约基本都是针对Fortran代码的,比如说不能写goto、变量命名要规范、逻辑要清晰能让别人看懂。
代码中纯Fortran的部分编译时比较好办,麻烦在其中使用QWin库,使用Compaq Visual Fortran6.5可以编译通过。但CVF在Win10上比较难用,所以本文介绍如何转到VSCode上进行编译使用,主要介绍QWin库的替代问题。
一、转换Fortran77到Fortran90
读Fortran77格式的代码及相关算法,感觉像是进入了爆炸现场,各种触目惊心吧。尽管90格式差不多像是暗无天日的地下矿场,但也好多了,所以打算先转成90格式再说。
因为有3万行Fortran代码,手工转换不可想象,所以用C#写了一个转换程序,从F77代码转换到F90代码,可以完成95以上的工作,细节部分人工校对。
private void Convter77To90(){
String[] strCode = textBox1.Text.Split('\n');
String strResult = "";
String strLF = "\r\n";
String strKep = "";
int ii = 0;
try
{
for (int i=0; i<strCode.Length; i++)
{
String strLine = strCode[i].TrimEnd();
ii = i; strKep = strLine;
String strComment = "";
if (strLine.IndexOf('!') >= 0)
{
int n = strLine.IndexOf('!');
strComment = strLine.Substring(n).TrimEnd();
strLine = strLine.Remove(n).TrimEnd();
if (strComment.Length == 1)
strComment = "";
}
if (strLine.IndexOf('c') == 0)
{
strLine = "!" + strLine.Remove(0, 1);
if (strLine.Length == 1)
strLine = "";
}
else if (strLine.TrimStart().IndexOf('&') == 0)
{
strLine = strLine.Replace('&', ' ');
if (strLine.IndexOf("then") > 0)
{
strCode[i - 1] = strCode[i - 1].TrimEnd() + strLine;
strLine = "";
}
else
{
int n = strCode[i - 1].IndexOf('!');
if (n < 0)
strCode[i - 1] = strCode[i - 1].TrimEnd() + '&';
else
{
String str = strCode[i - 1].Substring(n);
strCode[i - 1] = strCode[i - 1].Remove(n).TrimEnd() + "&\t" + str;
}
}
}
else
{
if (strLine.IndexOf(".eq.") > 0) strLine = strLine.Replace(".eq.", " == ");
if (strLine.IndexOf(".ne.") > 0) strLine = strLine.Replace(".ne.", " /= ");
if (strLine.IndexOf(".gt.") > 0) strLine = strLine.Replace(".gt.", " > ");
if (strLine.IndexOf(".ge.") > 0) strLine = strLine.Replace(".ge.", " >= ");
if (strLine.IndexOf(".lt.") > 0) strLine = strLine.Replace(".lt.", " < ");
if (strLine.IndexOf(".le.") > 0) strLine = strLine.Replace(".le.", " <= ");
if (strLine.IndexOf(".or.") > 0) strLine = strLine.Replace(".or.", " .or. ");
if (strLine.IndexOf(".and.") > 0) strLine = strLine.Replace(".and.", " .and. ");
if (strLine.IndexOf(".not.") > 0) strLine = strLine.Replace(".not.", " .not. ");
if (strLine.IndexOf(".eqv.") > 0) strLine = strLine.Replace(".eqv.", " .eqv. ");
if (strLine.IndexOf(".neqv.") > 0) strLine = strLine.Replace(".neqv.", " .neqv. ");
int n = strLine.IndexOf("do ");
if (n >= 0)
{
String strPre = strLine.Substring(0, n);
strLine = strLine.Remove(0, n);
strLine = strLine.Trim().Replace('\t', ' ');
strLine = strLine.Replace(" ", " ").Replace(" ", " ");
String[] arToken = strLine.Split(' ');
if (arToken.Length > 2 && arToken[2].IndexOf('=') >= 1)
{
String strLabel = arToken[1].Trim();
String str = " " + strLabel + " ";
for (int j = i + 1; j < strCode.Length; j++)
{
if (strCode[j].IndexOf(" do ") >= 0)
continue;
String str1 = " " + strCode[j].Replace('\t', ' ');
str1 = str1.Replace(" ", " ");
if (str1.IndexOf(str) == 0 || str1.IndexOf("!* " + strLabel + "") > 0)
{
if (strCode[j].IndexOf(strLabel) >=0 && strCode[j].IndexOf("continue") < 0)
{
strCode[j - 1] = strCode[j - 1] + "\n\t" + str1.Replace(str, "");
strCode[j] = " " + strLabel + " continue";
str1 = strCode[j];
if (j - 1 == i)
{
strLine = strCode[j - 1].TrimEnd();
if (strComment.Length > 0)
strLine = strLine.Replace(strComment, "");
}
}
if (strCode[j].IndexOf(strLabel) >= 0 && strCode[j].IndexOf("continue") > 1)
{
if (str1.IndexOf(strLabel) == 1)
strCode[j] = "\tenddo\t!*" + str1.TrimEnd();
else
strCode[j] += "\r\n\tenddo";
strLine = strLine.Replace(strLabel + " ", "") + "\t!*" + strLabel;
break;
}
}
}
}
strLine = strPre + strLine;
}
}
strCode[i] = strLine +"\t" + strComment;
}
for (int i = 0; i < strCode.Length; i++)
strResult += strCode[i] + strLF;
strCode = strResult.Split('\n');
FormatFortran(strCode);
strResult = "";
for (int i = 0; i < strCode.Length; i++)
strResult += strCode[i].TrimEnd() + strLF;
}
catch (System.Exception ex)
{
MessageBox.Show("出错了.\n" + ex.Message);
}
textBox2.Text = strResult;
}
private void FormatFortran(String[] arCode){
String strIndent = "";
bool bAddIndent = false;
int ii = 0;
try
{
for (int i = 0; i < arCode.Length; i++)
{
ii = i;
bAddIndent = false;
String strLine = arCode[i].Trim();
String strComment = "";
int n = strLine.IndexOf('!');
if (n >= 0)
{
strComment = strLine.Substring(n);
strLine = strLine.Remove(n);
}
if (strLine.IndexOf("end") == 0) strIndent = strIndent.Remove(0, 1);
else if (strLine.IndexOf("subroutine") == 0) bAddIndent = true;
else if (strLine.IndexOf("function") == 0) bAddIndent = true;
else if (strLine.IndexOf("do ") >= 0) bAddIndent = true;
else if (strLine.IndexOf("if") >= 0 && strLine.IndexOf("then") > 0) bAddIndent = true;
arCode[i] = strIndent + strLine + strComment;
if (bAddIndent) strIndent += "\t";
}
}
catch (System.Exception ex)
{
MessageBox.Show("出错了.\n" + ex.Message);
}
}
二、寻找Fortran图形库
由于代码中使用的图形库主要应用了文本和直线输出,没有牵扯到其他复杂的图形界面,所以本文尝试用标准编译库MinGW解决问题。
要是Fortran代码中有窗口之类的复杂界面编程,本文也没法解决,请移步Intel Visual Fortran(IVF)+ Visual Studio2019。
诡异的是,没有找到任何支持MinGW的Frotran图形库。想想也是,DOS世界里,谁会用Fortran去弄个图形界面来?!
想起了当年用Turbo C 编写代码,弄的满屏幕图形乱飞的情景,肯定有支持C/C++的图形库。经过一番查找,果然找到了EasyX Graphics Library for C++,并且是免费的,感谢大神们的贡献!
既然有C++的图形库,自然也可以让Fortan来使用。
三、制作Fortran图形库
准备工作有:
1、下载MinGW64-seh,注意要seh版本的。
2、下载EasyX_20220610_for_MinGW版本。
3、新建一个Graphic.cpp,写入下面的代码:
#include "graphics.h"
#include<conio.h>
#include <stdio.h>
#include <math.h>
extern "C"
{
__declspec(dllexport) void InitGraphic(int* maxx, int* maxy);
__declspec(dllexport) void ExitGraphic();
__declspec(dllexport) void ResetString(char* pText, int* nLen);
__declspec(dllexport) void Moveto(int* x, int* y);
__declspec(dllexport) void Lineto(int* x, int* y);
__declspec(dllexport) void SetTextClr(int* clr);
__declspec(dllexport) void SetLineClr(int* clr);
__declspec(dllexport) void OutputText(char* pText, int* x, int* y);
}
void InitGraphic(int* maxx, int* maxy)
{
initgraph(*maxx, *maxy);
}
void Moveto(int* x, int* y)
{
moveto(*x, *y);
}
void Lineto(int* x, int* y)
{
lineto(*x, *y);
}
unsigned int arColors[16] =
{
BLACK,
BLUE,
GREEN,
CYAN,
RED,
MAGENTA,
BROWN,
LIGHTGRAY,
DARKGRAY,
LIGHTBLUE,
LIGHTGREEN,
LIGHTCYAN,
LIGHTRED,
LIGHTMAGENTA,
YELLOW,
WHITE
};
void SetTextClr(int* clr)
{
if (*clr > 0 && *clr < 16)
settextcolor(arColors[*clr]);
}
void SetLineClr(int* clr)
{
if (*clr > 0 && *clr < 16)
setlinecolor(arColors[*clr]);
}
void OutputText(char* pText, int* x, int* y)
{
outtextxy(*x, *y, pText);
}
void ExitGraphic()
{
closegraph();
}
void ResetString(char* pText, int* nLen)
{
memset(pText, 0, *nLen);
}
上面的代码只和画线、输出文本相关,其他绘制功能可根据需要自行添加。
另外写了一个ResetString用来给Fortran中的字符串结尾(Fortran中的字符串是固定长度,结尾没有0,所以传入C++后,会出现乱码 。需要在调用C++函数前,用此函数在结尾设置0)。
Fortran只能调用标准C格式的函数,需要导出的函数一定要用extern "C"括起来。
4、用G++编译成dll
可以把下面的命令写成bat文件,或在Cmd中单步执行也可以。
set path=%path%;D:\Soft\mingw64-seh\bin
cd /d %~dp0
g++.exe -c Graphic.cpp -I D:\Soft\easyx4mingw\include
g++.exe -shared -o graphic.dll Graphic.o libeasyx.a
注意默认路径是Graphic.cpp文件所在的路径。不同于VC程序,g++编译的dll没有相应的lib文件,其函数入口信息包含在dll文件中了。
四、调用图形库
1、制作接口函数模块
新建一个glib.f90文件,写入以下代码:
module glib
type xycoord
integer xcoord
integer ycoord
end type
interface
subroutine moveto(x,y) bind(c,name='Moveto')
integer x
integer y
end subroutine
end interface
interface
subroutine lineto(x,y) bind(c,name='Lineto')
integer x,y
end subroutine
end interface
interface
subroutine initgraphic(x,y) bind(c,name='InitGraphic')
integer x,y
end subroutine
end interface
interface
subroutine settextcolor(clr) bind(c,name='SetTextClr')
integer clr
end subroutine
end interface
interface
subroutine setlinecolor(clr) bind(c,name='SetLineClr')
integer clr
end subroutine
end interface
interface
subroutine exitgrapic() bind(c,name='ExitGraphic')
end subroutine
end interface
interface
subroutine outtext(txt, x, y) bind(c,name='OutputText')
character txt(*)
integer x,y
end subroutine
end interface
interface
subroutine resetstr(str, len) bind(c,name='ResetString')
character str(*)
integer len
end subroutine
end interface
end module
编译成mod文件:
gfrotran.exe -c glib.f90
2、画线,要注意graphicsmode和closegrapic的配对使用。
subroutine drawtest()
integer maxx, maxy
maxx=1300
maxy=1000
graphicsmode(maxx,maxy) !注意,绘图要在另外一个线程里启动了一个窗口,所以开始绘图一定要调用graphicsmode,退出时要调用closegrapic,回到出程序的线程.
line(100,100,200,200,BLUE)
call write_text ('回车退出', 0.9*xmax,-0.1*ymax,13)
read(*,*)
closegrapic()
end
subroutine line(nx1, ny1, nx2, ny2, n)
use glib
integer status,result
call setlinecolor(n)
call moveto( nx1, ny1)
call lineto( nx2,ny2)
end
subroutine graphicsmode(maxx, maxy)
use glib
call initgraphic(maxx, maxy);
end
subroutine closegrapic()
use glib
call exitgrapic()
end
3、输出文字
subroutine drawtest()
integer maxx, maxy
character*20 txt1,txt2
maxx=1300
maxy=1000
graphicsmode(maxx,maxy) !注意,绘图要在另外一个线程里启动了一个窗口,所以开始绘图一定要调用graphicsmode,退出时要调用closegrapic,回到出程序的线程.
!给Fortran字符串的末尾设置0
call resetstr(txt1,21)
call resetstr(txt2,21)
txt1="Hello Fortran"
txt2="Graphic Mode"
call write_text (txt1,100,100,13)
call write_text (txt2,100,150,13)
call write_text ('回车退出', 0.9*xmax,-0.1*ymax,13)
read(*,*)
closegrapic()
end
subroutine write_text (tt, x1, y1, clr)
use glib
character tt(*)
integer clr
call scrnxy(x1,y1,ix,iy)
call settextcolor(clr)
call outtext(tt,0,ix,iy)
end
subroutine graphicsmode(maxx, maxy)
use glib
call initgraphic(maxx, maxy);
end
subroutine closegrapic()
use glib
call exitgrapic()
end
4、编译和链接
使用gfortran编译和链接f90文件及dll,生成可执行文件的命令如下:
gfortran.exe -w -g .\test.f90 .\graphic.dll -o Test.exe
注意,在编译主文件前,先要编译库文件glib.f90
五、配置VScode的编译task
1、vscode中的终端默认执行工具,要选用Command Prompt。非要用PowerShell的话,命令及过程要做相应的修改。
2、编译工具的路径可以在系统的环境变量path中设置,也可以在Task中设置,本文采用在task中设置环境变量的方式。
3、编写task.json
{
// See https://go.microsoft.com/fwlink/?LinkId=733558
// for the documentation about the tasks.json format
"version": "2.0.0",
"options": {
"cwd": "${workspaceRoot}",
"env": {
"Include":"D:\\Soft\\easyx4mingw\\include",
"Toolpath":"D:\\Soft\\easyx4mingw\\lib64",
"PATH":"${env:PATH};D:\\Soft\\mingw64-seh\\bin"
}
},
"tasks": [
{
"label": "Compile",
"type": "shell",
"command": "gfortran.exe",
"args": [
"-w",
"-g",
".\\test.f90",
".\\graphic.dll",
"-o",
"Test.exe"
],
"group": "build",
"dependsOn":"Compile3"
},
{
"label": "Compile1",
"type": "shell",
"command": "g++.exe",
"args": [
"-c",
".\\Graphic.cpp",
"-I",
"${Include}"
],
"group": "build",
},
{
"label": "Compile2",
"type": "shell",
"command": "g++.exe",
"args": [
"-shared",
"-o",
".\\graphic.dll",
".\\Graphic.o",
"${Toolpath}\\libeasyx.a"
],
"group": "build",
"dependsOn":"Compile1"
},
{
"label": "Compile3",
"type": "shell",
"command": "gfrotran.exe",
"args": [
"-c",
".\\glib.f90"
],
"group": "build",
"dependsOn":"Compile2"
},
]
}
4、这样就可以在vscode中自动完成整个编译过程了。