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

Interface to C routine with void* argument. Alternatives to type(*)?

Diehl__Martin
Novice
1,061 Views

As far as I know, 'type(*)' is the standard-conforming way to provide interfaces to C routines with dummy arguments (formal parameter in the C terminology) of type 'void*'. However, the use of 'type(*)' imposes several restrictions, e.g. https://www.ibm.com/support/knowledgecenter/SSAT4T_15.1.4/com.ibm.xlf1514.lelinux.doc/language_ref/assumedtypeobj.html

Since I want to call a C routine with 'void*' argument from Fortran, I have the following questions

  1. Is there an alternative to the use of 'type(*)'? What about simply calling the C-function without defining an interface?
  2. Is the following code valid? The GNU Fortran compiler rejects it because context_type contains a type-bound procedure which seems to be a valid concern. The Intel compiler compiles it
program main
  use context_module

  implicit none
  type(context_type) :: context

  call test(context) 

end program main


module context_module

  implicit none

  type ::  context_type
    integer, private :: foo

    contains
      procedure, public :: init => context_init
   end type context_type

contains

  subroutine context_init(self, foo)
   implicit none
    class(context_type), intent(in out) :: self
    integer, intent(in) :: foo
    self%foo = foo
  end subroutine context_init


 subroutine test(context)
   implicit none
   type(*) :: context
   write(6,*) 'hello world'   
 end subroutine

end module context_module

 

0 Kudos
6 Replies
Juergen_R_R
Valued Contributor I
1,061 Views

Dear Martin,

here is how I would do that, very generic, with an extern "C" C/C++ function, an interface and a  wrapper

type that contains a c_ptr to the extern C function.

extern "C"  void your_c_function (double arg) {
  .... [C code]
}

 type wrapper_t
   private
   type(c_ptr) :: obj
 end wrapper_t

 interface
     subroutine your_c_function (obj, arg) bind(C)
       import
       type(c_ptr), value :: obj
       real(c_double), value :: arg
     end your_c_function
  end interface

 type(wrapper_t) :: wrap

contains
  
  subroutine your_fortran_function (wrap, arg)
    type(wrapper_t), intent(inout) :: wrap
    real(default), intent(in) :: arg
    call your_c_function (wrap%obj, real (arg, c_double))
  end subroutine your_fortran_function  

 

0 Kudos
Steve_Lionel
Honored Contributor III
1,061 Views

I haven't yet spotted text in the standard that prohibits passing a type with a type-bound procedure to a TYPE(*). I also can't think of a reason for such a restriction, given the severe limits on what you can do with TYPE(*).

On the other hand, I wonder what it is you expect the C routine to do with such a type. It certainly is not interoperable.

TYPE(*) is indeed intended to be the equivalent of void (void* if you don't also say VALUE, assuming BIND(C)).

0 Kudos
FortranFan
Honored Contributor II
1,061 Views

.. Since I want to call a C routine with 'void*' argument from Fortran, I have the following questions

  1. Is there an alternative to the use of 'type(*)'? What about simply calling the C-function without defining an interface?
  2. Is the following code valid? ..

In response to question 1 and without getting into too many details yet: readers can take note <type(c_ptr), intent(in), value> in Fortran is a reasonable alternative to TYPE(*) option toward the INTERFACE for a C function with <void *> formal parameter.

In response to question 2, I think the code in the original post conforms to what one can expect will be the next revision (Fortran 2018) of the standard to be published in the near future; current "official" standard is technically still Fortran 2008 which doesn't support TYPE(*) i.e., assumed-type that is unlimited polymorphic.

Draft standard for Fortran 2018 constrains the TYPE(*) dummy argument to not have certain attributes (such as INTENT(OUT), ALLOCATABLE, POINTER, VALUE, etc,).  Also there are constraints on where the variable with TYPE(*) characteristic can appear.  But none of these constraints seem to apply to code in the original post.  I think gfortran got it wrong.

0 Kudos
Diehl__Martin
Novice
1,061 Views

Hi,

thanks a lot for the quick and extensive feedback.

Now that it is clarified that the actual clarified that the actual argument to a type(*) dummy argument can have type-bound procedures it seems to be the easiest way to go. Note that this contradicts 'An assumed-type dummy argument cannot correspond to an actual argument of a derived type that has type parameters, type-bound procedures, or final subroutines.' from above cited IBM page.

Since I still have an unresolved issue, I'll give some details. The Fortran interface is for PETSc (http://www.mcs.anl.gov/petsc/), an exemplary routine looks like

PetscErrorCode  SNESSetConvergenceTest(SNES snes,PetscErrorCode (*SNESConvergenceTestFunction)(SNES,PetscInt,PetscReal,PetscReal,PetscReal,SNESConvergedReason*,void*),void *cctx,PetscErrorCode (*destroy)(void*))
{
   if (!SNESConvergenceTestFunction) SNESConvergenceTestFunction = SNESConvergedSkip;
   if (snes->ops->convergeddestroy) {
     (*snes->ops->convergeddestroy)(snes->cnvP);
   }
   snes->ops->converged        = SNESConvergenceTestFunction;
   snes->ops->convergeddestroy = destroy;
   snes->cnvP                  = cctx;
   return(0);
 }

and the interface routine looks like

      Interface                                                                                     
      subroutine SNESSetConvergenceTest(snes,func,cctx,destroy,ierr)                                
      use petscsnesdef                                                                              
       type(tSNES) :: snes                                                                                 
       external :: func                                                                             
       type(*) :: cctx                                                                              
       external :: destroy                                                                          
       type(tPetscErrorCode), intent(out) :: ierr                                                          
      end subroutine                                                                                
      end Interface

Still, with or without the interface, I get the following error

/usr/bin/x86_64-linux-gnu-ld: test_snes.o: undefined reference to symbol 'for_set_reentrancy'
/opt/intel/compilers_and_libraries_2018/linux/lib/intel64/libifcoremt.so.5: error adding symbols: DSO missing from command line

PETSc is of course a relatively complex library, therefore I tried to provide the minimal example in the initial post which compiles fine with Intel Fortran. GNU fortran works fine for the PETSc example WITHOUT interface, but complains with the explicit interface

0 Kudos
FortranFan
Honored Contributor II
1,061 Views

Diehl, Martin wrote:

.. a type(*) dummy argument .. seems to be the easiest way to go..

Since I still have an unresolved issue, I'll give some details. The Fortran interface is for PETSc (http://www.mcs.anl.gov/petsc/), an exemplary routine looks like

PetscErrorCode  SNESSetConvergenceTest(SNES snes,PetscErrorCode (*SNESConvergenceTestFunction)(SNES,PetscInt,PetscReal,PetscReal,PetscReal,SNESConvergedReason*,void*),void *cctx,PetscErrorCode (*destroy)(void*))
{
   if (!SNESConvergenceTestFunction) SNESConvergenceTestFunction = SNESConvergedSkip;
   if (snes->ops->convergeddestroy) {
     (*snes->ops->convergeddestroy)(snes->cnvP);
   }
   snes->ops->converged        = SNESConvergenceTestFunction;
   snes->ops->convergeddestroy = destroy;
   snes->cnvP                  = cctx;
   return(0);
 }

and the interface routine looks like ..

In terms of "type(*) dummy argument .. seems to be the easiest way to go", you're arriving at conclusions that were not implied in the responses since the comments were constrained by your original post.  TYPE(*) feature introduced in Fortran 2018 has its place but it's not necessarily the "easiest way" under several circumstances.

Anyways, the interface in Fortran you show for PETSc library function is likely the cause of the issues.  I have not studied PETSc but assuming the C interface you list is correct, the following would be a suitable interface for it in Fortran:

interface

    function SNESSetConvergenceTest(snes, pSNESConvergenceTestFunction, pcctx, pdestroy)            &
       result( errorcode ) bind(C, name="SNESSetConvergenceTest")

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

       implicit none

       ! Argument list
       type(SNES), intent(inout)         :: snes
       type(c_funptr), intent(in), value :: pSNESConvergenceTestFunction
       type(c_ptr), intent(in), value    :: pcctx       
       type(c_funptr), intent(in), value :: pdestroy

       ! Function result
       integer(c_int) :: errorcode 

    end function

end interface

 

0 Kudos
Steve_Lionel
Honored Contributor III
1,061 Views

Ok, I found the relevant text in the standard. In the F2018 draft, 15.5.2.4 (Argument association > Ordinary dummy variables) it says:

"If the actual argument is of a derived type that has type parameters, type-bound procedures, or final subroutines, the dummy argument shall not be assumed type."

Since ifort claims to support this aspect of F2018 (the "further interoperabilty with C" features), it should have caught this. I will file a bug report with Intel.

 

0 Kudos
Reply