此文章鸣谢
@Dragon @竹叶知风飘 排名不分先后
运行环境
C/C++编译器:MSVS2019(VC9),对VS2008也适用。
Fortran编译器:Intel Visual Fortran Compiler 11.0,IVF的其它版本也适用。
C调用Fortran函数
一、创建FORTRAN DLL工程,生成forsubs.dll文件供调用。
用IVF建立一个Dynamic-link library项目,比如项目名为forsubs.dll。在项目中新建f90代码文件,比如文件名为forsubs.f90。在forsubs.f90中写下如下代码:
用IVF建立一个Dynamic-link library项目,比如项目名为forsubs.dll。在项目中新建f90代码文件,比如文件名为forsubs.f90。在forsubs.f90中写下如下代码:
! forsubs.f90
!
! FUNCTIONS/SUBROUTINES exported from FORSUBS.dll:
! FORSUBS - subroutine
!
INTEGER*4 FUNCTION Fact (n)
!DEC$ ATTRIBUTES DLLEXPORT::Fact
INTEGER*4 n [VALUE]
INTEGER*4 i, amt
amt = 1
DO i = 1, n
amt = amt * i
END DO
Fact = amt
write(*,*)"Mixed calls succeed!"
END
SUBROUTINE Pythagoras (a, b, c)
!DEC$ ATTRIBUTES DLLEXPORT::Pythagoras
REAL*4 a [VALUE]
REAL*4 b [VALUE]
REAL*4 c [REFERENCE]
c = SQRT (a * a + b * b)
END
编译后,你会找到FortranDLL.lib 和FortranDLL.dll,它们是后面调用所需要的。因
为建立的是动态库项目,所以不会有.exe 生成。二、创建win32 console application,调用forsubs.dll。
用MSVS2010建立一个C++的Win32控制台应用程序,当然MFC的也可以。代码如下:
#include "stdafx.h"
#include <stdio.h>
#include <iostream>
#include <windows.h>
using namespace std;
int main()
{
//声明调用约定
typedef int (__cdecl * FACT)(int n);
typedef void (__cdecl * PYTHAGORAS)(float a, float b, float *c);
//加载动态库文件
HINSTANCE hLibrary=LoadLibrary(L"forsubs.dll");
if(hLibrary==NULL)
{
cout<<"can't find the dll file"<<endl;
return -1;
}
//获得Fortran导出函数FACT的地址
FACT fact=(FACT)GetProcAddress(hLibrary,"FACT");
if(fact==NULL)
{
cout<<"can't find the function file."<<endl;
return -2;
}
//获得Fortran导出函数PYTHAGORAS的地址
PYTHAGORAS pythagoras=(PYTHAGORAS)GetProcAddress(hLibrary,"PYTHAGORAS");
if(pythagoras==NULL)
{
cout<<"can't find the function file."<<endl;
return -2;
}
float c;
printf("Factorial of 7 is: %d\n", fact(7));
pythagoras (30, 40, &c);
printf("Hypotenuse if sides 30, 40 is: %f\n", c);
FreeLibrary(hLibrary); //卸载动态库文件
return 0;
}
将之前生成的forsubs.lib连同路径一起添加在:项目->属性->属性配置->连接器->输入:附加依赖项里。如果它在当前目录下,就填forsubs.lib就可以了。
将之前生成的forsubs.dll放在当前目录下,在执行时程序要连接它的。当然,也可以把它放在任意目录下,然后通过系统的“环境变量”中的Path来指定这个目录。
编译生成,运行C++即可
Fortran调用C动态链接库(静态调用及动态调用)
1、静态调用部分(需要lib,dll)
首先先说明一下题目把,为什么Fortran调用的是C动态库而不是C++?
C语言函数库的调用和C++函数库的调用是不一样的,其不同主要是由于C++相较于C的特性造成的。
C语言中一个函数名只能代表一个函数,C++中由于函数重载的存在一个函数名并不能唯一确定一个函数。
因此在调用函数库时也就注定着C和C++必定有所区别。
而Fortran是基于C标准的,所以只能调用C的动态库。(如果必须用C++编译器编译的话,可以加入extern "C",确保特定函数使用C规则编译)
编译C动态链接库
1、首先在VS中新建动态链接库项目
2、添加.h,.cpp文件内容如下
xxxx.h文件
#include <stdio.h>
extern "C" __declspec(dllexport) void _cdecl write2file(char filename[],int a[],int n,int* val);
xxxx.cpp文件
#include "pch.h"
#include "xxxx.h"
void _cdecl write2file(char filename[], int a[], int n, int* val)
{
int i;
FILE* p = fopen(filename, "w");
*val = 0;
for (i=0;i<n;i++)
{
fprintf(p, "%d\n", a[i]);
*val += a[i];
a[i] = -a[i];
}
fclose(p);
}
这里要注意一下几点
1、extern "C"代表着以C语言的形式导出;
2、 __declspec(dllexport) 表示从DLL导出函数xxxx,与之相对应的是_declspec(dllimport)表示从dll导入到程序exe
3、堆栈管理规则
_stdcall:参数由右向左压入堆栈,堆栈由函数自己清理。
_cdecl:参数由右向左压入堆栈,堆栈由调用方清理。
msvc都是默认_stdcall的堆栈管理规则,Fortran是_cdecl,
因此在输出函数库时应显式设置它。
4、对于编译生成的.lib和.dll的作用这里做一下简单的介绍:
.lib是用来编译使用的,用来说明库里有什么东西去哪里找
.dll就是用来实现内容的。
在编译过程中,只需要知道去哪里找就够了,等到程序运行的时候才需要dll出来干活
因此,如果不想额外添加路径的话,只需要将.lib复制到项目根目录,.dll复制到可执行文件的同级目录之下就可以了。
5、Fortran调用C动态链接库
在项目->属性->链接器->输入->附加依赖项 中加入需要调用的lib文件的路径(这种办法把代码发给别人,会很麻烦的去配置文件)。
你也可以把需要的lib文件直接加入到Source Files文件当中,方法:Source Files右键添加->添加->现有项(推荐这一种方法)。
以下为Fortran代码
program f_all_c
use,intrinsic::iso_c_binding
implicit none
character(80) str
integer, parameter:: n=3
integer a(n), s
!函数接口
interface
subroutine write2file(filename,a,n,s) bind(c)
character filename(*)
integer,value:: n
integer a(n), s
end subroutine
end interface
a = [1,2,3]
str = "1.txt"// C_NULL_CHAR
call write2file(str,a,n,s)
write(*,*)s
write(*,*)a
end program
2、动态调用部分(仅需要dll)
1、创建Fortran的载入dll的模块
module LoadDllFile
use,intrinsic::ISO_C_BINDING
use kernel32
implicit none
type(c_funptr) ::fun_handle
integer(HANDLE)::dll_handle
contains
subroutine loadDll(dllName, dll_handle,stat)
implicit none
character(*), intent(in) :: dllName
integer(HANDLE), intent(out) :: dll_handle
logical, intent(out)::stat
stat = .false.
dll_handle = LoadLibrary(trim(dllName)//C_NULL_CHAR)
if(dll_handle/=NULL) stat = .true.
end subroutine loadDll
subroutine loadFunction(dll_handle, funName, fun_handle, stat)
implicit none
integer(HANDLE), intent(in) :: dll_handle
integer(C_INTPTR_T) :: f_handle
type(c_funptr) :: fun_handle
character(*),intent(in) ::funName
logical, intent(out) ::stat
stat = .false.
f_handle = GetProcAddress(hModule = dll_handle, lpProcName=trim(funName)//C_NULL_CHAR)
if(f_handle /= NULL) then
stat = .true.
fun_handle = TRANSFER(f_handle, C_NULL_FUNPTR)
end if
end subroutine loadFunction
end module LoadDllFile
2、创建Fortran主函数
program f_Call_c
use,intrinsic::iso_c_binding
use LoadDllFile
implicit none
character(80) str
integer, parameter:: n=3
integer a(n), s
!函数接口
interface
subroutine write2file(filename,a,n,s) bind(c)
character filename(*)
integer,value:: n
integer a(n), s
end subroutine
end interface
!DLL调用
procedure(write2file), pointer::p
logical::stat
call loadDll("task_0510", dll_handle, stat)
if(stat==.true.) then
call loadFunction(dll_handle, "write2file", fun_handle, stat)
else
print *, "NO_Dll"
end if
call c_f_procpointer(fun_handle, p)
a = [1,2,3]
str = "1.txt"// C_NULL_CHAR
call p(str,a,n,s)
write(*,*)s
write(*,*)a
end program
3、创建C动态库
xxxx.h
#include <stdio.h>
extern "C" __declspec(dllexport) void _cdecl write2file(char filename[],int a[],int n,int* val);
xxxx.app
#include "pch.h"
#include "xxxx.h"
void _cdecl write2file(char filename[], int a[], int n, int* val)
{
int i;
FILE* p = fopen(filename, "w");
*val = 0;
for (i=0;i<n;i++)
{
fprintf(p, "%d\n", a[i]);
*val += a[i];
a[i] = -a[i];
}
fclose(p);
}
4、把生成的C的dll拷贝Fortran程序根目录运行即可