- Mark as New
- Bookmark
- Subscribe
- Mute
- Subscribe to RSS Feed
- Permalink
- Report Inappropriate Content
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:
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:
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.
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" #includeextern "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.
1 Solution
- Mark as New
- Bookmark
- Subscribe
- Mute
- Subscribe to RSS Feed
- Permalink
- Report Inappropriate Content
(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:
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:
and
Similarly, the return procedure should take the pointer by value on the fortran side (as the fortran doesn't need to change it):
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.
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.
Link Copied
6 Replies
- Mark as New
- Bookmark
- Subscribe
- Mute
- Subscribe to RSS Feed
- Permalink
- Report Inappropriate Content
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.
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.
- Mark as New
- Bookmark
- Subscribe
- Mute
- Subscribe to RSS Feed
- Permalink
- Report Inappropriate Content
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."
- Mark as New
- Bookmark
- Subscribe
- Mute
- Subscribe to RSS Feed
- Permalink
- Report Inappropriate Content
(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:
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:
and
Similarly, the return procedure should take the pointer by value on the fortran side (as the fortran doesn't need to change it):
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.
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.
- Mark as New
- Bookmark
- Subscribe
- Mute
- Subscribe to RSS Feed
- Permalink
- Report Inappropriate Content
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.
- Mark as New
- Bookmark
- Subscribe
- Mute
- Subscribe to RSS Feed
- Permalink
- Report Inappropriate Content
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.
- Mark as New
- Bookmark
- Subscribe
- Mute
- Subscribe to RSS Feed
- Permalink
- Report Inappropriate Content
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!)
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!)

Reply
Topic Options
- Subscribe to RSS Feed
- Mark Topic as New
- Mark Topic as Read
- Float this Topic for Current User
- Bookmark
- Subscribe
- Printer Friendly Page