文章目录
- 问题来源
- 我的问题
- 头文件、库文件和动态链接库
- 头文件.h
- 库文件.lib
- 动态链接库.dll
- 三者的关系
-
- 初识 Fortran 预处理
-
- Fortran中function简单使用
- 声明interface调用function
- 声明function
- 结论
- 附录
-
问题来源
课题需要用Ansys做拓扑优化计算,有限元计算部分可以用APDL直接进行,而优化部分需要从ANSYS提取结构刚度矩阵等数据进行计算。ANSYS给出的是数据接口是用Fortran写的,因此课题组一直都是用APDL + Fortran进行计算。
之前用的环境是vs 2005 + ivf,有点太老了用着不太舒服所以折腾了快一个礼拜搞了vs2017 + Intel 2019全家桶和VSCode + gfortran + lapack两套开发环境。(为了方便实验室的师弟师妹,已经做了两套教程,后面陆续会补上。这不是重点)
在折腾的过程中遇到了一点关于库函数的问题问题,让我对自己已有的对程序库函数还有头文件的理解产生了怀疑,因此专门去网上查了头文件(.h)、库文件(.lib)、和动态链接库文件(.dll)的关系。
我的问题
上网翻了一些资料,发现我对对头文件、库函数和动态连接库的理解没有大问题。理清思路之后,发现困扰我的其实是下面两个问题。
- Fortran中的对包含文件 include(头文件)的预处理
- Fortran中自定义函数的(声明)使用方式
为了防止以后时间长了又忘记了,将查阅内容总结如下:
- 头文件、库文件和动态链接库
- 初识 Fortran 预处理
- Fortran中function的简单使用
头文件、库文件和动态链接库
参考了博客园和CSDN的这两篇帖子。对每种文件依次回答如下问题:这个文件
头文件.h
头文件作为一种包含功能函数、数据接口声明的载体文件,主要用于保存 函数的声明。.h头文件是 编译时必须的。C\C++标准库头文件一般都存放在 /include目录下。
库文件.lib
lib库是一种可执行代码的二进制形式,是别人已经写好、适用成熟、在遵守相关协议下可以复用被操作系统载入内存中执行的代码。 lib库文件存放 函数的定义(实现),是 链接时需要的。库文件一般存放在 /lib文件下。
lib库文件分为静态库和动态链接库(.dll)的导入库。
动态链接库.dll
dll库文件存放的是 函数可执行代码,一般在 /bin和 /lib文件夹下。动态库dll是程序 运行时需要的。
三者的关系
简单来说,库文件通过头文件向外导出接口,用户通过头文件找到库文件中需要的函数实现代码进行链接至程序当中。不理解不用纠结,先往下看。
静态链接
静态链接用到的lib库文件叫做 静态链接库。
简而言之,静态连接就是头文件告诉编译器要用到的函数是什么样子,然后编译器去lib文件里将程序运行时用到的指令和数据找出来,并将它们从lib中复制出来全部打包到可执行程序exe中。
运行程序时将它们提取出来装入内存中,使得程序可以顺利运行,因此程序运行的时候不再需要其它的库文件。这种方式虽然简单但是粗暴,不仅使得exe程序很臃肿而且极大程度的浪费内存空间。
动态链接
动态链接用到的lib库文件叫做 动态链接库导入库,dll库叫做 动态链接库。
针对静态链接的缺点,动态链接将exe运行时需要的数据和指令的具体内容存在dll中,exe本身并不储存这些数据。在运行的时候,exe再将它们从dll中提取出来,这样exe程序就不会很臃肿。
虽然h头文件告诉了程序要用的函数是什么样子,但是程序怎么直到到底在哪个dll中找到所需的函数指令和数据呢?这就是导入库lib的作用了。
lib将接口操作和数据(用什么方法,从哪个dll库,(库中)什么地方调用所需函数和数据)告诉编译器并将它们放在exe中。程序运行时找到对应的dll库,将所需的内容提取出来。因此IDE设置时附加依赖项的是lib不是dll,dll是动态链接程序运行时必须的。
不同的程序可以从同一个dll中提取所需指令和数据,因此dll也叫共享库。对于大型项目,动态链接可以极大的节约空间,减小版本迭代时发布程序的体量。
初识 Fortran 预处理
这一节主要参考了这篇博客,摘录原文如下:
预处理在Fortran语法中并未规定,但绝大多数编译器做了相应扩充。本文介绍了部分编译器环境下的Fortran预处理语句,例如宏定义、包含语句、条件编译等。
预处理,是编译器在 编译之前,对源代码进行的一些“替换”、“选择” 等操作。它对于代码的宏观控制、维护、跨平台等都有很好的作用。
> 注意,预处理只是简单的对源代码文件进行替换,与 Fortran 的语法并没有直接关系。预处理的结果,才是真正的 Fortran 源代码,并交给编译器编译成二进制的目标代码。这个过程大概如下图:
然而,Fortran 语法中,并没有规定任何预处理的内容。因此,一部分编译器扩充了自己的预处理语句,也有一些编译器使用了现成的 C 语言的预处理器,于是继承了 C 语言预处理的写法。
本文会介绍一部分的预处理语句,但并不能保证他们能在读者的编译器上使用。请读者 阅读自己使用的编译器帮助文档,以便了解该编译器支持的预处理语句和使用方法。
包含文件 include
包含文件被大量应用在 C 语言头文件中。但 Fortran 本身并不需要子程序的原型,函数也使用(interface接口)而不是原型。module 用 mod 文件实现接口。所以,Fortran 并不需要头文件。
在较老的代码中,由于大量使用了 common 共享数据。因此,常常把 common 里的数据定义放入 include 文件。
包含文件其实非常简单,它完全等效于打开被包含文件,全选,复制,然后粘贴在 include 语句处。它只是一个预处理的语句,并不参与语法的任何编译,也没有 module 或其他语法那样复杂的逻辑关系。
被包含文件(如 inc.h)只是简单的用文件内容替换 include 语句,它可以是任何文件名和扩展名(如 .h 或 .inc 或 .par 或任何扩展名),只要它实际上是书写有 fortran 代码的文本文件。
此外,如果包含文件(如name.f90)是自由格式,那么被包含文件(如 inc.h)也必须是自由格式。如果一个是固定格式,另一个也必须是固定格式。
需要注意的是,由于被包含文件(如 inc.h)已经被替换到 name.F90 文件中。因此,它不能再被编译、参与链接。所以,它不必,也不能出现在工程、解决方案中。这是区别于“多文件编译连接”的。
Fortran中function简单使用
Fortran中function、subroutine、 interface和module的简单使用主要参考了这篇CSDN博客。原文内容可以直接点进去看,这里只总结一下我的问题。
声明interface调用function
我在参考Intel MKL库调用的例子时,有如下的程序开头:
! vcMul Example Program Text
!*******************************************************
include "_rms.fi"
program MKL_VML_TEST
include "mkl_vml.f90"
...
其中"_rms.fi"中定义了程序program MKL_VML_TEST
中调用的sub函数,"mkl_vml.f90"在MKL的include文件夹下,里面定义了MKL_VML函数的使用接口。根据上一节对于Fortran对于include文件的处理,相当于在代码里定义了sub子函数和function的接口interface。这个程序例子能够正常运行,也验证了与前面的叙述正确性。
声明function
当我参考另一个MKL库的调用例子ZDOTU_MAIN
时,我发现这个例子里并没有关于MKL库函数的接口声明,而是只用external ZDOTU
和complex*16 ZDOTU
声明了MKL函数ZDOTU
是外部函数和它的返回数据类型。
! Z D O T U Example Program Text
!*****************************************************
program ZDOTU_MAIN
*
integer n, incx, incy
integer xmax, ymax
parameter (xmax=20, ymax=20)
complex*16 x(xmax), y(ymax), res
integer i
* Intrinsic Functions
intrinsic abs
* External Subroutines
external ZDOTU, PrintVectorZ
complex*16 ZDOTU
...
这个例子我没有成功运行,但是我仿照它的格式写了一个测试函数program testblas
:
program testblas
implicit none
!external sdot
real sdot !声明BLAS库函数 实数向量内积
real::a(2)=(/1,1/),c(2),b(2)=(/2,3/),d
call scopy(2,a,1,c,1) !调用BLAS库scopy函数,讲向量a复制给c
print*,"scopy函数"
print*,"a"
print*,a
print*,"c"
print*,c
print*,"sdot函数"
d = sdot(2,a,1,b,1)
print*,"d"
print*,d
end program testblas
程序调用BLAS库的实数向量内积sdot
函数和实数向量复制scopy
函数进行向量运算,程序运行结果如下:
这说明Fortran程序调用funtion函数,可以不用声明函数原型(定义接口,说明function的参数类型及返回数据类型),只在程序开头声明函数返回类型即可。通过实验,将program testblas
中前三行替换以下两种种写法也可以通过编译。
implicit none
external,real sdot !声明BLAS库函数 实数向量内积
!implicit none
external sdot !声明BLAS库函数 实数向量内积
结论
结合在网上找的资料,最后得出结论:
1. 可以通过(1)定义interface调用function函数。
2. 也可以声明‘type functionname’
的方式,即以function函数的返回数据类型 + 函数名声明自定义function函数并在程序中调用。
3. 通过‘type functionname’
声明function函数时,建议使用写法a的形式。implicit none
保证没有隐式类型,external
说明是外部定义函数避免混淆(比如把函数当初变量)。
附录
为了方便以后验证,这里给出program MKL_VML_TEST
源码。
vcmul.f
! Content:
! vcMul Example Program Text
!*******************************************************************************
include "_rms.fi"
program MKL_VML_TEST
include "mkl_vml.f90"
real(kind=4) :: srelerr
real(kind=8) :: drelerr
real(kind=4) :: crelerr
real(kind=8) :: zrelerr
complex(kind=4) cA(10)
complex(kind=4) cB(10)
complex(kind=4) cBha0(10)
complex(kind=4) cBha1(10)
complex(kind=4) cBha2(10)
complex(kind=4) cBla1(10)
complex(kind=4) cBla2(10)
complex(kind=4) cBep1(10)
complex(kind=4) cBep2(10)
real(kind=4) CurRMS,MaxRMS
integer(kind=8) mode
integer tmode
integer i, vec_len
vec_len=10
MaxRMS=0.0
cA( 1)=(-100.0000,100.0000)
cA( 2)=(-77.7777,77.7777)
cA( 3)=(-55.5555,55.5555)
cA( 4)=(-33.3333,33.3333)
cA( 5)=(-11.1111,11.1111)
cA( 6)=(11.1111,-11.1111)
cA( 7)=(33.3333,-33.3333)
cA( 8)=(55.5555,-55.5555)
cA( 9)=(77.7777,-77.7777)
cA(10)=(100.0000,-100.0000)
cB( 1)=(0.0000000000000000e+000,-2.0000000000000000e+004)
cB( 2)=(0.0000000000000000e+000,-1.2098741959948209e+004)
cB( 3)=(0.0000000000000000e+000,-6.1728271672816772e+003)
cB( 4)=(0.0000000000000000e+000,-2.2222179836717260e+003)
cB( 5)=(0.0000000000000000e+000,-2.4691309516836372e+002)
cB( 6)=(0.0000000000000000e+000,-2.4691309516836372e+002)
cB( 7)=(0.0000000000000000e+000,-2.2222179836717260e+003)
cB( 8)=(0.0000000000000000e+000,-6.1728271672816772e+003)
cB( 9)=(0.0000000000000000e+000,-1.2098741959948209e+004)
cB(10)=(0.0000000000000000e+000,-2.0000000000000000e+004)
call VCMUL(vec_len,cA,cA,cBha0)
mode=VML_EP
call VMCMUL(vec_len,cA,cA,cBep1,mode)
tmode=VML_EP
tmode=VMLSETMODE(tmode)
call VCMUL(vec_len,cA,cA,cBep2)
mode=VML_LA
call VMCMUL(vec_len,cA,cA,cBla1,mode)
tmode=VML_LA
tmode=VMLSETMODE(tmode)
call VCMUL(vec_len,cA,cA,cBla2)
mode=VML_HA
call VMCMUL(vec_len,cA,cA,cBha1,mode)
tmode=VML_HA
tmode=VMLSETMODE(tmode)
call VCMUL(vec_len,cA,cA,cBha2)
do i=1,10
if(cBha0(i) .ne. cBha1(i)) then
print *,"Error! Difference between VCMUL and"
print *," VMCMUL in VML_HA mode detected"
stop 1
endif
if(cBha1(i) .ne. cBha2(i)) then
print *,"Error! Difference between VCMUL and"
print *," VMCMUL in VML_HA mode detected"
stop 1
endif
if(cBla1(i) .ne. cBla2(i)) then
print *,"Error! Difference between VCMUL and"
print *," VMCMUL in VML_LA mode detected"
stop 1
endif
if(cBep1(i) .ne. cBep2(i)) then
print *,"Error! Difference between VCMUL and"
print *," VMCMUL in VML_EP mode detected"
stop 1
endif
end do
print *,"vcMul test/example program"
print *,""
print *," Arguments ", &
& " vcMul"
print *,"======================================================", &
& "========================"
do i=1,vec_len
print 10,cA(i),cA(i)," ",cBha0(i)
CurRMS=crelerr(cB(i),cBha0(i))
if(CurRMS>MaxRMS) MaxRMS=CurRMS
end do
print *,""
if(MaxRMS>=1e-5) then
print 11,"Error! Relative accuracy is ",MaxRMS
stop 1
else
print 11,"Relative accuracy is ",MaxRMS
endif
10 format(E12.3,E12.3,E12.3,E12.3,A5,E12.3,E12.3)
11 format(A,F25.16)
end
_rms.fi
! Content:
! routines for relative error calculation
!*******************************************************************************
real(kind=4) function sfabs(a)
real(kind=4) :: a
if(a>=0.0) then
sfabs=a
else
sfabs=-1.0*a
endif
end function
real(kind=8) function dfabs(a)
real(kind=8) :: a
if(a>=0.0) then
dfabs=a
else
dfabs=-1.0d+000*a
endif
if(dfabs<1e-15) then
dfabs=1e-15
endif
end function
real(kind=4) function srelerr(a,b)
real(kind=4) :: a
real(kind=4) :: b
real(kind=4) :: sfabs
srelerr=sfabs((a-b)/a)
end function
real(kind=8) function drelerr(a,b)
real(kind=8) :: a
real(kind=8) :: b
real(kind=8) :: dfabs
drelerr=dfabs((a-b)/a)
end function
real(kind=4) function crelerr(a,b)
complex(kind=4) :: a
complex(kind=4) :: b
real(kind=4) :: sfabs
real(kind=4) re
real(kind=4) im
re=sfabs((REAL(a)-REAL(b))/REAL(a))
im=sfabs((AIMAG(a)-AIMAG(b))/AIMAG(a))
if(re>=im) then
crelerr=re
else
crelerr=im
endif
end function
real(kind=8) function zrelerr(a,b)
complex(kind=8) :: a
complex(kind=8) :: b
real(kind=8) :: dfabs
real(kind=8) re
real(kind=8) im
re=dfabs((REAL(a)-REAL(b))/REAL(a))
im=dfabs((AIMAG(a)-AIMAG(b))/AIMAG(a))
if(re>=im) then
zrelerr=re
else
zrelerr=im
endif
end function
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)