- Mark as New
- Bookmark
- Subscribe
- Mute
- Subscribe to RSS Feed
- Permalink
- Report Inappropriate Content
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.
- Mark as New
- Bookmark
- Subscribe
- Mute
- Subscribe to RSS Feed
- Permalink
- Report Inappropriate Content
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.
Link Copied
- Mark as New
- Bookmark
- Subscribe
- Mute
- Subscribe to RSS Feed
- Permalink
- Report Inappropriate Content
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.
- Mark as New
- Bookmark
- Subscribe
- Mute
- Subscribe to RSS Feed
- Permalink
- Report Inappropriate Content
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?
- Mark as New
- Bookmark
- Subscribe
- Mute
- Subscribe to RSS Feed
- Permalink
- Report Inappropriate Content
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
- Mark as New
- Bookmark
- Subscribe
- Mute
- Subscribe to RSS Feed
- Permalink
- Report Inappropriate Content
- Mark as New
- Bookmark
- Subscribe
- Mute
- Subscribe to RSS Feed
- Permalink
- Report Inappropriate Content
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.)
- Mark as New
- Bookmark
- Subscribe
- Mute
- Subscribe to RSS Feed
- Permalink
- Report Inappropriate Content
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.
- Mark as New
- Bookmark
- Subscribe
- Mute
- Subscribe to RSS Feed
- Permalink
- Report Inappropriate Content
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
- Mark as New
- Bookmark
- Subscribe
- Mute
- Subscribe to RSS Feed
- Permalink
- Report Inappropriate Content
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?
- Mark as New
- Bookmark
- Subscribe
- Mute
- Subscribe to RSS Feed
- Permalink
- Report Inappropriate Content
@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.
- Mark as New
- Bookmark
- Subscribe
- Mute
- Subscribe to RSS Feed
- Permalink
- Report Inappropriate Content
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.
- Mark as New
- Bookmark
- Subscribe
- Mute
- Subscribe to RSS Feed
- Permalink
- Report Inappropriate Content
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
- Mark as New
- Bookmark
- Subscribe
- Mute
- Subscribe to RSS Feed
- Permalink
- Report Inappropriate Content
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.
- Mark as New
- Bookmark
- Subscribe
- Mute
- Subscribe to RSS Feed
- Permalink
- Report Inappropriate Content
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.
- Mark as New
- Bookmark
- Subscribe
- Mute
- Subscribe to RSS Feed
- Permalink
- Report Inappropriate Content
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.
- Mark as New
- Bookmark
- Subscribe
- Mute
- Subscribe to RSS Feed
- Permalink
- Report Inappropriate Content
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.
- Subscribe to RSS Feed
- Mark Topic as New
- Mark Topic as Read
- Float this Topic for Current User
- Bookmark
- Subscribe
- Printer Friendly Page