简体   繁体   中英

passing char arrays from c++ to fortran

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!

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;
}

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.

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.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM