Создание интерфейса FORTRAN для функции C, возвращающей символ*
Я задержался на этом около недели и искал форум за форумом для четкого объяснения того, как отправить char* из C в FORTRAN. Чтобы сделать этот вопрос более неприятным, отправка аргумента char * от FORTRAN к C была прямой...
отправка аргумента char* из FORTRAN в C (это отлично работает):
// The C header declaration (using __cdecl in a def file):
extern "C" double GetLoggingValue(char* name);
и от Фортрана:
! The FORTRAN interface:
INTERFACE
REAL(8) FUNCTION GetLoggingValue [C, ALIAS: '_GetLoggingValue'] (name)
USE ISO_C_BINDING
CHARACTER(LEN=1, KIND=C_CHAR), DIMENSION(*), INTENT(IN) :: name
END FUNCTION GetLoggingValue
END INTERFACE
! Calling the function:
GetLoggingValue(user_name)
при попытке использовать аналогичную логику для возврата char* из C, я получаю проблему после проблемы. Одна попытка, которую я чувствовал, должна работать:
// The C declaration header (using __cdecl in a def file):
extern "C" const char* GetLastErrorMessage();
и интерфейс FORTRAN:
INTERFACE
FUNCTION GetLastErrorMessage [C, ALIAS: '_GetLastErrorMessage'] ()
USE ISO_C_BINDING
CHARACTER(LEN=1, KIND=C_CHAR), DIMENSION(255), :: GetLastErrorMessage
END FUNCTION GetLastErrorMessage
END INTERFACE
(Я не могу буквально использовать размер (*), поэтому я перешел к 255.)
этой должны верните указатель на массив из 255 символов C-стиля-но если это так, я не смог преобразовать это в значимую строку. На практике он возвращает случайный набор символов, в любом месте от Wingdings до 'bell' характер...
Я также попытался вернуться:
- указатель на символ (LEN=255, KIND=C_CHAR).
- буквально символ(LEN=255, KIND=C_CHAR).
- целое число (C_SIZE_T) и попытался утончить это в указатель на строковый массив.
- ПЕРСОНАЖ.
- etc.
если кто-нибудь может дать мне пример, как это сделать, буду очень благодарна...
лучшие с уважением,
Майк
7 ответов
строки динамической длины всегда немного сложнее с взаимодействием C. Возможным решением является использование указателей.
сначала простой случай, когда вам нужно передать строку с нулевым символом в C-функцию. Если вы действительно передаете строку только в, вы должны убедиться, чтобы завершить ее с помощью c_null_char, таким образом, это направление довольно прямолинейно. Вот несколько примеров из Интерфейс LuaFortran:
subroutine flu_getfield(L, index, k)
type(flu_State) :: L
integer :: index
character(len=*) :: k
integer(kind=c_int) :: c_index
character(len=len_trim(k)+1) :: c_k
c_k = trim(k) // c_null_char
c_index = index
call lua_getfield(L%state, c_index, c_k)
end subroutine flu_getfield
и интерфейс из lua_getfield выглядит так:
subroutine lua_getfield(L, index, k) bind(c, name="lua_getfield")
use, intrinsic :: iso_c_binding
type(c_ptr), value :: L
integer(kind=c_int), value :: index
character(kind=c_char), dimension(*) :: k
end subroutine lua_getfield
и интерфейс C-кода:
void lua_getfield (lua_State *L, int idx, const char *k)
теперь немного более сложный случай, когда нам приходится иметь дело с возвращаемой строкой из C с динамической длиной. Наиболее портативное решение, которое я нашел до сих пор использует указатели. Вот пример с указателем, где строка задается C-подпрограммой (также из Библиотека аота, упомянутая выше):
function flu_tolstring(L, index, len) result(string)
type(flu_State) :: L
integer :: index
integer :: len
character,pointer,dimension(:) :: string
integer :: string_shape(1)
integer(kind=c_int) :: c_index
integer(kind=c_size_t) :: c_len
type(c_ptr) :: c_string
c_index = index
c_string = lua_tolstring(L%state, c_index, c_len)
len = int(c_len,kind=kind(len))
string_shape(1) = len
call c_f_pointer(c_string, string, string_shape)
end function flu_tolstring
где lua_tolstring имеет после интерфейс:
function lua_tolstring(L, index, len) bind(c, name="lua_tolstring")
use, intrinsic :: iso_c_binding
type(c_ptr), value :: L
integer(kind=c_int), value :: index
integer(kind=c_size_t) :: len
type(c_ptr) :: lua_tolstring
end function lua_tolstring
наконец, вот попытка прояснить, как c_ptr можно интерпретировать как строку символов Fortran: Предположим, у вас есть c_ptr, указывающий на строку:
type(c_ptr) :: a_c_string
и длина его задается переменной len со следующим типом:
integer(kind=c_size_t) :: stringlen
вы хотите получить эту строку в указателе на символьную строку в Fortran:
character,pointer,dimension(:) :: string
Итак, вы делаете отображение:
call c_f_pointer(a_c_string, string, [ stringlen ])
спасибо heraldkl за то, что дал мне решение этой очень неприятной проблемы. Я публикую то, что я в конечном итоге реализовал, какие роли преобразование указателя в интерфейс, то есть конечное приложение может вызвать функцию C без необходимости знать о преобразовании указателя:
функция C:
// The C declaration header (using __cdecl in a def file):
extern "C" const char* GetLastErrorMessage();
модуль интерфейса FORTRAN:
MODULE mINTERFACES
USE ISO_C_BINDING
INTERFACE
FUNCTION GetLastErrorMessagePtr [C, ALIAS: '_GetLastErrorMessage'] ()
USE ISO_C_BINDING
TYPE(C_PTR) :: GetLastErrorMessagePtr
END FUNCTION GetLastErrorMessagePtr
END INTERFACE
CONTAINS ! this must be after all INTERFACE definitions
FUNCTION GetLastErrorMessage()
USE ISO_C_BINDING
CHARACTER*255 :: GetLastErrorMessage
CHARACTER, POINTER, DIMENSION(:) :: last_message_array
CHARACTER*255 last_message
INTEGER message_length
CALL C_F_POINTER(GetLastErrorMessagePtr(), last_message_array, [ 255 ])
DO 10 i=1, 255
last_message(i:i+1) = last_message_array(i)
10 CONTINUE
message_length = LEN_TRIM(last_message(1:INDEX(last_message, CHAR(0))))
GetLastErrorMessage = last_message(1:message_length-1)
END FUNCTION GetLastErrorMessage
и вызвать эту функцию из FORTRAN программа:
USE MINTERFACES
PRINT *, "--> GetLastErrorMessage: '", TRIM(GetLastErrorMessage()), "'"
еще раз спасибо heraldkl для обеспечения этого решения - я не имел понятия, как это сделать без его ввода.
этот поток немного стар, но поскольку у меня была аналогичная проблема (и, вероятно, другие будут), я все равно отправлю ответ.
коды, размещенные выше, вызовут ошибку сегментации, если по какой-то причине строка C равна null. Кроме того, нет необходимости возвращать строку 255 символов (которую, вероятно, потребуется обрезать перед использованием), поскольку Fortran 2003/2008 поддерживает функции, возвращающие выделяемые объекты. Используя всю информацию, опубликованную выше, я закончил со следующим функция, которая получает строку C (указатель) и возвращает соответствующую строку Fortran; если строка C равна null, она возвращает "NULL", аналогично C" (null)", напечатанному в аналогичных случаях:
function C_to_F_string(c_string_pointer) result(f_string)
use, intrinsic :: iso_c_binding, only: c_ptr,c_f_pointer,c_char,c_null_char
type(c_ptr), intent(in) :: c_string_pointer
character(len=:), allocatable :: f_string
character(kind=c_char), dimension(:), pointer :: char_array_pointer => null()
character(len=255) :: aux_string
integer :: i,length
call c_f_pointer(c_string_pointer,char_array_pointer,[255])
if (.not.associated(char_array_pointer)) then
allocate(character(len=4)::f_string); f_string="NULL"; return
end if
aux_string=" "
do i=1,255
if (char_array_pointer(i)==c_null_char) then
length=i-1; exit
end if
aux_string(i:i)=char_array_pointer(i)
end do
allocate(character(len=length)::f_string)
f_string=aux_string(1:length)
end function C_to_F_string
Я всегда борюсь с этими функциями взаимодействия. Я думаю, что ваш интерфейс должен объявить
CHARACTER(KIND=C_CHAR),DIMENSION(*) :: getlasterrormessage
и что при вызове функции вы передаете соответствующую символьную переменную Fortran с длиной, равной или большей длины массива символов C, который вы ожидаете вернуть.
поскольку у вас, похоже, есть Intel Fortran, просмотрите предоставленные образцы кода, они дают полный пример для этого.
Я думаю, вы знаете то, что вы опубликовали, не является синтаксически правильным Fortran ?
Я также боролся с вызовом подпрограммы C, которая возвращает строку, и ответы выше были очень полезны, но, поскольку я почти ничего не знаю о C, и ответы немного сбивают с толку, я просто хотел внести свой вклад в решение, которое использует указатель C, мне не удалось использовать какие-либо другие предложения выше. Программа C, которую я вызываю, открывает отдельное окно для поиска имени файла.
program test
use iso_c_binding
implicit none
! A C function that returns a string need a pointer to the array of single char
type (c_ptr) :: C_String_ptr
! This is the Fortran equivalent to a string of single char
character (len=1, kind=c_char), dimension(:), pointer :: filchar=>null()
! Interface to a C routine which opens a window to browse for a file to open
interface
function tinyopen(typ) bind(c, name="tinyopen")
use iso_c_binding
implicit none
integer(c_int), value :: typ
type (C_Ptr) :: tinyopen
end function tinyopen
end interface
character (len=256) :: filename
integer typ,jj
typ=1
C_String_ptr = tinyopen(typ)
! convert C pointer to Fortran pointer
call c_f_pointer(C_String_ptr,filchar,[256])
filename=' '
if(.not.associated(filchar)) then
! if no characters give error message
write(*,*)'No file name'
else
! convert the array of single characters to a Fortran character
jj=1
do while(filchar(jj).ne.c_null_char)
filename(jj:jj)=filchar(jj)
jj=jj+1
enddo
endif
write(*,*)'Text is: ',trim(filename)
end program test
надеюсь, этот пример поможет следующая с тем же проблема.
в Fortran элемент должен быть объявлен как " символ (kind=c_char, len=1), размерность (255)", а не len=255. Это создаст массив символов длины один,что вам нужно на стороне C. Что может быть запутанным, так это то, что есть исключение, которое позволяет Fortran сопоставлять строки с одномерными массивами.
вы имеете в виду, что хотите вызвать процедуру Fortran из C? См. этот пример:вызов подпрограммы FORTRAN из C.
EDIT: и ifort, и gfortran говорят, что массивы не разрешены, поскольку функция возвращается в этом контексте. Что делает возврат строк в качестве аргументов функции из C в Fortran сложнее, чем использование строки в качестве аргумента (пример в ссылке выше) ... вы должны использовать указатель, а затем встроенный C_f_pointer Fortran для преобразования из C-строки в строку Fortran, как объяснил haraldkl. Вот еще один пример кода:
program test_c_func
use iso_c_binding
implicit none
type (C_PTR) :: C_String_ptr
character (len=1, kind=c_char), dimension (:), pointer :: ErrChars => null ()
character (len=255) :: ErrString
integer :: i
INTERFACE
FUNCTION GetLastErrorMessage () bind (C, name="GetLastErrorMessage" )
USE ISO_C_BINDING
type (C_PTR) :: GetLastErrorMessage
END FUNCTION GetLastErrorMessage
END INTERFACE
C_String_ptr = GetLastErrorMessage ()
call c_f_pointer ( C_String_ptr, ErrChars, [255] )
ErrString = " "
xfer_string: do i=1, 255
if ( ErrChars (i) == c_null_char) exit xfer_string
ErrString (i:i) = ErrChars (i)
end do xfer_string
write (*, '( "Fortran: <", A, ">" )' ) trim (ErrString)
end program test_c_func
Если вы знаете длину строки, потом ППА ответ выше можно значительно упростить:
function stringc2f(n, cstr) result(fstr)
integer, intent(in) :: n
type(c_ptr), intent(in) :: cstr
character(:), allocatable :: fstr
character(n, kind=c_char), pointer :: fptr
call c_f_pointer(cstr, fptr)
fstr = fptr
end function
вышеуказанная функция принимает указатель C со строкой и длиной строки и возвращает копию в виде строки Fortran.