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

Fortran 2022 treats value type from C++ as address

Martin__Paul
New Contributor I
1,621 Views

Hi,

I am in the process of upgrading a large mixed language (C++/Fortran) solution from Visual Studio & Fortran 2013 to Visual Studio 2022 (17.1.6) and Fortran 2022 (2022.1.0.139).

I have many examples of trivial getter/setter subroutines in Fortran for marshalling data from C++, for example:

SUBROUTINE SET_SOMENUMBER(SOMENUMBER)
USE MSOMEMODULE, ONLY: C_SOMENUMBER
IMPLICIT NONE
INTEGER*4, VALUE :: SOMENUMBER
C_SOMENUMBER = SOMENUMBER
END

Calling code from C++:

extern "C"
{

void __stdcall set_somenumber(int SOMENUMBER);

}

...

const int numbertopass = 1;
set_somenumber(numbertopass);

 

In the 2013 solution this all works fine.

Following the upgrade, the debugger breaks in the Fortran subroutine with a read access violation exception on the line where the assignment occurs.  The watch window reports "Undefined address" on the incoming value, suggesting it may be being erroneously interpreted as an address (0x1) and not a value - which would of course be expected to blow up.

If I change the code to pass by reference, it works fine, for example:

SUBROUTINE SET_SOMENUMBER(SOMENUMBER)
USE MSOMEMODULE, ONLY: C_SOMENUMBER
IMPLICIT NONE
INTEGER*4 :: SOMENUMBER
C_SOMENUMBER = SOMENUMBER
END

Calling code:

extern "C"
{

void __stdcall set_somenumber(const int& SOMENUMBER);

}

...

const int numbertopass = 1;
set_somenumber(numbertopass);

 

My Fortran compiler command is similar to:

"C:\Program Files (x86)\Intel\oneAPI\compiler\2022.1.0\windows\bin\intel64_ia32\ifort.exe" /nologo /debug:full /warn:interfaces /iface:cvf /names:lowercase /iface:nomixed_str_len_arg /module:"C:\someproject\int\debug\" /object:"C:\someproject\bin\debug\somecode.obj" /Fd"C:\someproject\int\debug\vc143.pdb" /traceback /check:bounds /check:stack /libs:static /threads /dbglibs /Qvc14.3 /Qlocation,link,"C:\Program Files\Microsoft Visual Studio\2022\Professional\VC\Tools\MSVC\14.31.31103\bin\Hostx86\x86" /c C:\someproject\somecode.for

 

Can anyone shed light on this?  Thanks.

0 Kudos
1 Solution
FortranFan
Honored Contributor II
1,597 Views

@Martin__Paul ,

See this thread circa 2013 and the comments by @Steve_Lionel regarding changes starting what was then version 14.0 of Intel Fortran compiler:

https://community.intel.com/t5/Intel-Fortran-Compiler/OPTIONAL-and-VALUE-attributes-unexpected-behaviour/td-p/977150

The above might be relevant to you but Intel team and/or @Steve_Lionel may best be able to explain to you the situation re: VALUE attribute in the absence of BIND(C,..) which is what you appear to have in your code.

 

View solution in original post

13 Replies
FortranFan
Honored Contributor II
1,598 Views

@Martin__Paul ,

See this thread circa 2013 and the comments by @Steve_Lionel regarding changes starting what was then version 14.0 of Intel Fortran compiler:

https://community.intel.com/t5/Intel-Fortran-Compiler/OPTIONAL-and-VALUE-attributes-unexpected-behaviour/td-p/977150

The above might be relevant to you but Intel team and/or @Steve_Lionel may best be able to explain to you the situation re: VALUE attribute in the absence of BIND(C,..) which is what you appear to have in your code.

 

Martin__Paul
New Contributor I
1,579 Views

Thanks, the link explains the behavior I am seeing here.

Adding BIND(C) has done the trick. I also needed to change the calling convention of the C++ function export to avoid a name decoration (leading underscore) mismatch:

extern "C"
{

void __cdecl set_somenumber(int SOMENUMBER);

}

SUBROUTINE SET_SOMENUMBER(SOMENUMBER) BIND(C)
USE MSOMEMODULE, ONLY: C_SOMENUMBER
IMPLICIT NONE
INTEGER*4, VALUE :: SOMENUMBER
C_SOMENUMBER = SOMENUMBER
END

I need to now decide which of changing my code everywhere to BIND(C) or using pass by reference would be less disruptive.

0 Kudos
andrew_4619
Honored Contributor II
1,570 Views

You could use  bind(C, name =..... )  to avoid changing the call convention maybe. "Integer(C_INT), value" would be nicer also

0 Kudos
Martin__Paul
New Contributor I
1,563 Views

I don't see how BIND(C, name=...) would avoid changing the calling convention - it forces the use of cdecl doesn't it?  Presumably I would also need  !DIR$ ATTRIBUTES STDCALL to go back to stdcall.

I agree about the the use of C types.  We do that in a few places but not consistently.

For now, I've settled on passing const references from the calling side. Seems to work.

0 Kudos
andrew_4619
Honored Contributor II
1,560 Views

if set_somenumber(numbertopass) on the C side expects a decorated name then you can name the routine of the Fortran side if you wanted to I would expect. Maybe I am misunderstanding something. 

0 Kudos
Steve_Lionel
Honored Contributor III
1,548 Views

Re: calling convention

Intel Fortran uses the C convention unless you explicitly override it. (On x64, there is only one calling convention.)  You can use !DIR$ ATTRIBUTES STDCALL with BIND(C) if needed.

As is said earlier, VALUE without BIND(C) means something different from what you wanted - it passes an anonymous, writable copy of the argument by reference. BIND(C) also prevents hidden arguments and should be used when calling other languages where possible. And if you're using C++, definitely use 'extern "C"' on the C++ side.

For naming, BIND(C) downcases the name and applies whatever name decoration the "companion C processor" would. Note that it doesn't say C++ - if you are using C++ you'll get a mangled name without 'extern "C"'. 

0 Kudos
Martin__Paul
New Contributor I
1,502 Views

Thanks for the info.  I retained __stdcall and changed the C++ callee to accept const references, which works fine.  After discussing with a colleague who has some Fortran experience, I also replaced VALUE with INTENT(IN).

I am now stuck on a possibly related marshalling issue.  As with the problem above, the code runs correctly with Composer 2013.

This time it concerns passing arrays to a C++ routine.  Here is a simplified example:

Fortran:

SUBROUTINE TEST()
IMPLICIT NONE
DOUBLE PRECISION array_1d(3)
DOUBLE PRECISION array_2d(2,2)

array_1d(1) = 1.D0/3.D0
array_1d(2) = 1.D0/7.D0
array_1d(3) = 1.D0/13.D0
array_2d(1,1) = 2.D0/3.D0
array_2d(1,2) = 2.D0/7.D0
array_2d(2,1) = 2.D0/11.D0
array_2d(2,2) = 2.D0/13.D0

CALL LOGARRAY([array_1d], SHAPE(array_1d))
CALL LOGARRAY([array_2d], SHAPE(array_2d))
RETURN
END SUBROUTINE TEST

SUBROUTINE LOGARRAY(elements, shape)
USE ISO_C_BINDING
IMPLICIT NONE
DOUBLE PRECISION, DIMENSION(*), INTENT(IN), TARGET :: elements
INTEGER, INTENT(IN), TARGET :: shape(:)

CALL logcpp(C_LOC(elements), SIZEOF(elements(1)), C_LOC(shape), SIZE(shape))

RETURN
END SUBROUTINE LOGARRAY

 

C++:

extern "C"

{

    void __stdcall logcpp(const char *elements, const int& element_size, const int *shape, const int& rank);

}

void __stdcall logcpp(const char *elements, const int& element_size, const int *shape, const int& rank)

{

 if (rank == 0) // Scalar

{
    log_data(&elements[0]);  // log shows &elements[0] has wrong value
}

else if (rank == 1) // Rank-1 array

{

    int nElem = shape[0];   // shape[0] is pointing to wrong memory (huge number)
    for (int i = 0; i < nElem; ++i)
    {
        log_data(&elements[i*element_size]);
    }
}

Thank you.

0 Kudos
Steve_Lionel
Honored Contributor III
1,493 Views

You DO want VALUE (and BIND(C), which I don't see in this example.) You DON'T want C_LOC. INTENT(IN) doesn't change how anything is passed. What you have now is passing an address by reference, an extra set of indirection.

What do you think the square brackets in [array_1d], etc., are doing for you? I suppose it makes this passing an expression rather than a variable, but I don't see the point.

Martin__Paul
New Contributor I
1,473 Views

Thanks Steve, you hit the nail on the head with C_LOC.  I don't know what the intention of the square brackets was I'm afraid (I'm not very experienced with Fortran, and the person who wrote this left).

I removed VALUE (example from the original post) because I changed the C++ interface in the trivial setter functions to accept const references.  They seem to work OK.

Here's the working code following your suggestions (key diffs in bold):

Fortran:

SUBROUTINE TEST()
IMPLICIT NONE
DOUBLE PRECISION array_1d(3)
DOUBLE PRECISION array_2d(2,2)

array_1d(1) = 1.D0/3.D0
array_1d(2) = 1.D0/7.D0
array_1d(3) = 1.D0/13.D0
array_2d(1,1) = 2.D0/3.D0
array_2d(1,2) = 2.D0/7.D0
array_2d(2,1) = 2.D0/11.D0
array_2d(2,2) = 2.D0/13.D0

CALL LOGARRAY(array_1d, SHAPE(array_1d))
CALL LOGARRAY(array_2d, SHAPE(array_2d))
RETURN
END SUBROUTINE TEST

SUBROUTINE LOGARRAY(elements, shape)
USE ISO_C_BINDING
IMPLICIT NONE
DOUBLE PRECISION, DIMENSION(*), INTENT(IN), TARGET :: elements
INTEGER, INTENT(IN), TARGET :: shape(:)

INTERFACE
  SUBROUTINE Log(elements, sizeOfElement, shape, sizeOfShape) BIND(C,NAME='logcpp')
    USE ISO_C_BINDING
    DOUBLE PRECISION elements
    INTEGER(KIND=C_SIZE_T), VALUE :: sizeOfElement
    INTEGER shape
    INTEGER, VALUE :: sizeOfShape
  END SUBROUTINE Log
END INTERFACE

CALL Log(elements(1), SIZEOF(elements(1)), shape(1), SIZE(shape))

RETURN
END SUBROUTINE LOGARRAY

C++:

extern "C" void __cdecl logcpp(const char* elements, const int elementSize, const int* shape, const int rank);

 

I actually got this cut-down example working without the interface and BIND(C).  However, my real code also passes a string and a function pointer (C_FUNLOC), and with these extra parameters, suddenly it doesn't like elements(1) and shape(1):

error #6633: The type of the actual argument differs from the type of the dummy argument. [ELEMENTS].

error #6633: The type of the actual argument differs from the type of the dummy argument. [SHAPE].

So perhaps the only way to get this working reliably (esp. when marshalling different C types in the same call) is to use an interface block with BIND(C), even though more trivial examples apparently don't always require this.

0 Kudos
Steve_Lionel
Honored Contributor III
1,459 Views

My understanding is that "const" is like INTENT(IN) and doesn't change how the argument is received. I can't help with the errors because you haven't shown the source that generates them - the code you posted doesn't.  I am a bit puzzled that on the C++ side elements is a char array but you're passing a double-precision array element to it.  What is the intention here?

0 Kudos
Martin__Paul
New Contributor I
1,449 Views

"My understanding is that "const" is like INTENT(IN) and doesn't change how the argument is received"

Yes, const is simply a compile-time check.

The char array is just a way of allowing Fortran to pass data of any type to the C++, along with a callback to the log routine appropriate to the type.  In a pure C++ app you would just use a method template.  The logcpp function can accept underlying INTEGER, DOUBLE PRECISION, REAL etc. data.

I tried to create a minimal example that reproduces the problem (including the string and function pointer) but it compiles without any problem.  The real code is too big to post but the example has all the key elements so I'm not sure quite what was going on with the error.  In any case, the code is stable with the interface - I'm going to run with that, just pleased to have this working at last!  Thanks again.

 

0 Kudos
Steve_Lionel
Honored Contributor III
1,446 Views

OK - in the future, you can declare the argument that accepts any type as TYPE(*). This is essentially the same as C's void and will match any type.

0 Kudos
Martin__Paul
New Contributor I
1,430 Views
0 Kudos
Reply