1 (edited by sankarnkp 2013-08-30 13:34:38)

Topic: Calling a function in C++ dll from FORTRAN

Hi,
I am stuck at a point where I am using mixed language programming of C++ and FORTRAN. I have a C++ function in a dll which returns a pointer. I tried to call it from my FORTRAN program. I have no problem in compiling, linking and running. However, after the run I get the C pointer returned as 0. It is not a problem with the function in the dl because I have checked with the dll being called from both C++ and C# programs. So I think it is something to dow ith FORTRAN calling the C++ function. Here is the structure of the program:

// C++ Header
// ---------------
# include statements

#ifdef BUILD_DLL
    #define DLL_EXPORT __declspec(dllexport)
#else
//    #define DLL_EXPORT __declspec(dllimport)
#endif


//#ifdef __cplusplus
extern "C"
{

double DLL_EXPORT __cdecl *CalcAirProperties(int InputCombination, double Input1, double Input2, double Pressure);

}
// ---------------

//C++ source code
// ---------------

double DLL_EXPORT __cdecl *CalcAirProperties(int InputCombination, double Input1, double Input2, double Pressure)
{
    double *AirProperties = new double[7];

//Do Calculations

   return AirProperties

}

// ---------------

Here is my FORTRAN code:
!********************

PROGRAM ProgramMain

USE Statements
USE iso_c_binding, ONLY : c_ptr,c_double,c_f_pointer

IMPLICIT NONE

TYPE(c_ptr) :: c_p
REAL(c_double), POINTER :: f_p(:)

INTERFACE
   
  FUNCTION CalcAirProperties(InputComb, InpValue1,InpValue2,BarometricPressure) BIND(C,NAME='CalcAirProperties')
     
      USE ISO_C_BINDING
     
      INTEGER(kind=C_INT), INTENT(IN)    :: InputComb
      REAL(kind=C_DOUBLE), INTENT(IN)    :: InpValue1
      REAL(kind=C_DOUBLE), INTENT(IN)    :: InpValue2
      REAL(kind=C_DOUBLE), INTENT(IN)    :: BarometricPressure
     
      TYPE(c_ptr)   :: CalcAirProperties

  END FUNCTION
 
END INTERFACE

    REAL :: TempDB_Test
    REAL :: TempRH_Test
    REAL :: BaroPressure

      TempDB_Test = 308.15
      TempRH_Test = 0.69
      BaroPressure = 101.325
     
      c_p = CalcAirProperties(2,TempDB_Test,TempRH_Test,BaroPressure)
     
      CALL c_f_pointer(c_p,f_p,[7])
     
      RHiC = f_p(3)
      RhoAiC = f_p(7)

END ProgramMain

!********************

In the above set up the pointer returned by the c_p=CalcAirProperties() call is 0 and thus the values f_p is not pointing to the right array start.

So to remove all the problems with pointers I converted the C++ function to accept an array (by reference) so that I can set the desired array values inside the C++ function and thus pass the array back to FORTRAN. Here is what I did for that:

// C++ Header
// ---------------
# include statements

#ifdef BUILD_DLL
    #define DLL_EXPORT __declspec(dllexport)
#else
//    #define DLL_EXPORT __declspec(dllimport)
#endif


//#ifdef __cplusplus
extern "C"
{

void DLL_EXPORT __cdecl CalcAirProperties(int InputCombination, double Input1, double Input2, double Pressure,double PropertyArray[]);

}
// ---------------

//C++ source code
// ---------------

void DLL_EXPORT __cdecl CalcAirProperties(int InputCombination, double Input1, double Input2, double Pressure,double PropertyArray[])
{
    double *AirProperties = new double[7];

//Do Calculations

    for(int i =0;i<7;i++)
    {
      PropertyArray(i) = AirProperties(i);
    }

}

// ---------------

Here is my FORTRAN code:
!********************

PROGRAM ProgramMain

USE Statements
USE iso_c_binding, ONLY : c_ptr,c_double,c_f_pointer

IMPLICIT NONE

TYPE(c_ptr) :: c_p
REAL(c_double), POINTER :: f_p(:)

INTERFACE
   
  SUBROUTINE CalcAirProperties(InputComb, InpValue1,InpValue2,BarometricPressure,PropertyArray) BIND(C,NAME='CalcAirProperties')
     
      USE ISO_C_BINDING
     
      INTEGER(kind=C_INT), INTENT(IN)    :: InputComb
      REAL(kind=C_DOUBLE), INTENT(IN)    :: InpValue1
      REAL(kind=C_DOUBLE), INTENT(IN)    :: InpValue2
      REAL(kind=C_DOUBLE), INTENT(IN)    :: BarometricPressure
     
      REAL(kind=C_DOUBLE), DIMENSION(7), INTENT(OUT)   :: PropertyArray

  END SUBROUTINE
 
END INTERFACE

    REAL :: TempDB_Test
    REAL :: TempRH_Test
    REAL :: BaroPressure
    REAL(kind=C_DOUBLE), DIMENSION(7)   :: PropertyArray

      TempDB_Test = 308.15
      TempRH_Test = 0.69
      BaroPressure = 101.325
     
      CALL CalcAirProperties(2, TempDB_Test,TempRH_Test,BaroPressure,PropertyArray)
     
      RHiC = PropertyArray(3)
      RhoAiC = PropertyArray(7)

END ProgramMain

!********************

The above method also did not work. So I am kind of lost. I am still a newbie when it comes to mixed language programming.

I understand this could not be a problem with Simply Fortran  IDE, but I would appreciate any help in this matter from the FORTRAN experts in this forum.

p.s: Do not ind the array element noted by normal parenthesis instead of square parenthesis. The forum would not accept square parenthesis.

Regards
skp

Re: Calling a function in C++ dll from FORTRAN

In your interface, try using the VALUE modifier when declaring your parameters rather than INTENT(IN).  I've had some better luck with that.  Additionally, I noticed that your C declaration says to use the cdecl calling convention.  This could theoretically be a problem (DLLs usually use the stdcall convention), but try using the VALUE modifier in your interface declarations first.

Jeff Armstrong
Approximatrix, LLC

Re: Calling a function in C++ dll from FORTRAN

Jeff,

Using VALUE modifier worked in my case. There was one small issue in the C++ side, but the major fix for me was changing the modifier to VALUE. Thanks for your help.

Regards
Sankar

Regards
skp