giraffe
giraffe

Reputation: 13

Fortran iso_c_binding standard: Error: Type mismatch in argument 'test_variable' at (1); passed INTEGER(8) to TYPE(c_ptr)

This question is reproducible because if the below program is compiled the below error message is output. There's no typographical errors.

My real life program uses integer*8 variables and does not compile with this specific iso_c_binding patch, see below. My program works fine when the iso_c_binding package is not used.

With the iso_c_binding package in my program, I get the following error message:

Error: Type mismatch in argument 'test_variable' at (1); passed INTEGER(8) to TYPE(c_ptr)

I’ve created the below testcase from my program. My program cannot be published. The testcase demonstrates the error that I’m getting.

Question 1: How do you fix this? The following is what I would like if at all possible! The two structs in the test.c file actually exist in my program. The game function formal arguments also actually exist in my real world program. My program passes integer*8 variables to the game routine. Everything else besides these three things should be able to be modified.

Other Questions: Does the C code have to change? Not counting the iso_c_binding interface code, does the Fortran code have to change? What has to change?

Can iso_c_binding do this?

Thank you,

File the_game.f:


  module the_game

  use, intrinsic :: iso_c_binding, only: c_int, c_float, c_ptr

  type, bind(c) :: myfloat
    real(c_float) :: float_tmp
  end type

  type, bind(c) :: myint
    integer(c_int) :: int_tmp
  end type

  interface
    function game(myint1, myfloat2) bind(c, name="game_")
        import :: c_int, c_ptr
        implicit none
        type(c_ptr) :: myint1
        type(c_ptr) :: myfloat2
        integer(c_int) :: game
    end function
  end interface

  end module the_game

File main.f:


   program main
     use the_game
     integer*8 test_variable
     integer*8 test_variable2
     integer ans

     ans=game (test_variable,test_variable2)
   end program main

File test.c:

#include <stdio.h>

struct myfloat 
{
   float float_tmp;
};

struct myint 
{
   int int_tmp;
};


int game_(myint1,myfloat2)
struct myint **myint1;
struct myfloat **myfloat2;
{/* The following shows how the code will eventually be used */
 /* after malloc etc. */
 /* printf ("(*myint1)->int_tmp = %d\n",(*myint1)->int_tmp); */
 /* printf ("(*myfloat2)->float_tmp = %f\n",(*myfloat2)->float_tmp); */
 /* (*myint1)->int_tmp = 1; */
 /* (*myfloat2)->float_tmp = 100.1; */
  return(0);
}

Compilation:

  gcc      -g -c test.c     -o test.o 
  gfortran -g -c the_game.f -o the_game.o
  gfortran -g -c main.f     -o main.o
  gfortran -g    main.o test.o   -o main.exe   -lgfortran

Output:

main.f:9.19:

         ans=game (test_variable,test_variable2)                        
                   1
Error: Type mismatch in argument 'myint1' at (1); passed INTEGER(8) to TYPE(c_ptr)

Upvotes: -6

Views: 808

Answers (1)

John Bollinger
John Bollinger

Reputation: 180058

Error: Type mismatch in argument 'test_variable' at (1); passed INTEGER(8) to TYPE(c_ptr)

Yes, INTEGER(8) is a different type from TYPE(c_ptr). Associating an argument of the former type with a subprogram parameter of the latter type is not allowed.

Question 1: How do you fix this? The following is what I would like if at all possible! The two structs in the test.c file actually exist in my program.

There aren't any struct objects in test.c. There are two structure type declarations, but no objects of those types in evidence. This is consequential, because it is unclear where you expect any such objects actually to be defined, or by whom allocated.

The game function formal arguments also actually exist in my real world program.

Of course the formal arguments to the game_() function exist. Their declarations in the definition of that function are that is required for that. But perhaps you're talking about the objects to which they point?

My program passes integer*8 variables to the game routine.

Probably not without your compiler at least complaining. The parameters are pointers, not integers. I would expect a C compiler to emit an analogous complaint when processing an attempt to call the game_() function in C with arguments that were integers of any size. C pointers are not integers, neither in C nor in Fortran.

Everything else besides these three things should be able to be modified.

There's been a lot of noise and lack of clarity here, but I think what you actually want is for

  • the Fortran code to define instances of two structure types, and
  • to pass pointers to those objects as arguments to the C game_() function,
  • so that the latter can access their members.

You have put a lot of attention on integer*8 and integer(8) (which are not necessarily the same thing), when these just aren't the right tools for the job. I suspect that you have also engaged one more level of indirection on the C side than is needed or helpful.

Since you're using the GNU compiler suite, you should consider reading its documentation of the Fortran / C interoperability features, which is not half bad.

You have at least these considerations to deal with:

  • declaring Fortran derived types that are interoperable with your C structure types
  • defining your C function with parameter types best suited to your actual needs
  • defining an appropriate interoperable interface to your C function
  • calling the C function correctly from Fortran.

I think you're actually making it harder than it needs to be on both sides.

You do seem to have the interoperable type declarations sorted out.

The C function declaration appears to be another story. I don't see any plausible reason why you would want to receive double pointers to the structures, and the result is probably not what you think. Whether you expect the Fortran code to associate a single structure with each parameter or an array of them, you probably want the C side to declare the parameters as single pointers. Note also that although you can use a C function name with a trailing underscore, you are not required to do. Just specify whatever name you choose in the Fortran interface to it.

Example:

test.c

#include <stdio.h>

struct myfloat {
   float float_tmp;
};

struct myint {
   int int_tmp;
};

int game_(struct myint *myint1, struct myfloat *myfloat2) {
    puts("C says:");
    printf ("myint1->int_tmp = %d\n", myint1->int_tmp);
    printf ("myfloat2->float_tmp = %f\n", myfloat2->float_tmp);

    myint1->int_tmp = 1;
    myfloat2->float_tmp = 2.0;

    return 0;
}

Note in particular that only one level of indirection is used for the arguments.

The Fortran interface is also pretty straightforward:

the_game.f90

module the_game

  use, intrinsic :: iso_c_binding, only: c_int, c_float, c_ptr

  type, bind(c) :: myfloat
    real(c_float) :: float_tmp
  end type

  type, bind(c) :: myint
    integer(c_int) :: int_tmp
  end type

  interface
    function game(myint1, myfloat2) bind(c, name="game_")
        import :: c_int, myfloat, myint
        implicit none
        type(myint) :: myint1      ! not a pointer!
        type(myfloat) :: myfloat2  ! not a pointer!
        integer(c_int) :: game
    end function
  end interface

end module the_game

Note that the function parameters are not defined as pointers. Fortran will pass pointers by default because its default subprogram invocation semantics use (approximately) pass by reference. It's mainly when you want to pass by value (as you often might do, because those are C's semantics) that you need to need to do anything special in the declared interface.

Having that in hand, it's not hard to call it from Fortran. Example:

main.f90

program main

use the_game

type(myint) :: test_variable
type(myfloat) :: test_variable2
integer ans

test_variable%int_tmp = 7
test_variable2%float_tmp = 3.14

ans = game(test_variable,test_variable2)

write(*, *)
write(*, *) 'Fortran says:'
write(*, *) 'Result = ', ans, '; my int = ', test_variable%int_tmp,     &
                              '; my_float = ', test_variable2%float_tmp

end program main

Note that there is neither any c_ptr nor any corresponding integer in the Fortran code that calls the C function. The Fortran side just works in terms of the (interoperable) Fortran derived types. And here's the output, showing that both sides are accessing the same objects:

$ ./main.exe 
C says:
myint1->int_tmp = 7
myfloat2->float_tmp = 3.140000

 Fortran says:
 Result =            0 ; my int =            1 ; my_float =    2.00000000

ALTERNATIVELY

Later comments suggest that perhaps you intend to use the parameters to game_() to pass C pointers back to the Fortran caller, perhaps pointers to dynamically allocated space. That, too, can be accommodated, and in that case you do have to deal with c_ptr, because that's what C will emit. But you do have to take care with level of indirection -- Fortran will pass an object of type c_ptr to C by reference, which C will receive as a double pointer.

Once it gets the C pointer values back, Fortran will use the c_f_pointer() subroutine to associate appropriately typed Fortran pointers with the allocated space, through which it can access the contents. Example:

alttest.c

#include <stdio.h>
#include <stdlib.h>

struct myfloat {
   float float_tmp;
};

struct myint {
   int int_tmp;
};

int game_(struct myint **myint1, struct myfloat **myfloat2) {
    *myint1 = malloc(sizeof(**myint1));
    *myfloat2 = malloc(sizeof(**myfloat2));

    if (!*myint1 || !*myfloat2) {
        return -1;
    }

    (*myint1)->int_tmp = 1;
    (*myfloat2)->float_tmp = 2.0;

    return 0;
}

the_game_alt.f90

module the_game_alt

  use, intrinsic :: iso_c_binding, only: c_int, c_float, c_ptr

  type, bind(c) :: myfloat
    real(c_float) :: float_tmp
  end type

  type, bind(c) :: myint
    integer(c_int) :: int_tmp
  end type

  interface
    function game(myint1_p, myfloat2_p) bind(c, name="game_")
        import :: c_int, c_ptr
        implicit none
        type(c_ptr), intent(out) :: myint1_p
  nd in this case you do have to deal with `c_ptr`      type(c_ptr), intent(out) :: myfloat2_p
        integer(c_int) :: game
    end function
  end interface

end module the_game_alt

altmain.f90

program main

use the_game_alt
use, intrinsic :: iso_c_binding

type(myint), pointer :: intvar
type(myfloat), pointer :: floatvar
type(c_ptr) :: myint_ptr, myfloat_ptr
integer ans


ans = game(myint_ptr, myfloat_ptr)
if (ans .ne. 0) then
    write(*, *) 'error: C returned ', ans
else
    call c_f_pointer(myint_ptr, intvar)
    call c_f_pointer(myfloat_ptr, floatvar)

    write(*, *)
    write(*, *) 'Fortran says:'
    write(*, *) 'Result = ', ans, '; my int = ', intvar%int_tmp, &
                                  '; my_float = ', floatvar%float_tmp
end if
end program main

Program output:

$ ./altmain.exe 

 Fortran says:
 Result =            0 ; my int =            1 ; my_float =    2.00000000

NOTE WELL:

It remains unclear what you actually want to do. It may be that something different from either of the above approaches is in fact required.

Upvotes: 1

Related Questions