近期尝试将GROOPS代码移植到Visual Studio平台上,遇到了很多问题,踩了许多坑,故写下这篇文章供有需求的人参考。内容主要分为四个部分:
1、准备工作
准备软件:VS2019, CMake、One API Toolkit。
(a)为什么选择VS2019而不是最新的VS2022?因为2022版还没有完整的配套One API Toolkit。
(b)CMake下载地址:https://cmake.org/download/ (我下载的是3.24 release版本)
(c)One API Toolkit下载地址:https://www.intel.com/content/www/us/en/developer/tools/oneapi/toolkits.html#gs.67f2ch
安装参考:https://zhuanlan.zhihu.com/p/411167136
2、移植
移植工作比较简单:
(a)首先创建一个自己的VS2019空项目(×64),并创建source文件夹。如果需要调试的话建议生成Debug程序,如果发布建议生成Release来加快运行速度。
(b)后将GROOPS的source文件目录下自己需要的代码直接copy到VS2019工程目录下的source文件夹下,这里十分不建议改动GROOPS源码(.h .cpp)的相对路径,例如以GROOPS的source文件所在的路径为起点,archive.cpp和archive.h相对于source的路径为inputOoutput,要拷贝这两个代码需要将inputOutputarchive.cpp和archive.h都拷贝到自己的工程目录下,如果你改动了这一层关系,那你就需要在VS工程中狂改路径。
(c)进入VS工程,将需要的cpp与h文件分别组织在源文件和头文件中(下图红色方框内以(b)中提到的为例)。
(d)观察右侧”解决方案资源管理器“,右键你工程的名字->属性->C/C++ 常规->附加包含目录,把你工程source文件路劲拷贝进去,这一步是为了让VS从source开始寻找头文件与源文件,避免了从groops上直接移植的代码出现头文件引用错误的问题。
3、外部依赖库
外部依赖库这一部分主要讲lapack与blas这两个库的引用,这两个库在VS2019中使用需要自己来编译,官方编译流程:https://icl.utk.edu/lapack-for-windows/lapack/index.html#running(看Build Instructions for LAPACK 3.5.0 for Windows with Visual Studio这一部分) ,不喜欢看英文?那就往下看:
(a)首先下载lapack与blas的fortran源码并解压,下载地址:
http://www.netlib.org/lapack/#_lapack_version_3_10_1_2
(b)打开CMake的GUI界面,依次输入CMakeList.txt所在目录以及构建结果存放目录。
(c)点击左下角Configure按钮,在弹出的窗口选择Visual studio 2019,并在下方勾选Specify native compilers,在弹出的窗口中的fortran选项中找到icl.exe(在第一步安装的One API Base Toolkit目录下,我的在E:\one_API Base Toolkit\compiler\2022.1.0\windows\bin\intel64_ia32下,使用的是64位fortran编译器)
点击Finish后点击Config,第一次Config肯能会出现很多标红内容,再点即可,直到标红部分消失后,点击Generate即可在输出目录下获得一个sln工程,进入该工程,右键"ALL_BUILD"->生成,获得lapack.lib以及blas.lib静态库文件,即完成生成。
(d)下面结合groops代码将lapack于lbas库引入VS2019。创建一个新的fortran静态库工程:VS2019->static library(fortran),我的工程叫做external。将lapack.lib以及blas.lib放置在工程目录下,在将groops->source->blasWrapper.f 以及 lapackWrapper.f拷贝至lapack.lib以及blas.lib同一目录下并添加在sln中的源文件中。
右键external->属性,在附加依赖库中加入lapack.lib blas.lib,在附加库目录添加lapack.lib blas.lib所在的文件目录,确定。右键external->生成,可得到external.lib。
(e)打开要以移植的VS工程,添加头文件,并把lapack.lib blas.lib external.lib全部拷贝到工程的external目录下。
并右键工程,属性->链接器->常规->附加库目录,将lapack.lib blas.lib external.lib所在路径拷贝进去,并在属性->链接器>输入->附加依赖项写入lapack.lib blas.lib external.lib。以防万一,右键工程->添加,把lapack.lib blas.lib external.lib添加进去。除此之外,进入fortran.h,注释掉#define FORTRANCALL(lower,upper) lower ## _,解除#define FORTRANCALL(lower,upper) upper的注释,如此对项目进行编译就不会出现外部库引用的问题了。
4、写在最后
关于gfortran于IVF(One API Base Toolkit)。经过无数次失败、翻阅材料与重新尝试,笔者发现gfortran与IVF编译的fortran码有极大不同。gfortran是GNU下的编译工具,一般用于linux,而IVF是intel推出的编译工具,一般结合vs使用,下边有一个fortran代码:
subroutine wrapdgeev(jobvl,jobvr,n,A,ldA,WR,WI,VL,ldVL,VR,ldVR,work,lwork,info)
external dgeev
integer jobvl, jobvr
character jobvlc, jobvrc
if(jobvl.eq.0) then
jobvlc = 'N'
else
jobvlc = 'V'
endif
if(jobvr.eq.0) then
jobvrc = 'N'
else
jobvrc = 'V'
endif
call dgeev(jobvlc,jobvrc,n,A,ldA,WR,WI,VL,ldVL,VR,ldVR,work,lwork,info)
end
函数名叫wrapdgeev对吧?gfortran编译后,它的函数名会变为wrapdgeev_, 而IVF编译后它的函数名将会变为WRAPDGEEV,这个差异如果没有注意,那么你在C++程序中引用该函数时就会出错”LINK2019 无法解析的外部符号 …“。因为C语言区分大小写,当它调用fortran库的函数时:
extern "C"
{
void wrapdgeev (const F77Int &jobvl, const F77Int &jobvr, const F77Int &n, F77Double A[], const F77Int &ldA, F77Double WR[], F77Double WI[], F77Double VL[], const F77Int &ldVL, F77Double VR[],
}
它会按照你声明的这个名字去寻找fortran库中的函数,如果fortran函数编译出来是大写,那么C++就无法识别。fortran是不区分大小写的,所以在fortran程序中调用fortran外部库函数不需要注意这些。
关于IVF的编译问题,我推荐网站:
https://community.intel.com/t5/Intel-Fortran-Compiler/bd-p/fortran-compiler/topic/624981