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

Headache free C-Fortran interface under 11+

Christopher_Watford
635 Views

I'm in charge of a rather large C-Fortran interface for our codes that has recently run afoul of the extra strictness included in 11+.

"error #8532: A character dummy argument with length other than 1 is not interoperable"

This problem has previously been raised by a coworker in 2011. Unfortunately, the answer given does not solve the real problem at hand (mostly because the co-worker was just trying to solve one specific issue and not the entire issue). I also understand that the old solution operated in a gray area, however, the implementation met all of the requirements imposed by operation in this gray area.

Problem Description

  1. Large C library with 100+ API entry points.
  2. Almost all of them include string arguments (90% IN, some are OUT)
  3. This library was previously written in Fortran.
  4. Thousands of lines in 50-60 Fortran executables which call the C library assuming it is still Fortran.
  5. The C Library includes the necessary logic to "pretend" to be Fortran (supports pretending to be CVF, IVF, and gfortran)
  6. The C Library provides an optional auto-generated module with the interface/subroutine/BIND magic to give argument type/dimension checking to these codes.

Example C API method

/* "data" is scalar to 7D, any of the primitive fortran types */
ABC_API(void) foobar (
  IN int *id,
  IN const char *folder FDECL_LENGTH_MIXED(folder),
  IN const char *file FDECL_LENGTH_MIXED(file),
  IN_BUFFER_ANY const void *data,
  IN_ESIZE(10) const int descriptor[],
  IN int *count,
  IN_ESIZE_MAX(7) const int startingElement[],
  OUT_ESIZE(2) int status[]
  FDECL_LENGTH_AFTER(folder)
  FDECL_LENGTH_AFTER(file)
);

Example Fortran Module entry for that routine (snipped for brevity)

! toplevel foobar selects type/dimension call
interface foobar
    module procedure foobar_i0, foobar_i1, foobar_i2, foobar_i3, foobar_i4, foobar_i5, foobar_i6, foobar_i7 &
          , foobar_r0, foobar_r1, foobar_r2, foobar_r3, foobar_r4, foobar_r5, foobar_r6, foobar_r7 &
          , foobar_d0, foobar_d1, foobar_d2, foobar_d3, foobar_d4, foobar_d5, foobar_d6, foobar_d7 &
          , foobar_cx0, foobar_cx1, foobar_cx2, foobar_cx3, foobar_cx4, foobar_cx5, foobar_cx6, foobar_cx7 &
          , foobar_l0, foobar_l1, foobar_l2, foobar_l3, foobar_l4, foobar_l5, foobar_l6, foobar_l7 &
          , foobar_c0, foobar_c1, foobar_c2, foobar_c3, foobar_c4, foobar_c5, foobar_c6, foobar_c7
endinterface foobar

! top level generic interface selects one of the following, which calls the C binding routines
subroutine foobar_i0 ( id, folder, file, idata, dsdes, ne, selem, istat )
    implicit none
    integer, intent(in) :: id
    character(len=*), intent(in) :: folder
    character(len=*), intent(in) :: file
    integer, intent(in) :: idata
    integer, intent(in) :: dsdes(DSDES_COUNT)
    integer, intent(in) :: ne
    integer, intent(in) :: selem(*)
    integer, intent(out) :: istat(STAT_COUNT)
    call foobar_internal_i0(id, folder, file, idata, dsdes, ne, selem, istat)
endsubroutine foobar_i0
subroutine foobar_i1 ( id, folder, file, idata, dsdes, ne, selem, istat )
    implicit none
    integer, intent(in) :: id
    character(len=*), intent(in) :: folder
    character(len=*), intent(in) :: file
    integer, intent(in) :: idata(:)
    integer, intent(in) :: dsdes(DSDES_COUNT)
    integer, intent(in) :: ne
    integer, intent(in) :: selem(*)
    integer, intent(out) :: istat(STAT_COUNT)
    call foobar_internal_i(id, folder, file, idata, dsdes, ne, selem, istat)
endsubroutine foobar_i1
! repeat subroutine foobar_XYZ for each type and dimension
! These _internal_ variants are the methods actually bound to the C, which provide both Scalar and
! DIMENSION(*) versions so that C just gets a raw pointer

subroutine foobar_internal_c ( id, folder, file, cdata, dsdes, ne, selem, istat ) bind(C, name='FOOBAR')
    implicit none
    integer, intent(in) :: id
    character(len=*), intent(in) :: folder
    character(len=*), intent(in) :: file
    character(len=*), intent(in) :: cdata(*)
    integer, intent(in) :: dsdes(DSDES_COUNT)
    integer, intent(in) :: ne
    integer, intent(in) :: selem(*)
    integer, intent(out) :: istat(STAT_COUNT)
endsubroutine foobar_internal_c

subroutine foobar_internal_c0 ( id, folder, file, cdata, dsdes, ne, selem, istat ) bind(C, name='FOOBAR')
    implicit none
    integer, intent(in) :: id
    character(len=*), intent(in) :: folder
    character(len=*), intent(in) :: file
    character(len=*), intent(in) :: cdata
    integer, intent(in) :: dsdes(DSDES_COUNT)
    integer, intent(in) :: ne
    integer, intent(in) :: selem(*)
    integer, intent(out) :: istat(STAT_COUNT)
endsubroutine foobar_internal_c0
! repeat foobar_internal_XYZ for each type

Basically, I can't update the C code (and its API) in any manner which would break the expectation that it is "Fortran". Now I'm caught because 11+ no longer allows this behavior. I also cannot introduce any required shim code on the Fortran side, because the API must work with or without the auto-generated module.

I'm more than happy to maintain the headache of the C side understanding Fortran string-isms, etc. This is far easier than introducing C-isms to the Fortran side, in both code support AND time-burden to update/verify all of the production Fortran.

Is there a way I can update this interface to meet both my desire to: (a) provide a type checking solution for the library, and (b) not introduce any required shimming?

 

0 Kudos
9 Replies
andrew_4619
Honored Contributor III
635 Views

You can switch off error #8532 if  you wish

0 Kudos
Christopher_Watford
635 Views

app4619 wrote:

You can switch off error #8532 if  you wish

Not per my tests:

Cannot disable Fortran error message 8532

 

0 Kudos
FortranFan
Honored Contributor III
635 Views

Christopher Watford wrote:

I'm in charge of a rather large C-Fortran interface for our codes that has recently run afoul of the extra strictness included in 11+.

"error #8532: A character dummy argument with length other than 1 is not interoperable"

This problem has previously been raised by a coworker in 2011. Unfortunately, the answer given does not solve the real problem at hand (mostly because the co-worker was just trying to solve one specific issue and not the entire issue). I also understand that the old solution operated in a gray area, however, the implementation met all of the requirements imposed by operation in this gray area.

...

Basically, I can't update the C code (and its API) in any manner which would break the expectation that it is "Fortran". Now I'm caught because 11+ no longer allows this behavior. I also cannot introduce any required shim code on the Fortran side, because the API must work with or without the auto-generated module.

I'm more than happy to maintain the headache of the C side understanding Fortran string-isms, etc. This is far easier than introducing C-isms to the Fortran side, in both code support AND time-burden to update/verify all of the production Fortran.

Is there a way I can update this interface to meet both my desire to: (a) provide a type checking solution for the library, and (b) not introduce any required shimming?

 

Instead of "auto-generated module with the interface/subroutine/BIND magic to give argument type/dimension checking to these codes" from the C library, can you create a concrete Fortran wrapper module that has wrapper procedures which translate "CHARACTER(LEN=*) :: " arguments from all the user Fortran code to "CHARACTER(LEN=1) :: var(*)" in the calls to the actual C code?  The Fortran users then only need to "USE" this wrapper module.

0 Kudos
JVanB
Valued Contributor II
635 Views

I was about to say that you probably can't meet your requirements without wrapper procedures, but given that you already have (I hope automatically generated) wrapper procedures, are you allowed to just change them? For example, in your foobar_i0 subroutine, change

call foobar_internal_i0(id, folder, file, idata, dsdes, ne, selem, istat)

to

call foobar_internal_i0(id, folder, file, idata, dsdes, ne, selem, istat, LEN(folder), LEN(file))

and then foobar_internal_i0 will read

subroutine foobar_internal_i0 ( id, folder, file, idata, dsdes, ne, selem, istat, folder_LEN, file_LEN ) bind(C, name='FOOBAR')
    implicit none
    integer, intent(in) :: id
    character, intent(in) :: folder(*)
    character, intent(in) :: file(*)
    integer, intent(in) :: idata
    integer, intent(in) :: dsdes(DSDES_COUNT)
    integer, intent(in) :: ne
    integer, intent(in) :: selem(*)
    integer, intent(out) :: istat(STAT_COUNT)
    integer, intent(in) :: folder_LEN
    integer, intent(in) :: file_LEN
endsubroutine foobar_internal_i0

Also you could save on writing out so many wrappers and interface bodies if you wrote out a template that worked for all data types (requires implicit typing for the template type). I guess there's a little problem for character data because you don't really want a C interface for a scalar character dummy argument here.

 

0 Kudos
Steven_L_Intel1
Employee
635 Views

You misunderstand the nature of the problem. There is no "extra strictness". You are, in some of the interfaces, using the C interoperability features of Fortran 2003. When you say BIND(C) for a routine interface, the standard places restrictions on the dummy arguments. One of these is that one about character lengths greater than 1. If this library creates auto-generated Fortran interfaces with BIND and with CHARACTER(*), then it is simply wrong.

There is a solution - remove bind(C) as it's clear from your text that the C code is not suitable for this. Use

!DEC$ ATTRIBUTES DECORATE, ALIAS:"c-routine-name" :: functionname

to get the name the way you want it. If you do this, then the character lengths are passed at the end of the argument list as hidden arguments.

0 Kudos
JVanB
Valued Contributor II
635 Views

In my gfortran-centric solution, I forgot to give the LEN parameters the VALUE attribute:

   integer(C_SIZE_T), VALUE :: folder_LEN

   integer(C_SIZE_T), VALUE :: file_LEN

The KIND of the hidden length arguments is a bit up in the air: ifort specifies C_SIZE_T (from ISO_C_BINDING) but gfortran used to use KIND(0) although I think there was supposed to be a change to C_SIZE_T but if they did make the change over,  they didn't document it. But given the way arguments are passed in 64-bit ABIs it shouldn't really matter. I think my approach should work in two of the target compilers, gfortran and ifort, whereas Steve's approach should work in ifort and CVF. Except ... CVF didn't have DECORATE, did it? I am more fond of using BIND(C) to solve interface issues because the rules for !DEC$ ATTRIBUTES are a bit arcane and inconsistent and only partially documented.

0 Kudos
Christopher_Watford
635 Views

Steve Lionel (Intel) wrote:

You misunderstand the nature of the problem. There is no "extra strictness". 

Perhaps I would have been better to word it as "now enforcing"! I'm going to attempt the decoration solution as that seems like a much easier solution.

Thank you all for the help.

0 Kudos
Steven_L_Intel1
Employee
635 Views

There may have been a compiler bug in version 10, where we first introduced BIND(C), that it did not do what the standard required.That was a long time ago.

I agree with RO that BIND(C) is generally preferable, but the C library being used here is expressly written in a way not compatible with BIND(C) so I suggested the alternative. (If you didn't mind changing the Fortran code to explicitly pass the character lengths and modified the interfaces to include them, then one could use BIND(C).)

0 Kudos
FortranFan
Honored Contributor III
635 Views

CVF 6.6a does support DECORATE; I don't know about earlier versions.

0 Kudos
Reply