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

C interoperablilty and character strings

Simon_Geard
New Contributor I
2,063 Views

This is for the v16.0.3 compiler.

I could be wrong about this, but I have in mind that with the latest C interoperability features it is possible to pass a c-array of characters to a fortran procedure as a fortran string. Am I right about this? If so how do I do it? Thanks.

 

0 Kudos
15 Replies
Arjen_Markus
Honored Contributor I
2,063 Views

The ISO_C_BINDING module passes arrays of single characters to and from C. That is: a Fortran string is considered to be a C array of single characters. The length is not passed as a hidden argument, as it used to be. So you are supposed to supply the terminating NUL character yourself or use "counted strings" on the C side. That works fine if you know the length to be expected (for instance: from Fortran to C).

You can, however, get a pointer to the C string and convert it to a Fortran pointer. Something along these lines:

type(c_ptr) :: c_string
character(len=1), dimension(:), pointer :: f_string

call c_f_pointer( c_string, f_string )

write(*,*) 'Length: ', index( f_string, char(0) ) - 1

The pointer c_string can come from the C routine as a char ** argument.

 

0 Kudos
IanH
Honored Contributor II
2,063 Views

Arjen Markus wrote:

The ISO_C_BINDING module passes arrays of single characters to and from C

The module isn't particularly special - it is the use of the BIND(C) suffix on the relevant function or subroutine statement that is important.

The C interoperability stuff in Fortran 2015 also allows a C function to work with a descriptor for a CHARACTER(*) argument.

MODULE m
  IMPLICIT NONE
CONTAINS
  SUBROUTINE sub(str) BIND(C, NAME='sub')
    USE, INTRINSIC :: ISO_C_BINDING, ONLY: C_CHAR
    CHARACTER(*,KIND=C_CHAR), INTENT(IN) :: str
    PRINT *, str
  END SUBROUTINE sub
END MODULE m
#include "ISO_Fortran_binding.h"

void sub(CFI_cdesc_t* str);

int main()
{
  char *string = "Hello from C";
  CFI_CDESC_T(0) str;
  
  CFI_establish( 
      (CFI_cdesc_t*) &str,             /* descriptor */
      string,                          /* base address */
      CFI_attribute_other,             /* attributes */
      CFI_type_char,                   /* type */
      strlen(string) * sizeof(char),   /* storage size */
      0,                               /* rank */
      0 );                             /* extents (ignored) */
  sub((CFI_cdesc_t*) &str);
  return 0;
}

 

>ifort /c /check:all /warn:all /standard-semantics "2016-06-03 c-interop-f.f90"
Intel(R) Visual Fortran Intel(R) 64 Compiler for applications running on Intel(R) 64, Version 16.0.3.207 Build 20160415
Copyright (C) 1985-2016 Intel Corporation.  All rights reserved.


>cl "2016-06-03 c-interop-c.c" "2016-06-03 c-interop-f.obj"
Microsoft (R) C/C++ Optimizing Compiler Version 19.00.23918 for x64
Copyright (C) Microsoft Corporation.  All rights reserved.

2016-06-03 c-interop-c.c
Microsoft (R) Incremental Linker Version 14.00.23918.0
Copyright (C) Microsoft Corporation.  All rights reserved.

"/out:2016-06-03 c-interop-c.exe"
"2016-06-03 c-interop-c.obj"
"2016-06-03 c-interop-f.obj"

>"2016-06-03 c-interop-c.exe"
 Hello from C

 

0 Kudos
Simon_Geard
New Contributor I
2,063 Views

Thanks, that's what I was trying to find.

0 Kudos
Steven_L_Intel1
Employee
2,063 Views

Arjen, ISO_C_BINDING doesn't have anything to do with this! I understand that people use the module name as a shorthand for "C interoperability", but really, the module is just that, a module with some declarations.

Through Fortran 2008 (and version 15 of the Intel compiler), Arjen is correct that there is no straightforward way for C to pass a character string to a Fortran routine that accepts it as a character variable (of length other than 1). I'm not sure I understand the point of Arjen's example, as one could declare the Fortran dummy argument as an array of characters and then use c_f_pointer to get something more useful. Something like this:

subroutine fsub (str) bind(C)
use, intrinsic :: iso_c_binding
character, dimension(*) :: str
character(10000), pointer :: lcl_str
integer str_len
call c_f_pointer (c_loc(str), lcl_str)
str_len = index(lcl_str,C_NULL_CHAR)
print *, lcl_str(1:str_len)
...

However, an addendum to the Fortran standard, TS29113 "Further Interoperability with C", part of draft Fortran 2015, adds a new capability which requires a bit of work on the C side but full interoperability with character variables on the Fortran side. This is supported by Intel Fortran 16.

Here's an example:

#include "ISO_Fortran_binding.h"
#include <string.h>

extern void fsub(CFI_cdesc_t * dv);

int main()
{
    CFI_cdesc_t stringdesc;
    int retval;
    char * stringval = "This is a C string!";

    // Initialize the C descriptor as a scalar character nonpointer
    retval = CFI_establish(&stringdesc, stringval, CFI_attribute_other, CFI_type_char, strlen(stringval), 0, NULL);
    // Call fsub
    fsub(&stringdesc);

    return 0;
}
subroutine fsub (stringval) bind(C)
    implicit none
    character(*) :: stringval
    print *, len(stringval), stringval
    end subroutine fsub

The C code needs to reference the Fortran Include folder and the Fortran run-time libraries.

0 Kudos
Steven_L_Intel1
Employee
2,063 Views

Ah, not fast enough!

0 Kudos
Arjen_Markus
Honored Contributor I
2,063 Views

Okay, I should not use "ISO_C_BINDING" to describe the C interoperability.

What I meant to do with my sketchy code is: get access to the string on the C side, where you do not know in advance how long that is going to be. But I should have explained that better.

That said, the Fortran 2015 method looks interesting. I knew of it, but haven't studied the possibilities.

 

 

0 Kudos
Steven_L_Intel1
Employee
2,063 Views

In Ian's example, the name='sub' in the BIND(C) isn't necessary, since the standard behavior is to downcase the Fortran name. Doesn't hurt, though. I also note that he uses the CFI_CDESC_T macro specifying zero dimensions - that is, scalar. Also perfectly reasonable, but I thought I should point it out in case people got confused.

0 Kudos
Simon_Geard
New Contributor I
2,063 Views

This has proved to be a bit more than the noop I was expecting on the C++ side. The essence of the problem is that the header file doesn't compile nicely (VS 2010):

1>C:\Program Files (x86)\IntelSWTools\compilers_and_libraries_2016.3.207\windows\compiler\include\ISO_Fortran_binding.h(156): error C2220: warning treated as error - no 'object' file generated
1>C:\Program Files (x86)\IntelSWTools\compilers_and_libraries_2016.3.207\windows\compiler\include\ISO_Fortran_binding.h(156): warning C4200: nonstandard extension used : zero-sized array in struct/union
1>          Cannot generate copy-ctor or copy-assignment operator when UDT contains a zero-sized array


Is there a recommended way of handling this? Currently I'm going through all the projects disabling the 4200 warning. It ought to be possible to do this with peoperty pages but I can't figure out how to 'and' the DisableSpecificWarnings setting.

Thanks.

 

0 Kudos
IanH
Honored Contributor II
2,063 Views

You are compiling with /W4 and /WX.  Some of microsoft's own headers won't survive that.

You could put "#pragma warning(disable: 4200)" immediately before you #include the ISO_Fortran_binding.h file.

0 Kudos
Steven_L_Intel1
Employee
2,063 Views

The warning C4200 you have to live with if you're using MSVC. Our C++ expert tells me the usage is valid C. Intel C++ doesn't complain about this.

0 Kudos
Simon_Geard
New Contributor I
2,063 Views

I've settled for

#pragma warning( push )
#pragma warning( disable : 4200 )
#include "ISO_Fortran_binding.h"
#pragma warning( pop )

which does the job as far as the warnings are concerned. As for it working in practice I still haven't conquered that:

string wd("a/b/c");
CFI_cdesc_t stringdesc;
int retval = CFI_establish(&stringdesc, const_cast<char*>(wd.c_str()), CFI_attribute_other, CFI_type_char, wd.length(), 0, NULL);
mysub(&stringdesc);

 

subroutine mysub(wf) bind(c)
character(*), intent(in) :: wf
integer :: len_wf

len_wf = len(wf)   ! Gives correct value of 5
 ...

 

However the value of wf is incorrect and the debugger says that the subscripts are out of range and attempts to use it only give the first character as correct. Is there some additional magic I need when using this in the C++ world? I had to load the libifportmdd.lib library in the debug build, was that correct?

 

 

0 Kudos
jimdempseyatthecove
Honored Contributor III
2,063 Views

To access the 3rd character use wf(3:3) using substring notation
Do not use wf(3), this is array subscript notation.

You've declared wf as a single character string (no dimension) with an externally defined length.

Jim Dempsey

0 Kudos
FortranFan
Honored Contributor II
2,063 Views

Simon Geard wrote:

.. However the value of wf is incorrect and the debugger says that the subscripts are out of range and attempts to use it only give the first character as correct. Is there some additional magic I need when using this in the C++ world? ..

Try (void *) cast instead for the 2nd parameter in CFI_establish function.

To reiterate Steve's point, if all you're interested in is passing (char *) information to the Fortran side, you don't need to do all that gymnastics with C descriptors on the C++ side; instead the following would be ok too

#include <iostream>
#include <string>
using namespace std;

extern "C" {

   void fsub(const char *);

}

int main()
{

   int rc = 0;

   string wd = "a/b/c";

   fsub( wd.c_str() );

   return rc;

}

with first fsub defined as shown by Steve in Message #5 above (by the way, I would apply the TARGET attribute on str dummy argument in Steve's example just to be explicit about the intended processing of that argument in the procedure for the compiler as well as anyone else reading the code).  Now, if you have control over the Fortran procedure (equivalent to fsub in Steve's example), I would suggest including an additional parameter to represent the string length with INTENT(IN), VALUE attribute; this will allow one to avoid declaring a local variable of some arbitrary large length (10000 as in the example above which may be an overkill or not long enough depending on one's needs).  On the C++ side, one can simply add wd.length() to the parameters for the Fortran procedure, as you know,

And you would know only if the parameters involve previously inoperable types in Fortran 2008 and 2003 such as assumed shape arrays, etc. and parameters with ALLOCATABLE attributes are the newer features from Fortran 2015 needed.  See this thread, especially Message #13 for an additional illustration of a case where the enhanced interoperability features from Fortran 2015 help:

https://software.intel.com/en-us/forums/intel-visual-fortran-compiler-for-windows/topic/601190

 

0 Kudos
IanH
Honored Contributor II
2,063 Views

FortranFan wrote:

Quote:

Simon Geard wrote:

 

.. However the value of wf is incorrect and the debugger says that the subscripts are out of range and attempts to use it only give the first character as correct. Is there some additional magic I need when using this in the C++ world? ..

 

 

Try (void *) cast instead for the 2nd parameter in CFI_establish function.

If all you're interested in is passing (char *) information to the Fortran side, you don't need to do all that gymnastics with C descriptors on the C++ side; instead the following would be ok too

#include <iostream>
#include <string>
using namespace std;

extern "C" {

   void fsub(const char *);

}

int main()
{

   int rc = 0;

   string wd = "a/b/c";

   fsub( wd.c_str() );

   return rc;

}

with fsub defined as shown by Steve in Message #5 above (by the way, I would apply the TARGET attribute on str dummy argument in Steve's example just to be explicit about the intended processing of that argument in the procedure for the compiler as well as anyone else reading the code).

(There are two fsub's in #5 - the above is only applicable to the first, where the dummy argument is a character(1) array.)

0 Kudos
FortranFan
Honored Contributor II
2,063 Views

ianh wrote:

.. (There are two fsub's in #5 - the above is only applicable to the first, where the dummy argument is a character(1) array.)

Good point, message #14 has now been edited to reflect this.  Thanks,

0 Kudos
Reply