Intel® Fortran Compiler
Build applications that can scale for the future with optimized code designed for Intel® Xeon® and compatible processors.
28381 Discussions

catch error in character dummy in bind(c) procedure

Aldo_H_
Beginner
1,335 Views

I recently wasted some time tracking a simple problem in a function with the `bind(c)` attribute:

    integer(c_int) function set_reference_stateD_i(ref,T,rhomolar,hmolar0,smolar0) bind(C,name='set_reference_stateD')
      use, intrinsic :: iso_c_binding, only: c_char, c_double, c_int
      character(kind=c_char), intent(in) :: ref
      ! THIS WAS WRONG:   character(len=*,kind=c_char), intent(in) :: ref
      real(c_double), value :: T, rhomolar, hmolar0, smolar0
    end function set_reference_stateD_i

For completeness, I appended the full program, which is to be linked to CoolProp. See also the GitHub thread.

 

Wouldn't it be better if `ifort` had detected this obvious syntax error during compilation? I found this issue when I recompiled the library and the code with `gfortran`, which stated correctly that the "character argument ‘ref’ at (1) must be length 1 because procedure ‘set_reference_stated_i’ is BIND(C)". Character interoperability is somewhat anomalous, so I guess this is a common mistake.

 

With my wrong interface, calling the function returned a `0_c_int`, without raising any alarm bells.

 

0 Kudos
7 Replies
Juergen_R_R
Valued Contributor I
1,335 Views

Hi Aldo,

this is only an error in Fortran 2008 status and earlier, as assumed e.g. by gfortran and nagfor. Ifort assumes the newest standard, 2018, to be valid, and there, according to the 18-007 document for the F2018 standard including the Technical Report TS29113 this allows for assumed-length character type in bind(C) procedures. Namely, in 18.3.7.2 it says

A Fortran procedure interface is interoperable with a C function prototype if

[....]

(5)   any dummy argument without the VALUE attributte corresponds to a formal parameter of the prototype that is of pointer type, and either

    [....]

     *  the dummy argument is a nonallocatable nonpointer variable of type CHARACTER with assumed character length and the formal parameter is a pointer to CFI_desc_t,

If you use ifort with the flags -std08 -warn stderrors it also throws an error:

error #8113: F2008 standard only allows CHARACTER(LEN=1) arrays with BIND(C).   [REF

0 Kudos
Aldo_H_
Beginner
1,335 Views

Thanks for the explanation; I didn't know this about Fortran 2018.

 

It's interesting that I actually got the wrong result (a `o_c_int`, instead of `1_c_int`) when my dummy was `len=*`. This is the corresponding C prototype (the return type is `c_int`):

EXPORT_CODE int CONVENTION set_reference_stateD(const char *Ref, double T, double rhomolar, double hmolar0, double smolar0)

Can

character(len=*,kind=c_char), intent(in) :: ref

not be associated with the `Ref` dummy in C?

 

As a side note, I'm also a little puzzled as to why a compiler can't/won't perform any type checking when linking C and Fortran. I think it knows both interfaces, so comparing types seems easy... but apparently it's not.

0 Kudos
Juergen_R_R
Valued Contributor I
1,335 Views

No, this cannot work I think. I have not yet looked into the 2018 enhancements of C interoperability, but on the C side you have to use

#include "iso_fortran_binding.h"

and

CFI_desc_t *Ref instead of const char *Ref

Look at the example in Section 21.4 of the newest edition of Metcalf/Cohen/Reid, Modern Fortran Explained on Assumed character length in C interoperability.

0 Kudos
FortranFan
Honored Contributor II
1,335 Views

To consume an existing function in C which has a prototype such as:

int some_C_func( .., const char *, .. )

the Fortran interface, per standard facility since Fortran 2003 toward interoperability with C, requires the dummy argument corresponding 'const char *' be declared as an assumed size array of Fortran CHARACTER type of length of one with a KIND of C_CHAR.  However the Fortran language standard was kind enough to allow an actual argument of Fortran CHARACTER object of length greater than one to be passed to such a procedure e.g.,

#include <string.h>

int Cfunc(const char *s) {
   return (int)strlen(s);
}
   use , intrinsic :: iso_c_binding, only : c_char, c_int, c_null_char

   interface
      function Cfunc(s) result(r) bind(C, name="Cfunc")
         import :: c_char, c_int
         implicit none
         character(kind=c_char,len=1), intent(in) :: s(*)
         ! Function result
         integer(c_int) :: r
      end function
   end interface

   character(kind=c_char,len=*), parameter :: s = c_char_"Hello World!" // c_null_char

   i = Cfunc(s)
   print *, "i = ", i, "; expected is ", len(s)-1 

end

As alluded to in Quote #1, if one were to now "wrap" the existing C function with another C function making use of extended features from Fortran 2018, then the interface in Fortran can be a dummy argument of CHARACTER type of assumed length but with a KIND of C_CHAR.

0 Kudos
Steve_Lionel
Honored Contributor III
1,335 Views

There is no communication between Fortran and C. The Fortran compiler knows only the C interfaces you declare in Fortran.

In Fortran 2018, CHARACTER(*) is interoperable with a "C Descriptor", an invention of the Fortran standard as part of the Further Interoperability with C TS29113. All of these features were added in Intel Fortran 16. It is a bit unfortunate that this leaves open the trap you fell into, but the benefits are worth it.

No, CHARACTER(*) is not interoperable with a C *char parameter. Without BIND(C), you're at the mercy of the Fortran implementation as to how such a thing is passed.

0 Kudos
Aldo_H_
Beginner
1,335 Views

Thank you for all the replies. They clearly answer my questions.

0 Kudos
FortranFan
Honored Contributor II
1,335 Views

FortranFan wrote:

To consume an existing function in C which has a prototype such as:

int some_C_func( .., const char *, .. )

.. to now "wrap" the existing C function with another C function making use of extended features from Fortran 2018, then the interface in Fortran can be a dummy argument of CHARACTER type of assumed length but with a KIND of C_CHAR.

Here's a working example toward such a wrapper function in C using Intel Fortran on Windows where the wrapper function simply casts the opaque pointer base_addr member of C descriptor struct to the one of interest:

C:\Temp>type cfunc.c
#include <string.h>
#include "ISO_Fortran_binding.h"

// Say an existing function in C is so:
int Cfunc(const char *s) {
    return (int)strlen(s);
}

// A wrapper function to interoperate with Fortran assumed length
// CHARACTER object can be like so:
int Cfunc_wrap(const CFI_cdesc_t *descr_s) {
    char *s;
    // Set the pointer to C descriptor address
    s = (char *)descr_s->base_addr;
    return Cfunc(s);
}


C:\Temp>cl /c cfunc.c
Microsoft (R) C/C++ Optimizing Compiler Version 19.14.26433 for x64
Copyright (C) Microsoft Corporation.  All rights reserved.

cfunc.c

C:\Temp>type p.f90
   use , intrinsic :: iso_c_binding, only : c_char, c_int, c_null_char

   interface
      function Cfunc_wrap(s) result(r) bind(C, name="Cfunc_wrap")
         import :: c_char, c_int
         implicit none
         character(kind=c_char,len=*), intent(in) :: s !<-- assumed length dummy arg
         ! Function result
         integer(c_int) :: r
      end function
   end interface

   character(kind=c_char,len=*), parameter :: s = c_char_"Hello World!" // c_nul
l_char

   i = Cfunc_wrap(s)
   print *, "i = ", i, "; expected is ", len(s)-1

end

C:\Temp>ifort /c /standard-semantics p.f90
Intel(R) Visual Fortran Intel(R) 64 Compiler for applications running on Intel(R
) 64, Version 19.0.1.144 Build 20181018
Copyright (C) 1985-2018 Intel Corporation.  All rights reserved.


C:\Temp>link p.obj cfunc.obj /subsystem:console /out:p.exe
Microsoft (R) Incremental Linker Version 14.14.26433.0
Copyright (C) Microsoft Corporation.  All rights reserved.


C:\Temp>p.exe
 i =  12 ; expected is  12

C:\Temp>

 

0 Kudos
Reply