制作简单的Fortran图形库,替代Qwin库

目录

前言

一、转换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中自动完成整个编译过程了。

  • 1
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值