1.VC++与Fortran的混合编程
目前VC和VF都是基于WINDOWS的IDE开发环境,并且在安装同一版本的Fortran时会自动集成到VS的开发环境,这样就给我们在进行调用Fortran时提供了三种不同的方法。
1.1 同一工程下包含不同语言的文件
集成后的编译器可以根据不同的扩展名选择编译方式,生成目标代码文件.OBJ,然后按照指定的调用方式进行链接,生成可执行文件.EXE。
在2中我们提到调用规则,在C语言中调用Fortran的函数或模块,必须在函数名前面冠以_stdcall关键字作为调用方式①,这样编译器会给我们处理包括参数传递顺序、参数传递方式、堆栈处理和命名修饰等问题。而在C++语言中,尽管在开发C++语言时考虑兼容C方式,即C++对函数的调用规则与C是一致的,但是C++在命名转换上与C语言是不一致的。参考以下对外部函数的声明:
1 C语言调用Fortran模块的声明:
extern void _stdcall SUBROUTINE(argType arg1,……);
extern float _stdcall FUNCTION(argType arg1,……);
2 C++语言调用Fortran模块的声明:
extern “C” { void _stdcall SUBROUTINE(argType arg1,……); }
extern “C” { float _stdcall FUNCTION(argType arg1,……); }
外部函数声明仅仅给调用过程提供函数的入口地置,目的是为了保证编译通过。
在链接生成可执行文件时,主调用过程根据函数名链接被调用模块,找到模块则可链接成功,有了调用规则和命名规则的修饰字进行转换,才能保证运行正常。
1.2 调用Fortran语言生成的dll模块:
如果为了调用在Fortran中已有的模块或者为了提高处理速度,可以考虑将已有程序或者算法用Fortran语言做成dll的模块,便于移植和修改,这样即使原dll模块中的算法做过修改,只要保证函数或子例行程序名字和入口参数不变,主调用过程无须进行重新修改或者编译。Dll文件的好处很多,感兴趣的读者可以参考相关计算机书籍。
Fortran程序制作dll模块的过程如下:
首先利用向导制作一个空的Visual Fortran dll项目,在生成的文件中可以看到用cDEC $ ATTRIBUTES DLLEXPORT 指令声明的函数,这个函数即为该dll输出的子程序,用户可以在外部程序中调用它来完成一定的功能。可以在一个dll文件中添加cDEC $ ATTRIBUTES DLLEXPORT指令声明多个函数、过程或要输出的数据。例如,
!在dll中声明输出函数 Fact
INTEGER*4 FUNCTION FACT(N)
!DEC$ ATTRIBUTES DLLEXPORT::FACT
INTEGER*4 N
……
END FUNCTION FACT
!在dll中声明子例行程序 ARRAYTEST
SUBROUTINE ARRAYTEST(ARR)
!DEC$ ATTRIBUTES DLLEXPORT::ARRAYTEST
REAL*4 ARR(3,7)
……
END SUBROUTINE ARRAYTEST
1.3 VC中调用IMSL数学库函数
这是混合编程比较实用的部分,笔者在使用过程中发现运用上面的改变调用规则和命名转换方法仍然无法在VC中直接调用VF的数学库,编译报错为:unresolved external call,即无法决议的外部调用。即使使用VC的编译指令#pragma comment (lib, “***.lib”)加入调用IMSL所需的三个lib文件,仍然存在未解决的外部调用错误,原因是内部调用过程不可知。
研究Compaq Visual Fortran的在线帮助可以发现,在Fortran程序中,要使用IMSL库,也需加入编译指令USE NUMERICAL_LIBRARIES来包含确切的头文件和库,这样就可以直接使用IMSL数学库中的函数。基于以上分析,我们可以定制dll,将要用到的数学函数包括到自己的模块中,然后在VC中使用,参考下面的例子:
REAL*4 FUNCTION FCBRT(A)
!Expose Function FCBRT to users of this DLL
!DEC$ ATTRIBUTES DLLEXPORT::FCBRT
USE numerical_libraries
REAL*4 A[VALUE]
!Variables
!Body of the Function
FCBRT=CBRT(A)
END FUNCTION FCBRT
注意输出函数名不要和IMSL库中的函数同名,否则生成dll时将报与库函数命名冲突!
当然用户在实际使用过程中为了简单的调用若干个数学函数,还要进行库函数的定制与打包,这是不大实际的。
以下提供一种简单的方法在VC中调用IMSL库函数,参考表一:
Static or DLL 多线程? 是否使用C调试库 使用的Fortran链接库存 使用的C链接库存
Static 否 否 dfor.lib libc.lib
Static 否 是 dfor.lib libcd.lib
Static 是 否 dformt.lib libcmt.lib
Static 是 是 dformt.lib libcmtd.lib
DLL 否 否 dfordll.lib(dforrt.dll) msvcrt.lib(msvcrt.dll)
DLL 否 是 dfordll.lib(dforrt.dll) msvcrtd.lib(msvcrtd.dll)
DLL 是 否 dformd.lib(dformd.dll) msvcrt.lib(mvcrt.dll)
DLL 是 是 dformd.lib(dformd.dll) msvcrtd.lib(msvcrtd.dll)
表一
在编程实践中,根据表一的调用依赖关系,笔者总结出了在VC工程中直接调用IMSL数学库的方法。第一,在控制台(console)应用程序中调用IMSL库函数,必须包含dfor.lib,dfconsol.lib,在MFC应用程序中调用IMSL库函数,则必须包含dformd.lib,当然上述两种情况的工程要想调用Fortran提供的数学库,如下三个库文件是基本的:
1 Imsl.lib IMSL静态库,包含了Fortran77 和 Fortran90 所有函数和子例行程序;
2 Imsls_err.lib IMSL错误处理库;
3 Imslmpistub.lib 基于多CPU的并行处理库(对于单CPU的PC机用户可省)
读者可以根据表一,结合在项目中Project菜单的Setting项中有关编译器参数对相应的库文件进行修改。
2 提供简单算例
在计算机图形学中关于曲线、曲面进行插值或拟合,将大量使用求解线性方程组。如在求B样条曲线插值问题中,要先进行反求控制顶点,然后根据B样条基求出进行插值,我们利用VC来完成界面,利用IMSL提供的数学函数完成计算。
例题:已知型值点序列Pi,i=0,1,2…,7:
P[0] P[1] P[2] P[3] P[4] P[5] P[6] P[7]
Abscissae x 8.125 8.400 9.000 9.485 9.600 9.959 10.166 10.200
Ordinates y 0.0774 0.099 0.28 0.6 0.708 1.20 1.80 2.177
求控制顶点序列。
反求B杨条曲线控制顶点的数学问题是:
D表示de Boor控制顶点,P已知型值点列和边界条件向量。
在VC中调用IMSL解线性方程组的函数LSASF/DLSASF求解,完整代码如下:
#include <iostream>
using namespace std;
#include <math.h>
#pragma comment (lib, "dfor")
#pragma comment (lib, "dfconsol")
#pragma comment (lib, "imsl")
#pragma comment (lib, "imsls_err")
//Solve a real symmetric system of linear equations with iterative refinement.
extern "C" { void _stdcall LSASF (int*, float*, int*, float*, float*); }
extern "C" { void _stdcall DLSASF (int*, double*, int*, double*, double*); }
int main(int argc, char* argv[])
{
int i,j;
int lda=8;
int n=8;
double A[8][8];
double Px[8]={ 6*8.125, 6*8.400, 6*9.000, 6*9.485, 6*9.600, 6*9.959, 6*10.166, 6*10.200 };
double Py[8]={ 6*0.0774, 6*0.099, 6*0.28, 6*0.6, 6*0.708, 6*1.20, 6*1.80, 6*2.177};
// 存放求得的解
double Dx[8],Dy[8];
for(i=0;i<8;i++)
{
for(j=0;j<8;j++)
{
if(i==j) A[i][j]=4.0;
else if(abs(i-j)==1) A[i][j]=1.0;
else A[i][j]=0.0;
}
}
A[0][0]=A[7][7]=5.0;
DLSASF(&n,&A[0][0],&lda,Px,Dx);
DLSASF(&n,&A[0][0],&lda,Py,Dy);
for(i=0;i<n;i++)
cout<<i<<"\t"<<Dx[i]<<"\t"<<Dy[i]<<endl;
return 0;
}
计算结果 Di, i=0,1,…,9
D[0]=D[1] D[2] D[3] D[4] D[5] D[6] P[7] D[8]=D[9]
Abscissae x 8.08523 8.32384 9.01941 9.59854 9.49644 10.0157 10.1948 10.201
Ordinates y 0.07852 0.07183 0.22819 0.69544 0.59008 1.19226 1.84088 2.24422
值得注意的是在声明外部函数时,Fortran 函数中的参数列表必须声明成对应C/C++参数类型的参考,即指针类型,数据类型对应关系参考表二:
Fortran Type INTEGER*1 INTEGER*2 INTEGER*4 REAL*4 REAL*8 CHRATER*1
C/C++ Type char short int, long float double unsigned char
表二
引入静态链接库或者动态链接库的方法有很多种,例程中采用的是预编译指令,读者可以参考相关编程资料。