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

passing an array of strings from Fortran to C

damienhocking
New Contributor I
3,156 Views

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
3,069 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

0 Kudos
15 Replies
Steve_Lionel
Honored Contributor III
3,138 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
3,127 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? 

0 Kudos
jimdempseyatthecove
Honored Contributor III
3,108 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

Steve_Lionel
Honored Contributor III
3,088 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.)

0 Kudos
damienhocking
New Contributor I
3,070 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.

 

0 Kudos
andrew_4619
Honored Contributor III
3,039 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
0 Kudos
mecej4
Honored Contributor III
3,033 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?

0 Kudos
IanH
Honored Contributor III
3,013 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.

0 Kudos
mecej4
Honored Contributor III
3,012 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.

 

 

0 Kudos
jimdempseyatthecove
Honored Contributor III
3,002 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

0 Kudos
damienhocking
New Contributor I
2,993 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.

  

0 Kudos
Steve_Lionel
Honored Contributor III
2,957 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.

0 Kudos
GVautier
New Contributor II
2,981 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.

 

0 Kudos
mecej4
Honored Contributor III
2,974 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.

0 Kudos
Reply