IVF与VS结合的Fortran IDE,由于VS中的项目只能使用同一种语言来编程,所以如果要实现C/C++调用Fortran的功能,目前只有将Fortran代码编译成动态库或静态库的方式来实现:
在介绍代码之前我觉得应该先介绍一下我们在实现C/C++调用Fortran功能时经常会遇到的一些问题和解决方法,因为很多人已经知道了怎样编写调用代码,但是遇到了一些问题,这样就可以直接看下面的解决方法,而不必看后面的一大堆代码了:
问题一:
在编译调用的C/C++程序时出现了一大堆的连接错误信息,如下:
1>LIBCMTD.lib(dbgheap.obj) : error LNK2005: __CrtSetCheckCount already defined in MSVCRTD.lib(MSVCR90D.dll)
1>LIBCMTD.lib(dbghook.obj) : error LNK2005: __crt_debugger_hook already defined in MSVCRTD.lib(MSVCR90D.dll)
1>LIBCMTD.lib(setlocal.obj) : error LNK2005: __configthreadlocale already defined in MSVCRTD.lib(MSVCR90D.dll)
1>LIBCMTD.lib(tidtable.obj) : error LNK2005: __encode_pointer already defined in MSVCRTD.lib(MSVCR90D.dll)
1>LIBCMTD.lib(tidtable.obj) : error LNK2005: __decode_pointer already defined in MSVCRTD.lib(MSVCR90D.dll)
以上错误都是函数重定义的错误,这是由于调用程序的“运行时库”类型和被调用程序的“运行时库”类型不一致造成的。解决方案是:
先看被调用的Fortran动态或静态库程序的形式库的类型,在“项目->属性->Fortran->Libraris-> Runtime Library",再看调用的C/C++程序的形式库的类型,在”项目->属性->Configuration Properties->C/C++->Code Generation->Runtime Library“;
如果Fortran是静态库并且配置Debug,则它的Runtime Library的类型一般是:
Debug Multithreaded (/libs:static /threads /dbglibs)
那么对应的C/C++程序Debug配置的Runtime Library的类型应该为:
Multi-threaded Debug (/MTd)
如果Fortran是静态库并且配置Release,则它的Runtime Library的类型一般是:
Multithreaded
那么对应的C/C++程序Release配置的Runtime Library的类型应该为:
Multi-threaded (/MT)
如果Fortran是动态库并且配置Debug,则它的Runtime Library的类型一般是:
Debug Multithread DLL (/libs:dll /threads /dbglibs)
那么对应的C/C++程序Debug配置的Runtime Library的类型应该为:
Multi-threaded Debug DLL (/MDd)
如果Fortran是动态库并且配置Release,则它的Runtime Library的类型一般是:
Multithread DLL (/libs:dll /threads)
那么对应的C/C++程序Release配置的Runtime Library的类型应该为:
Multi-threaded DLL (/MD)
问题二:
在调用Fortran静态库的时候,库的路径和名称都设置对了但是一直出现下面这样的连接错误:
error LNK2001: unresolved external symbol __imp__DataProcess
如果你的函数的定义像下面这样:
extern "C" int _declspec(dllimport) DataProcess()
那很好解决,只要将”_declspec(dllimport)“删除就可以了,因为调用静态库不需要这个声明,但是调用动态库时必须要有这个声明,如果去掉了还是有问题,那请再检查一遍静态库是否包含到项目中。
问题三:
在Fortran中调用C/C++传进来的回调函数时(运行时),出现下面的运行时错误:
Unhandled exception at 0x00000005 in FortranDllTest.exe: 0xC0000005: Access violation reading location 0x00000005.
那我们首先调试一下看是运行到哪一步出现这样的错误,在看看这一步的参数是否正确,起始一般是程序的堆栈被破坏导致的,很有可能是我们的回调函数的调用约定不正确造成的,这个调用约定只能是__cdecl,不能是__stdcall,否则就会出现上面的错误。
下面开始介绍程序了:
下面的例程都是将Fortran编译成动态库的方式:
1)简单的调用:
Fortran代码:
SUBROUTINE FSUB (INT_ARG, STR_IN, STR_OUT) IMPLICIT NONE !DEC$ IF DEFINED (_DLL) !DEC$ ATTRIBUTES DLLEXPORT :: FSUB !DEC$ END IF INTEGER, INTENT(IN) :: INT_ARG CHARACTER(*), INTENT(IN) :: STR_IN CHARACTER(*), INTENT(OUT) :: STR_OUT CHARACTER*5 INT_STR WRITE (INT_STR,'(I5.5)')INT_ARG STR_OUT = STR_IN // INT_STR // CHAR(0) RETURN END
#ifdef __cplusplus extern "C" #endif #ifdef USEDLL __declspec(dllimport) void FSUB (int *INT_ARG,char *STR_IN,char *STR_OUT,size_t STR_IN_LEN,size_t STR_OUT_LEN); //字符串操作 void main (int argc, char *argv[]) { char instring[40]; char outstring[40]; int intarg; strcpy_s(instring,"Testing..."); intarg = 123; FSUB(&intarg,instring,outstring,strlen(instring),sizeof(outstring)); printf("%s\n",outstring); }
INTEGER, value,INTENT(IN) :: INT_ARG
如果是地址传递则不需要加这个关键字,因为Fortran默认是地址传递,还有要注意的是向Fortran中传递字符串时应该要加上字符串的长度,从fortran中传出字符串时已改在字符串最后加上CHAR(0)
2)复杂调用:
Fortran代码:
function callbackFunction(Array,count,Fun) BIND(c,NAME='callbackFunction')
use,intrinsic :: ISO_C_BINDING
implicit none
!DEC$ IF DEFINED (_DLL)
!DEC$ ATTRIBUTES DLLEXPORT :: callbackFunction
!DEC$ END IF
interface
function Fun(num)
use,intrinsic :: ISO_C_BINDING
implicit none
integer(C_INT),value :: num
logical :: Fun
end function
end interface
integer (C_INT),value :: count ! 加value表示值传递
integer (C_INT),INTENT(IN)::Array(count)
integer ::callbackFunction
integer :: i
logical ::b
!开始循环计算
do i=1,count
b=Fun(Array(i))
if(b) exit; !如果为true则跳出循环
end do
callbackFunction=0;
return
END
C/C++代码:
callbackFunctioncallbackFunctioncallbackFunction
bool HelloNumber3(int i) //回调函数
{
if(i>97)
{
printf("Hello %d\n",i);
return false;
}
else
return true;
}
#ifdef __cplusplus
extern "C"
#endif
#ifdef USEDLL
__declspec(dllimport) int callbackFunction(int* Array,int rows,bool(*f)(int));
void main (int argc, char *argv[])
{
int ary[4];
ary[0]=10;ary[1]=11;ary[2]=12;ary[3]=13;
int rtb=callbackFunction(ary,4,HelloNumber);
}
在Fortran中使用:
BIND(c,NAME='callbackFunction')
声明,可以将导出函数的名称命名为
”callbackFunction“,而不像第一种方法要将函数名都写成大写的。
在Fortran中回调函数的声明可以像上面用Interface来声明也可以用下面这一句来代替:
logical,external :: Fun
但是这样就不能声明回调函数的参数类型了。
C/C++中回调函数的调用约定默认是__cdecl,如果不是请将它改为”__cdecl“这样才不会留下隐患。
如果要向Fortran中传递二维数组,特别要注意的是Fortran中是列先数组,而C/C++中是行先数组。
如有错误请指出,大家一起进步!