Community
cancel
Showing results for 
Search instead for 
Did you mean: 
damienhocking
New Contributor I
582 Views

passing an array of strings from Fortran to C

Jump to solution

Hi all, I'm trying to pass an array of strings allocated in Fortran to a C function that takes a char** argument.

The C function is this:

int SetupComponents(char** compnames, int numnames)
{
   for (int i = 0; i < numnames; i++)
   {

      /* Do setup work in here*/
   }
return 0;
}

An example of the Fortran code that calls this is:

character(len=10,kind=c_char), allocatable :: comps(:)
character(len=10,kind=c_char), allocatable :: c1, c2, c3, c4

c1 = "methane" ; c2 = "ethane"; c3 = "propane"; c4 = "i-butane"

allocate (comps(4))
comps = [c1, c2, c3, c4]
ok = SetupComponents(comps, 4)
deallocate (comps)

The Fortran wrapper to call this is (supposed to be...):

function SetupComponents(compnames, numnames) result(ok) bind(C, name="SetupComponents")
use, intrinsic :: ISO_C_BINDING
character(kind=c_char) :: compnames !WHAT GOES ON THIS LINE?
integer(c_int), value :: numnames
integer(c_int) :: ok
end function SetupComponents

I've tried various permutations of the "character(kind=c_char) :: compnames..." line but I'm either getting compiler errors about TARGETs or crashes inside the C function, where compnames is garbage but numnames is fine. I'm not a Fortran expert, I know just enough to be dangerous.

This is an API under development, so we can change any aspect of this to be standards-compliant. Any help would be appreciated.

Labels (1)
0 Kudos
1 Solution
damienhocking
New Contributor I
495 Views

In the interests of a complete, clean example for future reference, here's the finished code:

This is the C function to be called from Fortran:

int SetupComponents(char** compnames, int numnames);

 

This is the iso_c_binding function that is used to wrap the C function: 

(as per Steve Lionel, the char** compnames needs to be an array of c_ptr inside the definition)

function SetupComponents(compnames, numnames) result(ok) bind(C, name="SetupComponents")
use, intrinsic :: iso_c_binding
type (c_ptr), dimension(*) :: compnames
integer(c_int), value :: numnames
integer(c_int) :: ok
end function SetupComponents

 

This is the Fortran code that calls the function above. The names are now a dynamic array.

integer(4) :: howmany ! how many names are you going to have 

! this is the array of names, max 40 characters including null termination
character(len=40,kind=c_char), allocatable, target :: names(:) ! must be target in order to get the starting C address of each name, which is a C char*

type (c_ptr), dimension(:), allocatable :: comps ! this is what becomes the char** on the C side, an array of C char*, each entry is a starting address (see above)

howmany = 4 ! 4 names
allocate(names(howmany)) ! allocate the names array

! You must have //C_NULL_CHAR termination on dynamically allocated strings
! Otherwise on the C side there's no termination and this is very unpleasant
names(1) = "methane"//C_NULL_CHAR
names(2) = "ethane"//C_NULL_CHAR
names(3) = "propane"//C_NULL_CHAR
names(4) = "i-butane"//C_NULL_CHAR

allocate (comps(howmany)) ! allocate the storage for what the C char** points to
comps(1) = c_loc(names(1)) ! get the starting C address of each name
comps(2) = c_loc(names(2))
comps(3) = c_loc(names(3))
comps(4) = c_loc(names(4))

ok = SetupComponents(comps, howmany) ! call the C function

deallocate (comps) ! deallocate the C char** array
deallocate (names) ! deallocate the names

 

With thanks to @Steve_Lionel and @jimdempseyatthecove for their help.

 

View solution in original post

15 Replies
Steve_Lionel
Black Belt Retired Employee
564 Views

In C, char** is an array of pointers to strings. So you would need:

type(C_PTR), dimension(*) :: compnames

Then in your Fortran code you would need to allocate a local array of type(C_PTR) and use C_LOC on each element of the array passed in to fill in the array to be passed to the C routine. 

damienhocking
New Contributor I
553 Views

Thanks @Steve_Lionel that's very helpful. Here's what I have now with a simple example, which appears to work, I can see the names passed in now: 

C prototype:

int SetupComponents(char** compnames, int numnames);

Fortran example that calls the above:

type (c_ptr), dimension(:), allocatable :: comps
character(len=10,kind=c_char), allocatable, target :: c1, c2, c3, c4

c1 = "methane" ; c2 = "ethane"; c3 = "propane"; c4 = "i-butane"

allocate (comps(4))
comps = [c_loc(c1), c_loc(c2), c_loc(c3), c_loc(c4)]
ok = SetupComponents(comps, 4)
deallocate (comps)

And the interface is:

function SetupComponents(compnames, numnames) result(ok) bind(C, name="SetupComponents")
use, intrinsic :: ISO_C_BINDING
type (c_ptr), dimension(*) :: compnames
integer(c_int), value :: numnames
integer(c_int) :: ok
end function SetupComponents

This seems to work. Is there anything on the Fortran side I should tweak to get null terminations right or any memory running over the ends? 

jimdempseyatthecove
Black Belt
534 Views

You should terminate your Fortran strings with NULL when passing to C function.

c1 = "methane"C ; c2 = "ethane"C; c3 = "propane"C; c4 = "i-butane"C

c1 = "methane"//char(0) ; c2 = "ethane"//char(0); c3 = "propane"//char(0); c4 = "i-butane"//char(0)

Jim Dempsey

damienhocking
New Contributor I
526 Views
Steve_Lionel
Black Belt Retired Employee
514 Views

If you want to stick to the standard, concatenate with C_NULL_CHAR instead of the extension "xxx"C.

The next revision of the standard will (probably) have a F_C_STRING function that returns a NUL-terminated copy of its argument, making this a bit cleaner. (It also will probably have a C_F_STRPOINTER subroutine to convert a NUL-terminated string to a CHARACTER, POINTER of the proper length.)

damienhocking
New Contributor I
496 Views

In the interests of a complete, clean example for future reference, here's the finished code:

This is the C function to be called from Fortran:

int SetupComponents(char** compnames, int numnames);

 

This is the iso_c_binding function that is used to wrap the C function: 

(as per Steve Lionel, the char** compnames needs to be an array of c_ptr inside the definition)

function SetupComponents(compnames, numnames) result(ok) bind(C, name="SetupComponents")
use, intrinsic :: iso_c_binding
type (c_ptr), dimension(*) :: compnames
integer(c_int), value :: numnames
integer(c_int) :: ok
end function SetupComponents

 

This is the Fortran code that calls the function above. The names are now a dynamic array.

integer(4) :: howmany ! how many names are you going to have 

! this is the array of names, max 40 characters including null termination
character(len=40,kind=c_char), allocatable, target :: names(:) ! must be target in order to get the starting C address of each name, which is a C char*

type (c_ptr), dimension(:), allocatable :: comps ! this is what becomes the char** on the C side, an array of C char*, each entry is a starting address (see above)

howmany = 4 ! 4 names
allocate(names(howmany)) ! allocate the names array

! You must have //C_NULL_CHAR termination on dynamically allocated strings
! Otherwise on the C side there's no termination and this is very unpleasant
names(1) = "methane"//C_NULL_CHAR
names(2) = "ethane"//C_NULL_CHAR
names(3) = "propane"//C_NULL_CHAR
names(4) = "i-butane"//C_NULL_CHAR

allocate (comps(howmany)) ! allocate the storage for what the C char** points to
comps(1) = c_loc(names(1)) ! get the starting C address of each name
comps(2) = c_loc(names(2))
comps(3) = c_loc(names(3))
comps(4) = c_loc(names(4))

ok = SetupComponents(comps, howmany) ! call the C function

deallocate (comps) ! deallocate the C char** array
deallocate (names) ! deallocate the names

 

With thanks to @Steve_Lionel and @jimdempseyatthecove for their help.

 

View solution in original post

andrew_4619
Honored Contributor I
465 Views

I think this has been a neat thread for illustrating some usage of C interoperability. Just a small point in that the final code example set my OCD nerves on edge so I had to make a more compact version that was more parametric:

    subroutine OCD()
        use, intrinsic :: iso_c_binding
        implicit none (external)
        integer            :: howmany, J
        integer, parameter :: nmlen = 40
        integer(c_int)     :: OK
        character(len=nmlen,kind=c_char), allocatable, target :: names(:)
        type (c_ptr), allocatable                             :: comps(:)
        names = [character(nmlen) :: "methane"//C_NULL_CHAR, "ethane"//C_NULL_CHAR, &
                                     "propane"//C_NULL_CHAR, "i-butane"//C_NULL_CHAR   ]
        howmany = size( names )
        allocate (comps( howmany) )
        do J = 1 , howmany
            comps(J) = c_loc( names(J) ) 
        enddo
        ok = SetupComponents(comps, howmany)
    end subroutine OCD
mecej4
Black Belt
453 Views

How about, in addition, we leave out the "//C_NULL_CHAR"-s in the initializations, and append "// C_NULL_CHAR" to the argument of  C_LOC?

IanH
Black Belt
439 Views

@mecej4 wrote:

How about, in addition, we leave out the "//C_NULL_CHAR"-s in the initializations, and append "// C_NULL_CHAR" to the argument of  C_LOC?


You (and the compiler...) are off TARGET with that suggestion.

mecej4
Black Belt
438 Views

Thanks, Ian. What about:

 

 

    subroutine OCD()
        use, intrinsic :: iso_c_binding
        implicit none! (external)
        integer            :: howmany, J
        integer, parameter :: nmlen = 40
        integer(c_int)     :: OK
        character(len=nmlen,kind=c_char), allocatable, target :: names(:)
        type (c_ptr), allocatable                             :: comps(:)
        names = [character(nmlen) :: "methane", "ethane", &
                                     "propane", "i-butane"   ]
        howmany = size( names )
        allocate (comps( howmany))
        do J = 1 , howmany
            names(J) = trim(names(J)) // C_NULL_CHAR
            comps(J) = c_loc( names(J)) 
        enddo
        ok = SetupComponents(comps, howmany)
    end subroutine OCD

P.S. Modfied code to correct errors pointed out by GVautier below.

 

 

jimdempseyatthecove
Black Belt
428 Views

I'd like to mention the un-obvious here...

It is not clear as to if the C function called (with the char** argument) requires the char** list, and more importantly those strings being pointed to, remain persistent following the call. That being the case, you would not want Fortran to auto-deallocate the arguments. In this situation, consider making the arguments either module variables or attribute them with SAVE.

Jim Dempsey

damienhocking
New Contributor I
419 Views

Jim, that's a good point and I didn't explain that aspect. In this particular case, names is a temporary list of components selected from somewhere else in the Fortran world (our client's software). That list is handed to us through the C interface, where we take those names, look those components up in our database and configure our C++ calculation engine based on those components. After that we have the configuration and names is discarded. Said client also has a C# .NET application that we had to develop an API for that's similar to the Fortran. API development takes just as long as the core engineering.

Thanks to everyone else for their additional feedback, I went all over Google, Stackoverflow, etc trying to find out how to do this and couldn't find anything useful, before coming here. Many of the examples I found showed that there wasn't a clear understanding of how the interop worked let alone what to do.

  

Steve_Lionel
Black Belt Retired Employee
383 Views

Instead of:

names(J) = names(J) // C_NULL_CHAR

you should use:

names(J) = TRIM(names(J)) // C_NULL_CHAR

otherwise you're including any trailing blanks.

GVautier
New Contributor II
407 Views
        do J = 1 , howmany
            cname = names(J) // C_NULL_CHAR
            comps(J) = c_loc( cname) 
        enddo

For me that doesn't work. All comps(J) will have the same address.

 

mecej4
Black Belt
400 Views

You are correct; I have corrected the code taking your comments into account.

It is probably best to make such changes when working on the full problem rather than on an isolated subroutine.

Reply