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

Fortran/C++ interopability (passing void pointers)

eos_pengwern
Beginner
2,141 Views
Hello,

This may be as much a C++ question as a Fortran question, but I figure I'm more likely to find C++ expertise here than to find Fortran expertise in a C++ forum!

I'm trying to implement the procedure, described (among other places) in section 14.10 of Metcalf Reid and Cohen, of having a Fortran routine set up a non-interopable data structure, send a pointer to it back to a C++ program, and then have C++ send the pointer back to the Fortran routine when it wants to access the structure. I have set up a pair of very simple test programs...

First, here is the Fortran routine that takes two numbers, adds them together, puts the result into a structure and returns a pointer to the structure using "c_loc"; a second subroutine receives the pointer, accesses the result and sends it back as an ordinary number:

[cpp]module ExampleDll

    use iso_c_binding
    type :: ResultObject
        real(c_float) :: c
    end type ResultObject
    
contains

    subroutine add(c_result, a, b) bind(c, name='add')
      !DEC$ ATTRIBUTES DLLEXPORT :: add
      type(c_ptr), intent(out) :: c_result
      real(c_float), intent(in) :: a, b
      type(ResultObject), pointer :: result
      allocate (result)
      c_result=c_loc(result)
      result%c = a + b  
    end subroutine add
    
    subroutine return_result(c, c_result) &
               bind(c, name='return_result')
      !DEC$ ATTRIBUTES DLLEXPORT :: return_result
      real(c_float), intent(out) :: c
      type(c_ptr), intent(in) :: c_result
      type(ResultObject), pointer :: result
      call c_f_pointer(c_result, result)
      c = result%c
    end subroutine return_result
    
end module ExampleDLL[/cpp]

Now, here is the C program that gets two numbers from the console, calls the Fortran routine to add them together, receives the pointer, and then invokes the pointer to recover the result:

[cpp]#include "stdafx.h"
#include 

extern "C" _declspec(dllimport) void add(void *, const float &, const float &);
extern "C" _declspec(dllimport) void return_result(float &, void *);

int _tmain(int argc, _TCHAR* argv[])
{
    float  a, b, c;
    void   *result = 0;

    std::cout << "Enter two numbers:";
    std::cin >> a >> b;
    add(result, a, b);

    return_result(c, result);
    std::cout << "nThe sum of " << a << " and " << b << " is " << c << std::endl;
    return 0;
}[/cpp]

Both programs compile and link without errors - I have set them up with the Fortran code in a DLL, but I expect it could just as well be statically linked. However, I get a runtime error on line 16 of the Fortran program [i.e. "c_result = c_loc(result)"], stating "Access violation writing location 0x00000000". According to the Fortran debugger, however, it has by this time successfully received the two numbers from the C++ program, and allocated the 'ResultObject'.

It looks as though the Fortran routine either can't see, or is in some way offended by, the memory location which C++ has reserved for the pointer by declaring "void *result". I can't see how else to do this, however. Does anyone have any suggestions?

Many thanks,
Stephen.
0 Kudos
1 Solution
IanH
Honored Contributor III
2,141 Views
(You'd worked it out already before I finished, but there are some other thoughts below anyway, so sod it, I'm posting...)

You have some pass by reference/pass by value mismatches. Consider adding an additional level of indirection to your C++ declaration for add and its associated call:

[cpp]extern "C" void add(void **, ...)
...
add(&result, a, b);[/cpp]

Otherwise, there's no way that the address that the void * holds on the C++ side can be changed. Your fortran code is already set up to expect the address of a pointer.

An alternative is to have the pointer returned as the result of a function call, ie:

[cpp]FUNCTION add(a, b) RESULT(c_result) BIND(C, NAME='add')
...
[/cpp]

and

[cpp]extern "C" void* add(float*, float*);
result = add(&a, &b); [/cpp]

Similarly, the return procedure should take the pointer by value on the fortran side (as the fortran doesn't need to change it):

[plain]type(c_ptr), intent(in), VALUE :: c_result[/plain]

Alternatively (but not in addition) add in the extra level of indirection as per the add declaration and call above.

An unrelated technicality is that the interoperability is formally with C, and C doesn't have references. I know that references are often implemented as pointers, but I'm not sure whether there's a guarantee in C++ that calling conventions will be the same. For "add", you could change both the C++ and Fortran so that the float's are passed by value.

View solution in original post

0 Kudos
6 Replies
eos_pengwern
Beginner
2,141 Views
Step 1: Wander off, do something else, come back, read Metcalf Read and Cohen again...
Step 2: Replace "result" with "&result" in lines 14 and 16 of the C++ program.
Step 3: Recompile, and find everything works perfectly.

Maybe in about five years' time I'll have got used to the C++ pointer reference, address and dereference syntax.


0 Kudos
TimP
Honored Contributor III
2,141 Views
I have to admit, I wonder about C++ references passing through extern "C." I guess it's almost "guaranteed" to work where we happen to know that the Fortran mechanism is "by reference."
0 Kudos
IanH
Honored Contributor III
2,142 Views
(You'd worked it out already before I finished, but there are some other thoughts below anyway, so sod it, I'm posting...)

You have some pass by reference/pass by value mismatches. Consider adding an additional level of indirection to your C++ declaration for add and its associated call:

[cpp]extern "C" void add(void **, ...)
...
add(&result, a, b);[/cpp]

Otherwise, there's no way that the address that the void * holds on the C++ side can be changed. Your fortran code is already set up to expect the address of a pointer.

An alternative is to have the pointer returned as the result of a function call, ie:

[cpp]FUNCTION add(a, b) RESULT(c_result) BIND(C, NAME='add')
...
[/cpp]

and

[cpp]extern "C" void* add(float*, float*);
result = add(&a, &b); [/cpp]

Similarly, the return procedure should take the pointer by value on the fortran side (as the fortran doesn't need to change it):

[plain]type(c_ptr), intent(in), VALUE :: c_result[/plain]

Alternatively (but not in addition) add in the extra level of indirection as per the add declaration and call above.

An unrelated technicality is that the interoperability is formally with C, and C doesn't have references. I know that references are often implemented as pointers, but I'm not sure whether there's a guarantee in C++ that calling conventions will be the same. For "add", you could change both the C++ and Fortran so that the float's are passed by value.
0 Kudos
TimP
Honored Contributor III
2,141 Views
Quoting - IanH


An unrelated technicality is that the interoperability is formally with C, and C doesn't have references. I know that references are often implemented as pointers, but I'm not sure whether there's a guarantee in C++ that calling conventions will be the same. For "add", you could change both the C++ and Fortran so that the float's are passed by value.
The attitude seems to be that extern "C" does nothing but remove data type dependent mangling, and that apparently iso_c_binding makes Fortran pass by reference (unless value is specified). I'm still unsure whether to differentiate my calls to C++ from Fortran from calls to C.
0 Kudos
eos_pengwern
Beginner
2,141 Views
Quoting - IanH
An unrelated technicality is that the interoperability is formally with C, and C doesn't have references. I know that references are often implemented as pointers, but I'm not sure whether there's a guarantee in C++ that calling conventions will be the same. For "add", you could change both the C++ and Fortran so that the float's are passed by value.

Thanks IanH; I had you in mind as someone likely to have expertise on this subject.

The point about the differences between C and C++ in this context is a good one, which I hadn't thought of. In practice (i.e. when working on actual applications rather than test cases), I usually need to pass arrays rather than scalars and these are sent as pointers anyway. When passing scalars, you're quite right that it is more correct to pass them by value, or as pointers, rather than by reference.

For the sake of pragmatism, however, I verified that passing-by-reference (per my original example) works both when the C++ code is compiled by Microsoft C++ and when it is compiled by MinGW.

Stephen.
0 Kudos
Steven_L_Intel1
Employee
2,141 Views
Glad to hear you figured it out.

It is important to note, as Tim says, BIND(C) does not change to pass-by-value. There is a VALUE attribute if you want that. (The VALUE attribute has an additional effect on a Fortran routine - a local, writeable copy of a VALUE dummy argument is created and then discarded on exit, so it's not exactly the same as !DEC$ ATTRIBUTES VALUE, just as BIND(C) is not the same as ATTRIBUTES C (in so many ways!)
0 Kudos
Reply