Создание интерфейса 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.