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

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

andy_in_oxford
New Contributor I
1,101 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
1,010 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 I
1,091 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
1,067 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 I
1,061 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
1,053 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 I
1,050 Views

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

0 Kudos
andy_in_oxford
New Contributor I
1,045 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
1,042 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
1,040 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
1,040 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
1,037 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
1,036 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
1,011 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