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

And another problem with using 'interface' for accessing C from Fortran

andy_in_oxford
New Contributor I
2,547 Views

I have yet another problem with accessing C from Fortran. This follows on from my two earlier problems that were solved. 

 

So the program compiles but crashes on running. I am building and running the following in Windows using the Intel Fortran compiler. My program compiles eccodes.f90 and grib_f90.f90 correctly and grib_copy_msg.f90 compiles correctly. However when I try to run it an error is thrown:

 

I have the following: 

 

In the top part of the program grib_copy_msg.f90 I have:

 

program copy

  use eccodes

  implicit none

  integer                            :: err, centre

  integer                            :: byte_size

  integer                            :: infile,outfile

  integer                            :: igrib_in

  integer                            :: igrib_out

  character(len=1), dimension(:), allocatable :: message

  character(len=32)             :: product_kind

  integer                            :: status

 

  call codes_open_file(infile,'msl_000032_me.grb','r')

  call codes_open_file(outfile,'copy.grb','w')

  call codes_grib_new_from_file(infile, igrib_in, status)

  call codes_get_message_size_int(igrib_in, byte_size)

  allocate(message(byte_size), stat=err)

  call codes_copy_message(igrib_in, message)

  call codes_new_from_message(igrib_out, message)

 

  print *,'I am here 1'

  call codes_get(igrib_out, 'kindOfProduct', product_kind)

  print *,'I am here 2'

.....

 

In eccodes.f90 there is:

 

interface codes_get

    module procedure codes_get_int, &

                     codes_get_real4, &

                     codes_get_real8, &

                     codes_get_string, &

                     codes_get_byte_array, &

                     codes_get_int_array, &

                     codes_get_real4_array, &

                     codes_get_real8_array

end interface codes_get

 

subroutine codes_get_string ( msgid, key, value, status )

    integer(kind=kindOfInt),          intent(in)  :: msgid

    character(len=*),                 intent(in)  :: key

    character(len=*),                 intent(out) :: value

    integer(kind=kindOfInt),optional, intent(out) :: status

    !DIR$ ATTRIBUTES DLLEXPORT :: codes_get_string

    print *,'I am here 1.1'

    call grib_get_string ( msgid, key, value, status )

end subroutine codes_get_string

 

 

grib_get_string is in grib_f90.f90

 

  subroutine grib_get_string ( gribid, key, value, status )

      integer(kind=kindOfInt),          intent(in)  :: gribid

      character(len=*),                 intent(in)  :: key

      character(len=*),                 intent(out) :: value

      integer(kind=kindOfInt),optional, intent(out) :: status

      integer(kind=kindOfInt)                       :: iret

 

      interface

        integer(c_int) function grib_f_get_string(gribid,key, &

              value,lkey,lval) bind(C,name="grib_f_get_string_")

          use iso_c_binding, only: c_int,c_char

          integer(c_int), intent(in)     :: gribid

          integer(c_int)                 :: lkey,lval

          character(c_char), intent(in)  :: key

          character(c_char), intent(out) :: value

        end function grib_f_get_string

      end interface

 

      interface

          subroutine grib_f_write_on_fail( &

               gribid) bind(C,name="grib_f_write_on_fail_")

            use iso_c_binding, only: c_int

            integer(c_int), intent(in) :: gribid

          end subroutine grib_f_write_on_fail

      end interface

 

      print *,'I am here 1.1.1'

      iret=grib_f_get_string ( gribid, key, value, len(key), len(value) )

      print *,'I am here 1.1.2'

 

      if (iret /= 0) then

        call grib_f_write_on_fail(gribid)

      endif

      if (present(status)) then

         status = iret

      else

         call grib_check(iret,'get',key)

      endif

  end subroutine grib_get_string

 

 

grib_f_get_string is in a C file called grib_fortran.c:

 

int grib_f_get_string_(int* gid, char* key, char* val,int len, int len2){

 

    grib_handle *h = get_handle(*gid);

    int err = GRIB_SUCCESS;

    char buf[1024];

    size_t lsize = len2;

    if(!h) return  GRIB_INVALID_GRIB;

    fort_char_clean(val,len2);

 

    printf("%s\n","I am here 1.1.1.1");

    err = grib_get_string(h, cast_char(buf,key,len), val, &lsize);

    printf("%s\n","I am here 1.1.1.2");

 

    czstr_to_fortran(val,len2);

    return  err;

}

 

 

When I try to run it, I get the output:

 

I am here 1

I am here 1.1

I am here 1.1.1

 

The program then crashes.

 

What mistake have I made with this interface?

0 Kudos
1 Solution
andy_in_oxford
New Contributor I
2,456 Views

Thank you for your help on this Arjen. Based on your comments I have a solution to this now.

The solution to this is to have in the interface:

integer(c_int), value :: lkey,lval

And in the C file:

int grib_f_get_string_(int* gid, char* key, char* val,int* len, int* len2){

View solution in original post

0 Kudos
12 Replies
Arjen_Markus
Honored Contributor II
2,537 Views

I can see several mistakes:

  • The declaration on the Fortran side of the key and value string is wrong. It should be:
          character(len=*,kind=c_char), intent(in)  :: key
          character(len=*,kind=c_char), intent(out) :: value

     otherwise the strings are c_char characters long (the kind and the length are confused here)

  • The lengths of the strings are declared as "integer(c_int)", but as Fortran passes arguments by reference by default, this would mean "int*" on the C side. You probably want:
    integer(c_int), value :: lkey,lval​
    unless the length changes. Alternatively: declare them as pointer on the C side.
  • Be aware that C works best with nul-terminated strings, so you need to arrange for that extra character. That may be done in fort_char_clean() but you do not show the implementation.
0 Kudos
andy_in_oxford
New Contributor I
2,513 Views

Thank you Arjen.

 

When I substitute in:

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

character(len=*,kind=c_char), intent(out) :: value

 

I get a compile error:

error 6683: A kind type parameter must be a compile-time constant.  [C_CHAR]

 

When I substitute into the interface (without the 'kind=c_char' change):

integer(c_int), value :: lkey,lval​

 

It compiles and runs and crashes in fort_char_clean

fort_char_clean is defined as:

 

static void fort_char_clean(char* str,int len)
{
char *p,*end;
p=str; end=str+len-1;
while (p != end) *(p++)=' ';
*p=' ';
}

 

How should I change the interface to get this working?

0 Kudos
Arjen_Markus
Honored Contributor II
2,507 Views

That is odd. Could you attach your complete source code (omit the code for the eccodes library) or the relevant part, so that we can reproduce this compile-time error? Since you import c_char from iso_c_binding, the compiler message is odd. Hence my request for a reproducer program, rather than the fragments.

0 Kudos
andy_in_oxford
New Contributor I
2,499 Views

Hi Arjen,

 

Would you like the code just for grib_copy_msg.f90?  The rest of code comes from eccodes which is available online from ECMWF.

0 Kudos
Arjen_Markus
Honored Contributor II
2,496 Views

Well, the code you wrote :). I should be able to download the ECMWF library myself.

0 Kudos
andy_in_oxford
New Contributor I
2,491 Views

Here is the grib_copy_msg.f90 code:

 

program copy

  use eccodes

  implicit none

  integer                            :: err, centre

  !integer(kind=kindOfSize)           :: byte_size

  integer                            :: byte_size

  integer                            :: infile,outfile

  integer                            :: igrib_in

  integer                            :: igrib_out

  character(len=1), dimension(:), allocatable :: message

  character(len=32)                  :: product_kind

  integer                            :: status

 

  call codes_open_file(infile,'msl_000032_me.grb','r')

  call codes_open_file(outfile,'copy.grb','w')

  call codes_grib_new_from_file(infile, igrib_in, status)

  call codes_get_message_size_int(igrib_in, byte_size)

 

  allocate(message(byte_size), stat=err)

 

  call codes_copy_message(igrib_in, message)

  call codes_new_from_message(igrib_out, message)

 

  print *,'I am here 1'

  call codes_get(igrib_out, 'kindOfProduct', product_kind)

  print *,'I am here 2'

 

  write(*,*) 'kindOfProduct=',product_kind

 

  centre=80

  call codes_set(igrib_out, 'centre', centre)

 

  call codes_write(igrib_out, outfile)

 

  call codes_release(igrib_out)

  call codes_release(igrib_in)

  call codes_close_file(infile)

  call codes_close_file(outfile)

  deallocate(message)

 

end program copy

0 Kudos
andy_in_oxford
New Contributor I
2,488 Views

Hi Arjen,

 

To build eccodes in Windows is a bit of a pain at the moment. It is currently set up to build in Linux and Mac. There are a number of steps to build in it in Windows:

 

- There are hard-coded symlinks that don't work and need to be replaced. I replaced with a copy of the actual file that was symlinked.

- There are instances of 'type' and 'echo' that need to be replaced.

- I had to replace the contents of eccodes/fortran/grib_types.f90

0 Kudos
andy_in_oxford
New Contributor I
2,486 Views

To:

eccodes/fortran/CMakeFiles/eccodes_f90.dir/build.make

 

On line 70, change:
type <your_path_to_eccodes>/eccodes/fortran/grib_f90_head.f90 <your_path_to_eccodes>/eccodes/fortran/grib_f90_int.f90 <your_path_to_eccodes>/eccodes/fortran/grib_f90_long_size_t.f90 <your_path_to_eccodes>/eccodes/fortran/grib_f90_tail.f90 > grib_f90.f90

To:
copy /y grib_f90_head.f90+grib_f90_int.f90+grib_f90_long_size_t.f90+grib_f90_tail.f90 grib_f90.f90

 

On line 80, change:
type <your_path_to_eccodes>/eccodes/fortran/eccodes_f90_head.f90 <your_path_to_eccodes>/eccodes/fortran/eccodes_f90_int.f90 <your_path_to_eccodes>/eccodes/fortran/eccodes_f90_long_size_t.f90 <your_path_to_eccodes>/eccodes/fortran/eccodes_f90_tail.f90 > eccodes_f90.f90

To:

copy /y eccodes_f90_head.f90+eccodes_f90_int.f90+eccodes_f90_long_size_t.f90+eccodes_f90_tail.f90 eccodes_f90.f90

0 Kudos
andy_in_oxford
New Contributor I
2,486 Views

To line 127 of:

<your_path_to_eccodes>/eccodes/fortran/CMakeFiles/grib_types.dir/build.make

Change:
echo >nul && "C:\Program Files (x86)\CMake\bin\cmake.exe" -E remove <your_path_to_eccodes>/eccodes/fortran/grib_types.exe

To:
"C:\Program Files (x86)\CMake\bin\cmake.exe" -E remove <your_path_to_eccodes>/eccodes/fortran/grib_types.exe

0 Kudos
andy_in_oxford
New Contributor I
2,483 Views

To the file:

<your_path_to_eccodes>/eccodes/fortran/CMakeFiles/eccodes_f90.dir/build.make

 

On line 181 change:
echo >nul && "C:\Program Files (x86)\CMake\bin\cmake.exe" -E remove <your_path_to_eccodes>/eccodes/bin/eccodes_f90.dll

To:
"C:\Program Files (x86)\CMake\bin\cmake.exe" -E remove <your_path_to_eccodes>/eccodes/bin/eccodes_f90.dll

 

Change lines 190 to 192:
echo >num && "C:\Program Files (x86)\CMake\bin\cmake.exe" -E make_directory <your_path_to_eccodes>/eccodes/include
echo >num && "C:\Program Files (x86)\CMake\bin\cmake.exe" -E copy <your_path_to_eccodes>/eccodes/fortran/modules/./eccodes.mod <your_path_to_eccodes>/eccodes/include
echo >num && "C:\Program Files (x86)\CMake\bin\cmake.exe" -E copy <your_path_to_eccodes>/eccodes/fortran/modules/./grib_api.mod <your_path_to_eccodes>/eccodes/include

To:
"C:\Program Files (x86)\CMake\bin\cmake.exe" -E make_directory <your_path_to_eccodes>/eccodes/include
"C:\Program Files (x86)\CMake\bin\cmake.exe" -E copy <your_path_to_eccodes>/eccodes/fortran/modules/./eccodes.mod <your_path_to_eccodes>/eccodes/include
"C:\Program Files (x86)\CMake\bin\cmake.exe" -E copy <your_path_to_eccodes>/eccodes/fortran/modules/./grib_api.mod <your_path_to_eccodes>/eccodes/include

0 Kudos
andy_in_oxford
New Contributor I
2,482 Views

You will need a copy of bash.exe (this can be obtained from the Windows version of Git)

And then build eccodes in Windows using the following CMake command:

cmake -G "NMake Makefiles" -DBASH_EXE=<your_path_to_bash_exe>/bash.exe <your_path_to_eccodes> -DCMAKE_BUILD_TYPE=Release -DCMAKE_Fortran_COMPILER=<your_path_to_intel_fortran>/compilers_and_libraries_2020.0.166/windows/bin/intel64/ifort.exe"

Follow that by the nmake commands:

nmake clean

nmake

0 Kudos
andy_in_oxford
New Contributor I
2,457 Views

Thank you for your help on this Arjen. Based on your comments I have a solution to this now.

The solution to this is to have in the interface:

integer(c_int), value :: lkey,lval

And in the C file:

int grib_f_get_string_(int* gid, char* key, char* val,int* len, int* len2){

0 Kudos
Reply