VS与IVF C/C++与Fortran混合编程的终极解决方案

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 

C/C++代码:

#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);
}

像Fortran传递数据时,可以是值传递也可以是地址传递,如果是值传递则应该在Fortran从参数声明中加上Value关键字,如:
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++中是行先数组


如有错误请指出,大家一起进步!

你可能感兴趣的:(VS与IVF C/C++与Fortran混合编程的终极解决方案)