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
- Is there an alternative to the use of 'type(*)'? What about simply calling the C-function without defining an interface?
- 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
链接已复制
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
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)).
.. Since I want to call a C routine with 'void*' argument from Fortran, I have the following questions
- Is there an alternative to the use of 'type(*)'? What about simply calling the C-function without defining an interface?
- 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.
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
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
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.