C++与Fortran交叉编程
本文主要内容为C++与Fortran的交叉编程,介绍了在Visual Studio中, 以动态库作为中介,Fortran程序与C++程序的数据相互传递以及函数相互调用。Fortran 实际上是没有与C++的交互接口,只有与C的交互接口。主要使用Fortran提供的iso_c_binding接口。C++特有的class和重载机制,需要转化为C代码形式,以完成对应的接口。
1. 环境配置
- Visual Studio 2019:C++桌面开发模块
- OneAPI:
2. Fortran 调用C++
在Fortran中调用C++的程序,首先将C++程序生成为一个动态库,然后在Fortran中调用C++的动态库。
2.1 生成C++的动态库
打开VS2019程序,新建C++ Dynamic-Link Library程序 ,命名为cdll。在新建的dllmain.cpp中添加代码:
// dllmain.cpp : Defines the entry point for the DLL application.
#include "pch.h"
BOOL APIENTRY DllMain( HMODULE hModule,
DWORD ul_reason_for_call,
LPVOID lpReserved
)
{
switch (ul_reason_for_call)
{
case DLL_PROCESS_ATTACH:
case DLL_THREAD_ATTACH:
case DLL_THREAD_DETACH:
case DLL_PROCESS_DETACH:
break;
}
return TRUE;
}
//添加代码
#define EXPORT __declspec(dllexport) //可以被外部程序使用
extern "C" { //以C的方式编译
EXPORT int plus(int a, int b);
}
int plus(int a, int b)
{
return a + b;
}
点击Build Solution,可以看到Debug文件夹中生成了 cdll.dll和cdll.lib
2.2 调用C++的动态库
新建Fortran90 Main Program Code程序,准备调用C++的动态库。对于如何使用lib和dll的方法,在VS中有很多方法,这里介绍一种:将lib和dll放置在前面生成的Main Program Code程序的根目录中,即与.f90文件同一目录中。右击Fortran程序的Resource File 添加cdll.lib.
编写Fortran代码如下:
program FTest
implicit none
!增加函数接口
interface
function plus(a,b) bind (c) !注意这里的plus名称要与C中函数名称一致
use iso_c_binding
integer(c_int),value::a
integer(c_int),value::b
integer(c_int)::plus
end function
end interface
!输出测试
print *, plus(2,3)
end program FTest
2.3 案例
2.3.1 函数名
dll文件:
#define EXPORT __declspec(dllexport) //可以被外部程序使用
extern "C" { //以C的方式编译
EXPORT int plus(int a, int b);
}
int plus(int a, int b)
{
return a + b;
}
Fortran文件
program FTest
implicit none
!增加函数接口
interface
function plus(a,b) bind (c) !注意这里的plus名称要与C中函数名称一致
use iso_c_binding
integer(c_int),value::a
integer(c_int),value::b
integer(c_int)::plus
end function
function plusTest(a,b) bind (c,name="plus") !plusTest名称与C中名称不一致,但可以用name属性绑定
use iso_c_binding
integer(c_int),value::a
integer(c_int),value::b
integer(c_int)::plusTest
end function
end interface
!输出测试
print *, plus(2,3)
print *, plusTest(4,5)
end program FTest
2.3.2 不同类型数据
iso_c_binding接口提供各种类型数据的接口,C++的基本数据类型在iso_c_binding模块都有对应的数据类型。一般而言都是通过Fortran主程序调用C++的函数,而达到数据传输的目的:即Fortran调用C++中的函数,也是C++函数使用Fortran的实参。
dll文件
#include<iostream>
struct dataClass {
int i;
double j;
double* p;
int length;
};
#define EXPORT __declspec(dllexport) //可以被外部程序使用
extern "C" { //以C的方式编译
EXPORT void test(dataClass d);
}
void test(dataClass d)
{
std::cout << d.i << " " << d.j << " " << std::endl;
for (int index = 0; index < d.length; index++)
{
std::cout << *(d.p+ index) << " ";
}
std::cout << std::endl;
}
Fortran文件
module link_to_c
use iso_c_binding
type,bind(c)::dataClass !与C中dataClass一一对应
integer(c_int)::a
real(c_double)::b
type(c_ptr)::ptr !动态数组
integer(c_int)::length
end type dataClass
end module
program FTest
use link_to_c
implicit none
interface
subroutine test(d) bind(c)
use iso_c_binding
use link_to_c
type(dataClass),value::d
end subroutine
end interface
type(dataClass)::g
real(8),allocatable::a(:)
integer::i
g%a=1
g%b=2.0
!dynamic array
allocate(a(8))
do i=1,8
a(i)=2*i+1.0
end do
g%ptr=c_loc(a)
g%length=8
call test(g)
end program FTest
2.3.3 函数传值与传引用
dll文件:
#define EXPORT __declspec(dllexport)
extern "C" {
EXPORT int plus(int a, int b); //pass by value
EXPORT int plus2(int& a, int& b); //pass by reference
}
int plus(int a, int b)
{
a++;
b++;
return a + b;
}
int plus2(int& a, int& b)
{
a++;
b++;
return a + b;
}
Fortran文件:
program FTest
implicit none
integer::i,j
!增加函数接口
interface
function plus(a,b) bind (c)
use iso_c_binding
integer(c_int),value::a !添加value 代表传值
integer(c_int),value::b
integer(c_int)::plus
end function
function plus2(a,b) bind (c)
use iso_c_binding
integer(c_int)::a !去掉value,fortran默认使用传引用
integer(c_int)::b
integer(c_int)::plus2
end function
end interface
i=2
j=3
!输出测试
print *, plus(i,j),i,j
print *, plus2(i,j),i,j
end program FTest
输出结果为:
7 2 3
7 3 4
3.C++调用Fortran
在C++中调用Fortran的程序,首先将Fortran程序生成为一个动态库,然后在C++中调用Fortran的动态库。
3.1 生成Fortran的动态库
打开VS2019程序,新建Fortran Dynamic-Link Library程序 ,命名为fdll。在新建的fdll.f90中添加代码:
subroutine sqrtd(a,b,c) bind(c,name="sqrtT") !bind name
! Expose subroutine sqrtd to user
!DEC$ ATTRIBUTES DLLEXPORT::sqrtd
use iso_c_binding
integer(c_int)::a[value] ! pass by value
real(c_float)::b[value] ! pass by value
real(c_double)::c[reference] ! pass by reference
c=sqrt(a*a+b*b)
end subroutine sqrtd
build完毕后生成fdll.lib和fdll.dll
3.2 调用Fortran的动态库
新建C++ Console App程序,命名为ctest。将lib和dll放置在ctest.cpp的根目录中。右击Fortran程序的Resource File 添加fdll.lib, 如下图所示:
编写C++代码如下:
#include <iostream>
extern "C" { void sqrtT(int, float, double&); } //pass by value,value,reference
int main()
{
int a = 30;
float b = 40;
double c = 0;
std::cout << a << " " << b << " " << c << std::endl;
sqrtT(a, b, c);
std::cout << a << " " << b << " " << c << std::endl;
}
3.3 案例
Tips: 这一小节中,涉及到在fortran和C++程序中同时使用控制台终端输出结果。一般而言,交叉编程是不建议共享I/O输出的。在fortran主程序中调用C++的dll没问题,但是在C++中调用Fortran的dll出现问题。经过尝试,这里需要在按照oneAPI的目录下复制部分相关dll,以完成相关配置
从oneAPI目录:oneAPI\compiler\2021.3.0\windows\redist\ia32_win\compiler中,复制libifcoremdd.dll和libmmd.dll两个动态库到VS生成的exe根目录下。
dll文件:
module link_to_c
use iso_c_binding
type,bind(c)::dataStruct
integer(c_int)::a
real(c_double)::c
type(c_ptr)::ptr
integer(c_int)::length
end type
end module link_to_c
subroutine TransferData(g) bind(c,name="TransferData") !bind name
! Expose subroutine sqrtd to user
!DEC$ ATTRIBUTES DLLEXPORT::TransferData
use iso_c_binding
use link_to_c
type(dataStruct)::g[reference]
real(c_double),pointer::s(:)
write(*,*) g%a,g%c
allocate(s(g%length))
call c_f_pointer(g%ptr,s,[g%length]) !将c_ptr转换为fortran的pointer
do i=1,g%length
write(*,*) s(i)
end do
end subroutine TransferData
cpp文件:
#include <iostream>
extern "C" {
struct dataStruct
{
int a;
double c;
double* ptr; //动态数组测试
int length;
};
void TransferData(dataStruct&);
}
int main()
{
dataStruct ds;
double a[3] = { 10,20,30 };
ds.ptr = a;
ds.length = 3;
ds.a = 2;
ds.c = 3.0;
TransferData(ds);
}