VS2019中C++与Fortran的混合编程

C++与Fortran交叉编程

​ 本文主要内容为C++与Fortran的交叉编程,介绍了在Visual Studio中, 以动态库作为中介,Fortran程序与C++程序的数据相互传递以及函数相互调用。Fortran 实际上是没有与C++的交互接口,只有与C的交互接口。主要使用Fortran提供的iso_c_binding接口。C++特有的class和重载机制,需要转化为C代码形式,以完成对应的接口。

1. 环境配置

  1. Visual Studio 2019:C++桌面开发模块
  2. 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);
}

4.参考

  1. Mixed-language Programming
  2. Fortran wiki
评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值