Community
cancel
Showing results for 
Search instead for 
Did you mean: 
eos_pengwern
Beginner
223 Views

Passing an interoperable C struct to Fortran by value gives 24-byte offset

I have a C struct and a corresponding Fortran derived type which is defined with bind(c), and have successfully been passing instances back and forth using the syntax (in C):

myFortranRoutine(&myInteroperableStruct);

...and in Fortran:

subroutine myFortranRoutine(c_myInteroperableStruct) bind(c, name='myFortranRoutine')

    type(c_ptr), value :: c_myInteroperableStruct

    type(interoperableStruct), pointer :: myInteroperableStruct

    call c_f_pointer(c_myInteroperableStruct, myInteroperableStruct)

    call doInterestingStuff(myInteroperableStruct)

end subroutine myFortranRoutine

For a variety of reasons it would be convenient to change the C call from passing a pointer to passing-by-value in cases where I don't require the struct to be changed by the Fortran routine. In other words, in C I'd like to write:

myFortranRoutine(myInteroperableStruct);

...and on the Fortran side I'd put:

subroutine myFortranRoutine(myInteroperableStruct) bind(c, name='myFortranRoutine')

    type(interoperableStruct), value:: myInteroperableStruct

    call doInterestingStuff(myInteroperableStruct)  
                                            ! Obviously, any changes to myInteroperableStruct won't
                                            ! be returned to the C application

end subroutine myFortranRoutine

The trouble is that, when I do this, the application compiles without any errors but at runtime there is a 24-byte offset in the structs: that is to say, in the Fortran code, the first 24 bytes of the struct contain apparent garbage, and the rest of the struct contains exactly what it should but with a fixed offset of 24 bytes.

Is this because I've got the syntax wrong, or simply because I'm doing something which isn't supported?

 

0 Kudos
16 Replies
Steven_L_Intel1
Employee
223 Views

Please come up with a buildable and runnable example that demonstrates the problem. You've left out too much that could be important, especially the declaration of interoperableStruct. My experience is that paraphrases and snippets usually obscure the real problem.

jimdempseyatthecove
Black Belt
223 Views

If you pass the C struct by value then it will have to be pushed onto the stack. Why not on the C++ side, declare the function prototype as taking reference and on the Fortran side declare the argument with type you have (not as c_ptr). This should work.... since both are references. Give it a try.

This should work with user defined type but not with arrays that require a descriptor.

You will still have to work out issues between languages with regard to packing of structs.

Jim Dempsey

eos_pengwern
Beginner
223 Views

Thank you both. I haven't tried Jim's suggestion yet, but here's what I did in response to Steve's...

Firstly, the C code:

#include "stdafx.h"

struct interoperableStruct {
	int myVariables[10];
};

extern "C" __declspec(dllimport) void __cdecl myFortranRoutine(interoperableStruct myStruct);

int _tmain(int argc, _TCHAR* argv[])
{
	interoperableStruct myInteroperableStruct;
	for (int i = 0; i < 10; ++i)
	{
		myInteroperableStruct.myVariables = i;
	}
	myFortranRoutine(myInteroperableStruct);
	return 0;
}

Then the Fortran, which I wrote as a DLL to reflect the usage in my own application:

module myFortranLibrary
    
    use, intrinsic :: iso_c_binding
    implicit none
    
    type, bind(c) :: interoperableStruct
        integer(c_int), dimension(10) :: myVariables
    end type interoperableStruct
    
contains
    
    subroutine myFortranRoutine(myInteroperableStruct) bind(c, name='myFortranRoutine')

!DEC$ ATTRIBUTES DLLEXPORT :: myFortranRoutine
    
        type(interoperableStruct), value :: myInteroperableStruct
        integer :: i
        
        do i=1,10
            print *, myInteroperableStruct%myVariables(i)
        end do

    end subroutine myFortranRoutine
    
end module myFortranLibrary

Here’s the funny thing. When I compiled this in 32-bit mode, everything worked absolutely fine, as it ought to have done. However, when I recompiled exactly the same source code in 64-bit mode (as my own application is 64-bit), it failed completely: that is to say, there were no compilation or runtime errors, but on the Fortran side every field of the struct was filled with #CCCCCCCC. 

I double-checked that I had the same alignment in each case (compiling the C++ code with /Zp8 and the Fortran with /align:rec8byte rather than relying on the defaults), but this made no difference.

So I went back to passing the struct by reference. The changes to the C code were trivial, and the revised Fortran code was now:

module myFortranLibrary
    
    use, intrinsic :: iso_c_binding
    implicit none
    
    type, bind(c) :: interoperableStruct
        integer(c_int), dimension(10) :: myVariables
    end type interoperableStruct
    
contains
    
    subroutine myFortranRoutine(c_myInteroperableStruct) bind(c, name='myFortranRoutine')

!DEC$ ATTRIBUTES DLLEXPORT :: myFortranRoutine
    
        type(c_ptr), value :: c_myInteroperableStruct
        type(interoperableStruct), pointer :: myInteroperableStruct
        integer :: i
        
        call c_f_pointer(c_myInteroperableStruct, myInteroperableStruct)
        
        do i=1,10
            print *, myInteroperableStruct%myVariables(i)
        end do

    end subroutine myFortranRoutine
    
end module myFortranLibrary

Just as in my own application, this worked absolutely fine in 32 and 64-bit mode.

So I’m none the wiser. It seems that there is definitely a problem when passing structs by value in 64-bit mode, though not in 32-bit mode, but even in 64-bit mode I don’t see quite the same failure in the test application as I do in my own application. I’d really appreciate any further suggestions.

 

Mark_Lewy
New Contributor II
223 Views

Which version?  I had a similar issue with a user-defined type argument with the value attribute not being passed correctly in XE 2013 SP1 update 1.  I was going to create a premier support issue for this, but noticed that it had been fixed in update 2.  My suspicion is this was fixed by DPD200249357 (VALUE attribute inconsistently applied in 14.0 with structure), could be the case for your problem too.

eos_pengwern
Beginner
223 Views

I'm running SP1 Update 3, installed about a fortnight ago.

eos_pengwern
Beginner
223 Views

In case it's remotely relevant, I attach my test project (which is a Visual Studio 2013 Solution containing Microsoft C++ and Intel Fortran projects).

Steven_L_Intel1
Employee
223 Views

I can see something wrong is happening, but I am not exactly sure what. What I do see is that both the C and Fortran code is making a copy of your struct - C is passing a copy and Fortran is making a copy of what is passed in. Fortran doesn't seem to be copying the right thing, though. We will investigate.

Steven_L_Intel1
Employee
223 Views

Correction - Fortran is not making a copy. But it still gets the address wrong. An all-Fortran example that ought to behave identically is working ok, so I need to dig deeper.

eos_pengwern
Beginner
223 Views

Thank you Steve; I appreciate your attention!

eos_pengwern
Beginner
223 Views

As I needed to bolt down my API and move on, I've done this a different way.

My reason for wanting to pass my structs by value was to make it absolutely obvious when a Fortran call was likely to change a function argument, so that in a C function call like:

    myFortranFunction(&firstArg, secondArg);

it would be clear that firstArg might be modified but secondArg wouldn't.

Of course, this only holds in C anyway, since in C++ there'd be nothing to stop someone from rewriting myFortranFunction in C++ and passing secondArg as a reference, in which case it absolutely could be modified. Anyway, I can achieve my objective just by making the second argument a const pointer, so in the C header:

void myFortranFunction(interoperableStruct *firstArg, const interoperableStruct *secondArg);

...and in the program:

myFortranProgram(&firstArg, &secondArg);

...all of which is slightly less aesthetically pleasing, but just as precise. 

jimdempseyatthecove
Black Belt
223 Views

You could write multiple same named shell functions with different argument signatures. Those without pointer to object get copied on the C++ side, than call made with pointer version to temporary copy. This way you can use the & for modifiable arguments and non-& arg for non-modifiable.

I might add that you can also use the make copy to consolidate a C++ multi-dimension array (pointer to pointers) that is non-contiguous. This will simplify construction the array descriptor on the Fortran side.

Jim Dempsey

Steven_L_Intel1
Employee
223 Views

This is very strange. The other day when I took your ZIP and rebuilt it, I could swear that I saw the printed output wrong. But when I try it today, with a fresh copy of your ZIP, the output is correct. But what is consistently wrong is the view of the variable in the debugger. The debugger thinks the dummy argument is allocated at a different location than where it is - the distance between the two is not consistent.

Is this what you're seeing?

eos_pengwern
Beginner
223 Views

Thanks Jim for your earlier comment.

And thanks Steve, yes this is what I see. When I raised the issue I wasn't even paying much attention to what got printed out in the console, as I was mainly looking at the debugger which was definitely awry. I repeated the experiment myself today and sure enough, the debugger output is wrong but the console output is right.

By the way, I've been working on something vaguely related in an all-C project today, and I've noticed some comparable behaviour, though not just affecting the debugger. If I pass a struct by value, then the subroutine receives the struct as intended and runs properly, but on returning to the main routine the struct which was passed (and which should, if I understand what's meant to happen under the hood, have been copied onto the stack without being affected itself in any way) becomes corrupted. If I pass the same struct using a const pointer or (in the C++ parts of the code) a const reference, then everything is absolutely fine and works without errors. I found a couple of cases where the Intel C compiler failed but the Microsoft one didn't, and yet there were also plenty of cases where the Microsoft one did as well.

All told, while my understanding of the relevant language standards is that passing structs by value should work (so long as they're small enough not to cause a stack overflow, and none of mine are bigger than 100 bytes), in practice it's just causing me needless trouble. I'm probably just doing it wrong, but as there's a simple enough workaround I think I should just stick with that.

 

Steven_L_Intel1
Employee
223 Views

I'm sure the Intel C++ developers would be interested in seeing an example that failed.

I will report the Fortran debugging issue to the (Fortran) developers.

eos_pengwern
Beginner
223 Views

Thanks Steve. I'll send a report to the Premier Support people.

eos_pengwern
Beginner
223 Views

As a matter of fact, there is nothing to report; the error was mine.

In the all-C case I mentioned yesterday,  the object being passed by value was actually a class with a set of constructors and a destructor. When the copy onto the stack was made, a syntax error on my part caused the wrong constructor to be called, so that some internal data was not copied correctly. This left my new object on the stack with a pointer which was still pointing to a memory buffer in my old object. At the end of the subroutine, the right destructor was called, freeing the memory buffer in the old object so that when control returned from the subroutine my old object which had been copied had nevertheless become corrupted!

I guess the reason for the difference in behaviour between Intel and Microsoft C++ was that, in a case which was basically undefined behaviour because I'd failed to specify a correct constructor, the different compilers took different guesses as to which alternative to use.

This is a reason why I love Fortran - it saves you from this kind of pain! It's also a salutary lesson why passing C structs and classes by value is a bad and dangerous thing to do (unless there is a really good reason such as the copy-and-swap idiom, but such nasties have no place in a civilised Fortran forum).

Reply