passing char arrays from c++ to fortran
Asked Answered
O

3

6

I am having trouble passing char arrays from c++ to fortran (f90).

Here is my c++ file, 'cmain.cxx':

#include <iostream>

using namespace std;

extern "C" int ftest_( char (*string)[4] );

int main() {
    char string[2][4];

    strcpy(string[0],"abc");
    strcpy(string[1],"xyz");

    cout << "c++: string[0] = '" << string[0] << "'" << endl;
    cout << "c++: string[1] = '" << string[1] << "'" << endl;

    ftest_(string);

    return 0;
}

Here is my fortran file, 'ftest.f90':

SUBROUTINE FTEST(string)

CHARACTER*3 string(2)
CHARACTER*3 expected(2)
data expected(1)/'abc'/
data expected(2)/'xyz'/

DO i=1,2
    WRITE(6,10) i,string(i)
10  FORMAT("fortran: string(",i1,") = '", a, "'" )

    IF(string(i).eq.expected(i)) THEN
        WRITE(6,20) string(i),expected(i)
20      FORMAT("'",a,"' equals '",a,"'")
    ELSE
        WRITE(6,30) string(i),expected(i)
30      FORMAT("'",a,"' does not equal '",a,"'")
    END IF
ENDDO

RETURN
END

The build process is:

gfortran -c -m64   ftest.f90 
g++ -c  cmain.cxx
gfortran -m64 -lstdc++ -gnofor_main -o test ftest.o cmain.o

Edit: note that the executable can also be build via:

g++ -lgfortran -o test ftest.o cmain.o

Also, the -m64 flag is required as I am running OSX 10.6.

The output from executing 'test' is:

c++: string[0] = 'abc'
c++: string[1] = 'xyz'
fortran: string(1) = 'abc'
'abc' equals 'abc'
fortran: string(2) = 'xy'
'xy' does not equal 'xyz'

Declaring the 'string' and 'expected' character arrays in ftest.f90 with size 4, ie:

CHARACTER*4 string(2)
CHARACTER*4 expected(2)

and recompiling gives the following output:

c++: string[0] = 'abc'
c++: string[1] = 'xyz'
fortran: string(1) = 'abc'
'abc' does not equal 'abc '
fortran: string(2) = 'xyz'
'xyz' does not equal 'xyz '

Declaring the character arrays in 'cmain.cxx' with size 3, ie:

extern "C" int ftest_( char (*string)[3] );

int main() {
    char string[2][3];

and reverting to the original size in the fortran file (3), ie:

CHARACTER*3 string(2)
CHARACTER*3 expected(2)

and recompiling gives the following output:

c++: string[0] = 'abcxyz'
c++: string[1] = 'xyz'
fortran: string(1) = 'abc'
'abc' equals 'abc'
fortran: string(2) = 'xyz'
'xyz' equals 'xyz'

So the last case is the only one that works, but here I have assigned 3 characters to a char array of size 3 which means the terminating '\0' is missing, and leads to the 'abcxyz' output - this is not acceptable for my intended application.

Any help would be greatly appreciated, this is driving me nuts!

Ogren answered 15/4, 2012 at 15:43 Comment(3)
I see no sign that you are using Fortran's 'Interoperability with C' capabilities which are designed to ease such problems as you are facing. I suggest you do use them.Ettieettinger
Hi Mark, I have an externally provided fortran program that I wish to interface with via c++. I cannot modify the fortran code. Can you see a way to get this to work without modifying the original fortran file?Ogren
It depends on the actual type of interfacing you are doing, but if you have the Fortran code, even if you can't change it, you could add a C interface module to it. The module would just make some conversion functions visible to C. As a bonus, you could get rid of the trailing _ in function names.Malleable
H
13

C strings are zero terminated whereas fortran strings, by convention, are space padded but of fixed size. You shouldn't expect to be able to pass C strings to fortran without some conversion.

For example:

#include <algorithm>

void ConvertToFortran(char* fstring, std::size_t fstring_len,
                      const char* cstring)
{
    std::size_t inlen = std::strlen(cstring);
    std::size_t cpylen = std::min(inlen, fstring_len);

    if (inlen > fstring_len)
    {
        // TODO: truncation error or warning
    }

    std::copy(cstring, cstring + cpylen, fstring);
    std::fill(fstring + cpylen, fstring + fstring_len, ' ');
}

Which you can then use with either the 3 or 4 length version of ftest:

#include <iostream>
#include <ostream>
extern "C" int ftest_( char string[][4] );

void ConvertToFortran(char* fstring, std::size_t fstring_len,
                      const char* cstring);

int main()
{
    char cstring[2][4] = { "abc", "xyz" };
    char string[2][4];

    ConvertToFortran(string[0], sizeof string[0], cstring[0]);
    ConvertToFortran(string[1], sizeof string[1], cstring[1]);

    std::cout << "c++: string[0] = '" << cstring[0] << "'" << std::endl;
    std::cout << "c++: string[1] = '" << cstring[1] << "'" << std::endl;

    ftest_(string);

    return 0;
}
Headon answered 15/4, 2012 at 16:25 Comment(0)
I
5

I recommend using the ISO C Binding on the Fortran side as suggested by "High Performance Mark". You are already using "extern C". The ISO C Binding of Fortran 2003 (currently implemented in most Fortran 95 / partial Fortan 2003 compilers) makes this a compiler and platform independent approach. Charles Bailey described the differences between strings in the two languages. This Stackoverflow question has a code example: Calling a FORTRAN subroutine from C

If you don't want to modify existing Fortran code you could write a "glue" routine in between your C++ code and the existing Fortran code. Writing the glue routine in Fortran using the ISO C Binding would be more reliable and stable since this would be based on the features of a language standard.

Inextricable answered 15/4, 2012 at 18:35 Comment(0)
S
2

The examples given are far too heavyweight, as long as you don't want to pass more than one string you can make use of the "hidden" length parameter ...

extern "C" void function_( const char* s, size_t len )  {  
  std::string some_string( s, 0, len );
  /// do your stuff here ...
  std::cout << "using string " << some_string << std::endl;
  /// ...

}

which you can call from fortran like

  call function( "some string or other" )

You don't need to faff about with individual copy operations, since the std::string constructor can do all that for you.

Selfcontained answered 21/9, 2015 at 8:55 Comment(2)
Maybe they are heavyweight (but it is not my opinion, adding bind(C) is simple), but they are portable. There is nothing in the Fortran standard which says that the function symbol will be the name with the underscore and there is nothing which guarantees the type of the hidden argument and its position in the argument list. There is even no guarantee there is any hidden argument.Malleable
Thanks, although the example fortran wrapper one would need to write in eg #8208497 is significantly more complicated than my 3 line example, so I will stick with my approach.Selfcontained

© 2022 - 2024 — McMap. All rights reserved.