Get length of HDF5 filename in Fortran? - fortran

I'm trying to get the filename of an HDF5 object in Fortran, for which I do not know the length of the filename in advance. The HDF5 documentation says this can be accomplished by passing NULL to h5fget_name:
If the length of the name, which determines the required value of size, is unknown, a preliminary H5Fget_name call can be made by setting name to NULL.
However, it is unclear how to accomplish this from Fortran code. Per the above language in the documentation, something like this should work:
function get_hdf5_filename(obj_id) result(filename)
use hdf5
use iso_c_binding, ONLY: c_null_ptr
! Arguments
integer(HID_T), intent(in)::obj_id
!! Handle of HDF5 object
! Result
character(:), allocatable::filename
integer(SIZE_T)::size = 0
!! Length of filename
integer::hdferr
!! HDF5 error code
call h5fget_name_f(obj_id, c_null_ptr, size, hdferr)
allocate (character(size)::filename)
call h5fget_name_f(obj_id, filename, size, hdferr)
end function get_hdf5_filename
However, gfortran won't compile the above and gives a type mismatch error:
Error: Type mismatch in argument 'buf' at (1); passed TYPE(c_ptr) to CHARACTER(1)

A null can be passed by declaring the filename variable as a pointer and nullifying it before the initial call to h5fget_name_f:
function get_hdf5_filename(obj_id) result(filename)
use hdf5
use iso_c_binding, ONLY: c_null_ptr
! Arguments
integer(HID_T), intent(in)::obj_id
!! Handle of HDF5 object
! Result
character(:), pointer::filename
integer(SIZE_T)::size = 50
!! Length of filename
integer::hdferr
!! HDF5 error code
nullify(filename)
call h5fget_name_f(obj_id, filename, size, hdferr)
! HDF5 needs one more character (probably for the null character terminating the string), so we allocate filename to length size+1
allocate (character(size+1)::filename)
call h5fget_name_f(obj_id, filename, size, hdferr)
! Remove last character from the returned string
filename => filename(1:size)
end function get_hdf5_filename
Note that h5fget_name_f needs an extra character in the passed string (probably for the terminating null character), so the name pointer has to be allocated one character longer than the value set in the size argument. The terminating null can be removed by redirecting the file pointer to a subset that excludes the last character using array slicing syntax (filename => filename(1:size) in the above example).

A possible solution is to create a C function that returns the filename length for a given HDF5 object:
#include "hdf5.h"
#include "H5f90i.h"
int_f get_hdf5_filename_length(hid_t_f *obj_id){
return H5Fget_name((hid_t)*obj_id, NULL, 0);
}
which can be called from Fortran in the following way:
function get_hdf5_filename(obj_id) result(filename)
use hdf5
use iso_c_binding, ONLY: c_null_ptr
interface
function get_hdf5_filename_length(obj_id) bind(c) result(length)
use hdf5
integer(HID_T)::obj_id
integer(SIZE_T)::length
end function get_hdf5_filename_length
end interface
! Arguments
integer(HID_T), intent(in)::obj_id
!! Handle of HDF5 object
! Result
character(:), pointer::filename
integer(SIZE_T)::size = 50
!! Length of filename
integer::hdferr
!! HDF5 error code
integer::i
!! Loop counter
size = get_hdf5_filename_length(obj_id)
! filename has to be allocated to size+1 to allow for the terminating null
! of the filename string in C
allocate (character(size+1)::filename)
! h5fget_name_f uses len_trim to determine the buffer length,
! which requires the buffer be filled with non-whitespace characters
! in order to work correctly
do i = 1, size + 1
filename(i:i) = 'a'
end do
! Get the filename
call h5fget_name_f(obj_id, filename, size, hdferr)
! Remove the null character from the end of the string
filename => filename(1:size)
end function get_hdf5_filename
Note that it is not enough to allocate the string buffer to the correct size; it also needs to be populated with non-whitespace characters before being passed to h5fget_name_f, because h5fget_name_f calls len_trim on the passed buffer and uses the result to determine the maximum permissible filename length.

Related

Can I reference a substring of a string function result? [duplicate]

This question already has answers here:
Access elements of returned array in Fortran
(4 answers)
Closed 5 years ago.
I have a function str that takes an integer and converts it to a string. I also want to consider a substring of the string of that value, which would be easy if it were a character value rather than a character function.
In other words, I'd like to write something like this:
str(12345)(2:3)
instead of having to do this:
character(10) :: c
c = str(12345)
print *, c(2:3)
or rolling my own substr() function. Is there any way to do what I want without storing the character value in a temporary character variable or writing another function?
Here's a complete program:
program main
character(10) :: c
! works fine
c = str(12345)
print *, c(2:3)
! nope
print *, str(12345)(2:3)
! this would work if i wrote a substr function
print *, substr( str(12345), 2, 3 )
contains
character(len=1064) function str(k)
! convert an integer to string
integer, intent(in) :: k
write (str, *) k
str = adjustl(str)
end function str
end program main
No, you cannot substring or array-index a Fortran expression. And a function result is an expression. The closest thing to avoiding a temporary is using associate from Fortran 2003, but you won't save much code either
associate(c=>str(12345))
print *,c(2:3)
end associate

Passing string array from Fortran to C/C++ DLL and get array with changed values

I am creating a C/C++ DLL which accepts char*[] (String array), changes the value in array and return back.
My C/C++ implementation:
int doubleijk_(char* data[]) // what should be the return type ???
{
// code to change array elements
return 0;
}
In Fortran (using ifort) I am calling the DLL's function like this:
module variables
type string
character(len=:), allocatable :: str
end type string
end module variables
program Call_CPP
use variables
type(string) :: array(3)
array = [string('xyza'), string('abcd'), string('mnopqrs')]
INTERFACE
! How to write SUBROUTINE for array
SUBROUTINE doubleijk_(arr) BIND(C,NAME='doubleijk_')
!???????WHAT SHOULD BE SUBROUTINE FOR ARRAY OF STRINGS????????
END SUBROUTINE doubleijk_
END INTERFACE
! Body of Call_CPP
call doubleijk_(array)
! print values of array after call
end program Call_CPP
I am able to pass string, integer from Fortran and got changed value from C/C++. What I need is to pass string array from Fortran to C/C++ and get back the array with changed values. How do I do this?
Finally made that working. Here is the solution.
program Call_CPP
use iso_c_binding, only: c_associated, c_loc, c_ptr
INTERFACE
SUBROUTINE doubleijk_(stringPtrs) BIND(C,NAME='doubleijk_')
use iso_c_binding
TYPE(C_PTR), DIMENSION(3) :: stringPtrs
END SUBROUTINE doubleijk_
END INTERFACE
TYPE(C_PTR), DIMENSION(3) :: stringPtr
CHARACTER(LEN=20), DIMENSION(3), TARGET :: stringArray
DO ns = 1, 3
stringArray(ns) = "My String"//char(0)
stringPtr(ns) = c_loc(stringArray(ns))
END DO
! Body of Call_CPP
call doubleijk_(stringPtr) !Call C function
DO ns = 1, 3
print *, stringArray(ns) !print array after call-changed values
END DO
end program Call_CPP

Form character*(*) for character variable generation

I have a third party script for a subroutine that I need to work with. This subroutine is as follows
Subroutine COpen(io, Name )
Character*(*) Name
Character*1023 NameIn, NameOut
NameIn = Trim(Name)//' '
Call Get_OrMakeFileName( NameIn, NameOut )
Open(io,file=NameOut,access="APPEND")
End
I don't understand the Character*(*) name syntax. Isn't the typical way to declare string variables simply. character :: name*4 with the *4 part designating the number of characters? Can anyone please explain the purpose of this alternate syntax? What kind of object does it generate?
In short: character*(*) declares a character variable of assumed length.
There are a number of ways of declaring the length of a character variable. One, as seen in the question's code, is
character*1023 ...
where a literal constant follows the *. Equivalent to that is
character([len=]1023) ...
(len= being optional). In this case the length needn't be a literal constant.
These two forms declare a variable of a particular length. There are two other forms of a length for a character variable:
assumed length - character([len=]*) ... ;
deferred length - character([len=]:) ....
Like with character*1023 the assumed and deferred length declarations may be written in this style:
character*(*) ... ! Assumed length
character*(:) ... ! Deferred length
character*(1023) ... ! For completeness
Well, what does "assumed length" mean?
For a dummy argument such as Name it's length is taken from the length of the actual argument of the procedure. With character :: Name*4 the argument is of length 4, regardless of the length of the argument to the subroutine (as long as it's of length at least 4). When the dummy is of assumed length it is of length 12 if the argument is of length 12, and so on.
Although not in the question, a character named constant may also assume its length from the defining expression:
character*(*), parameter :: label='This long'
Deferred length is left to other questions.

Missing explicit interface for subroutine

I just want to read a two-column text file with variable lines into an array.
The first column of the text file is time in the unit of second, and the second is temperature. Just like this:
1.1 10
2.1 20
3.2 30
4.2 40
5.3 50
6.3 60
7.4 70
Following is the code I write:
module myData
type line_info
real :: time
real :: temp
end type
type datalink
type(line_info) :: time_temp
type(datalink), pointer :: next
end type
type(line_info), allocatable :: FileInfoArr(:)
end module
program Console1
use myData
implicit none
! Variables
type(line_info),allocatable :: time_temp_arr(:)
!real,allocatable :: File2Arr(:)
character (len=80) :: FileFullName="C:\t.txt"
call File2Arr(FileFullName,time_temp_arr)
End Program Console1
Subroutine File2Arr(FileFullName,InfoArray)
use myData
character (len=80) :: FileFullName
type(line_info),allocatable :: InfoArray(:)
type(datalink), pointer :: head
type(datalink), pointer :: p
integer error,size,i
logical alive
! check if file exists
inquire(file=FileFullName, exist=alive)
if(alive==0) then
write(*,*) FileFullName, "doesn't exist."
stop
end if
! read file using pointer
open(10, file=FileFullName, status="old", iostat=error)
if(error/=0) then
write(*,*) "open file fail!"
stop
end if
allocate(head)
nullify(head%next)
p=>head
size=0
!read(10,"(A80)") tempstr
do while(.true.)
read(10, fmt=*, iostat=error) p%time_temp
if(error/=0) exit
size=size+1
allocate(p%next, stat=error) ! add next line
if(error/=0) then
write(*,*) "Out of memory!"
stop
end if
p=>p%next
nullify(p%next)
end do
!save link info into an array
allocate(InfoArray(size))
p=>head
i=0
do while(associated(p%next))
i=i+1
InfoArray(i)=p%time_temp
p=>p%next
!write(*,*) FileInfoArr(i)%time, FileInfoArr(i)%temp
end do
End Subroutine
When I compile it, I got this :
error #8055: The procedure has a dummy argument that has the ALLOCATABLE, ASYNCHRONOUS, OPTIONAL, POINTER, TARGET, VALUE or VOLATILE attribute. Required explicit interface is missing from original source. [TIME_TEMP_ARR]
Any idea on how to fix this error, thanks for any help.
Put your subroutine File2Arr inside the module MyData (and remove the use mydata line inside that subroutine). It compiled & ran for me doing that.
Alternatively to adding your subroutine File2Arr to your module MyData, you could put it also directly in your program by using the CONTAINS keyword (here you can also remove the use mydata line in the subroutine).

FORTRAN function returning an array causes a segfault (calling from C++)

Basically, here's my problem. I'm calling someone else's FORTRAN functions from my C++ code, and it's giving me headaches. Some code:
function c_error_message()
character(len = 255) :: c_error_message
errmsg(1:9) = 'ERROR MSG'
return
end
That's the FORTRAN function. My first question is: Is there anything in there that would cause a segfault?
If not, then second: What does that return? A pointer?
I'm trying to call it with the following C statement:
char *e = c_error_message_();
That causes a segfault.
c_error_message();
That too causes a segfault.
I declared c_error_message_() earlier on with the following code:
extern"C" {
char* c_error_message_();
}
Would declaring a function with a different return type than the actual return type cause a segfault?
I'm at a loss. Thanks for any replies.
Here is a method that works. When I tried to use the ISO C Binding with a function returning a string, the compiler objected. So if instead you use a subroutine argument:
subroutine fort_err_message (c_error_message) bind (C, name="fort_err_message")
use iso_c_binding, only: C_CHAR, C_NULL_CHAR
character (len=1, kind=C_CHAR), dimension (255), intent (out) :: c_error_message
character (len=255, kind=C_CHAR) :: string
integer :: i
string = 'ERROR MSG' // C_NULL_CHAR
do i=1, 255
c_error_message (i) = string (i:i)
end do
return
end subroutine fort_err_message
The Fortran is a bit awkward because technically a C-string is an 1D array of characters.
And example C code to demo that this works:
#include <stdio.h>
void fort_err_message ( char chars [255] );
int main ( void ) {
char chars [255];
fort_err_message ( chars );
printf ( "%s\n", chars );
return 0;
}
Since the second line declares a name that is the same as the function name, it is declaring the type of the function return, namely a scaler character string of 255 characters. But in theory this doesn't tell us the internal API -- that is up the the compiler. I don't know where "errmsg" comes from -- it must be declared elsewhere -- perhaps a global variable as suggested by Michael Anderson. Or maybe this is a mistake and the line should be c_error_message = "ERROR MSG". (There is no need to designate the sub-string range -- the rest of the string will be filled with blanks.) IMO, the best approach to calling Fortran from C (or vice-a-versa) is to use the ISO C Binding, which will cause the Fortran compiler to use a C compatible Interface. I haven't done a function returning a string, but have done strings as arguments.
FORTRAN functions return the value assigned to the function name. The type returned is specified by the function definition, and in this case, it is returning an character string 255 characters long. I think the type mismatch is why you are segfaulting. So what C type should you use? I don't know.
I found this Using C with FORTRAN page, and the author strongly recommends using FORTRAN subtroutines and calling them as C functions returning type void.
Here is an F95 quick reference.