Holding a pointer to a C function inside a Fortran derived type
Asked Answered
C

1

7

I have a Fortran DLL which is called from a C program, and one of my procedures needs periodically to call a callback function which is supplied by the C program. I currently have it working well in its 'simple' form, but I'd like to be able to store my callback pointer inside a derived type, so that it can be passed around within my Fortran code more easily. So far, nothing I've tried seems to work.

To begin with, here is what I have at the moment, and this does work:

Starting in the C (OK, actually C++) program, the header prototype for the callback is:

typedef void (*fcb)(void *)

and the prototype for the Fortran call is:

extern "C" __declspec(dllexport) int fortran_function(int n,
                                                      uchar *image_buffer,
                                                      fcb callback,
                                                      void *object);

The actual callback function is:

void callback(void* pObject)
{
    // Cast the void pointer back to the appropriate class type:
    MyClass *pMyObject = static_cast<MyClass *>(pObject);
    pMyObject -> updateImageInGUI();
}

and the call to the Fortran code from C++ is:

int error = fortran_function(m_image.size(), m_image.data, callback, this);

where m_image is an array of image data which is a member attribute of the current object. What happens is that the C++ passes the raw image data to the Fortran DLL and asks the Fortran to process it, and since this takes a long time the Fortran periodically updates the image buffer and calls the callback to refresh the GUI. Anyway, moving on to the Fortran side, we define an interface for the C callback:

abstract interface
    subroutine c_callback(c_object) bind(c)   
        use, intrinsic :: iso_c_binding
        type(c_ptr), intent(in) :: c_object     
    end subroutine c_callback
end interface

and define our main Fortran routine thus:

integer(c_int) fortran_function(n, image, callback, c_object)     &
                                bind(c, name='fortran_function')

    integer(c_int), value :: n
    integer(4), intent(inout), dimension(n) :: image
    procedure(c_callback) :: callback
    type(c_ptr), intent(in) :: c_object

Somewhere in the main routine we call our subroutine, foo:

call foo(data, callback, c_object)

...where foo is defined as:

subroutine foo(data, callback, c_object)

    type(my_type), intent(inout) :: data
    procedure(c_callback) :: callback
    type(c_ptr), intent(in) :: c_object
    ...
    call callback(c_object)
    ...
end function foo

As I said, all of this works well and has done so for a long time.

Now for the things I've tried but which don't work:

The naive approach, just copying the arguments into the fields of a structure

I'd expect this to work, since all all I'm doing is to copy the original elements into a structure with no modification. Nothing changes on the C side, nor in the definition of the main Fortran function nor the abstract interface to c_callback. All I do is to create a new Fortran derived type:

type :: callback_data
    procedure(c_callback), pointer, nopass :: callback => null()
    type(c_ptr) :: c_object
end type callback_data

and then in my main function I populate this with the values received from the C application:

data%callback_data%callback => callback
data%callback_data%c_object = c_object
call foo(data)

The subroutine foo has been slightly modified so that it now looks for the callback and C object within the structure:

subroutine foo(data)
    type(my_augmented_type), intent(inout) :: data
    ...
    call data%callback_data%callback(data%callback_data%c_object)
    ...
end function foo

This fails at the call with an "access violation reading location 0xffffffffffffffff".

The sophisticated approach using more of the iso_c_binding features

Again nothing changes on the C side but I modify the Fortran side of the main function to receive the callback as a c_funptr:

integer(c_int) fortran_function(n, image, callback, c_object)     &
                                bind(c, name='fortran_function')

    integer(c_int), value :: n
    integer(4), intent(inout), dimension(n) :: image
    type(c_funptr), intent(in) :: callback
    type(c_ptr), intent(in) :: c_object

I define the abstract interface to subroutine c_callback just as before, though I've experimented both with leaving the bind(c) part of it in, and omitting it. The code within the main function that calls the subroutine foo is now:

call c_f_procpointer(callback, data%callback_data%callback)
data%callback_data%c_object = c_object
call foo(data)

...with the subroutine foo itself still defined as in the previous example.

Unfortunately this fails in exactly the same way as the previous example.

I assume that there is a correct syntax to achieve what I'm trying to achieve here, and I'd be very grateful for any advice.

Closer answered 16/1, 2014 at 22:51 Comment(2)
You are passing a intent(in) pointer by reference. Is that intentional? Both your first "working" approach and your naive approach don't conform due to the way that you say you are handling the procedure. Please show a) the C prototype of the Fortran procedure that the C calls b) the actual C call to the fortran (that passes the callback), c) the interface of the Fortran procedure that the C calls, and d) the prototype of the C callback function.Caracalla
Thank you IanH; I'm not sure I understand your point and my quoted code is obviously a bit oversimplified. I'll edit the question and give a few more details.Closer
C
4

A dummy argument in a Fortran procedure with the BIND(C) attribute that doesn't have the VALUE argument is equivalent on the C side to a pointer parameter (this is broadly consistent with the usual Fortran convention of things being passed by reference). So if on the Fortran side you have INTEGER(C_INT) :: a (no value attribute), that's equivalent on the C side to int *a.

Perhaps that's obvious, but it has a surprising consequence - if you have TYPE(C_PTR) :: p, that's equivalent to void **p - a C_PTR is a pointer, so a C_PTR passed without value is a pointer to a pointer. Given this, your interface for the callback is out (you need to add VALUE).

The interoperable analogue in a type sense to a C pointer to a function (which is what a function name sans parentheses is in C) in Fortran is a TYPE(C_FUNPTR). The same considerations with respect to the absence of the VALUE attribute and C_PTR apply - an argument declared TYPE(C_FUNPTR) :: f is a pointer to a pointer to a function. Given this and your C side call of the Fortran, the argument corresponding to the function pointer should have the VALUE attribute.

The fact that a Fortran procedure pointer happens to work is just a (not terribly surprising) coincidence of the underlying implementation of C function pointers and Fortran procedure pointers, and the way that Fortran procedure pointers are passed.

All up, your Fortran procedure probably needs to have an interface that looks like:

integer(c_int) fortran_function(n, image, callback, c_object)     &
                                bind(c, name='fortran_function')

  integer(c_int), value :: n
  integer(c_signed_char), intent(inout), dimension(n) :: image
  type(c_funptr), intent(in), value :: callback
  type(c_ptr), intent(in), value :: c_object

(your declaration of the image array in your original code seems astray - perhaps the above is appropriate, perhaps not)

and your declaration of the interface of the C callback needs to have an interface of:

abstract interface
  subroutine c_callback(c_object) bind(c)   
    use, intrinsic :: iso_c_binding
    implicit none
    type(c_ptr), intent(in), value :: c_object     
  end subroutine c_callback
end interface

(As discussed on the Intel fora over the last few months (where have you been?), current ifort may have a problem with it's handling of C_PTR and VALUE.)

Caracalla answered 17/1, 2014 at 10:18 Comment(2)
IanH, you are a gentleman and a scholar! That fixes the problem. What I did was to re-implement my 'sophisticated' approach as described in the original question, but adding the 'value' qualifiers in the places you suggested. Now it works.Closer
By the way, the Intel forum for some reason doesn't let me post anything there: I just get rejected as a spammer! Intel Premier Support are looking into this for me. I'd missed any recent discussion of C_PTR and VALUE, although I did a search in the forum for any mention of C_FUNPTR and found nothing relevant. For what it's worth, I did my successful test just now using version 14.0.1.139 of the ifort compiler.Closer

© 2022 - 2024 — McMap. All rights reserved.