Создание интерфейса FORTRAN для функции C, которая возвращает символ * - PullRequest
10 голосов
/ 02 апреля 2012

Я задерживался на этом около недели, и искал форум за форумом, чтобы получить четкое объяснение того, как отправить символ * из 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)

При попытке использовать аналогичную логику для возврата символа * из 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 до символа «колокол» ...

Я также пытался вернуться:

  • Указатель на CHARACTER (LEN = 255, KIND = C_CHAR).
  • Буквально CHARACTER (LEN = 255, KIND = C_CHAR).
  • INTEGER (C_SIZE_T), и попытался изобразить это в указатель на массив строк.
  • ХАРАКТЕР.
  • и т.д.

Если кто-нибудь может дать мне пример того, как это сделать, я был бы очень признателен ...

С уважением,

Mike

Ответы [ 7 ]

13 голосов
/ 02 апреля 2012

Строки динамической длины всегда немного хитры с взаимодействием Си. Возможное решение - использовать указатели.

Сначала простой случай, когда вам нужно передать завершенную нулем строку в C-функцию. Если вы действительно передаете строку только в, вы должны убедиться, что завершите ее с помощью c_null_char, таким образом, это направление довольно простое. Вот примеры из LuaFortran Interface :

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_get выглядит так:

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-рутиной (также из Библиотека Aotus, упомянутая выше):

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 может интерпретироваться как строка символов Фортрана: Предположим, вы получили c_ptr, указывающий на строку:

type(c_ptr) :: a_c_string

И его длина задается переменной len следующего типа:

integer(kind=c_size_t) :: stringlen

Вы хотите получить эту строку в указателе на символьную строку в Фортране:

character,pointer,dimension(:) :: string

Итак, вы делаете отображение:

call c_f_pointer(a_c_string, string, [ stringlen ])
5 голосов
/ 03 апреля 2012

Благодарю 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

И для вызова этой функции из программы на Фортране:

USE MINTERFACES

PRINT *, "--> GetLastErrorMessage:      '", TRIM(GetLastErrorMessage()), "'"

Еще раз спасибо heraldkl за предоставление этого решения - я бы не понял, как это сделать без его участия.

4 голосов
/ 21 ноября 2013

Эта тема немного устарела, но, поскольку у меня была похожая проблема (и, вероятно, другие будут), я все равно отправляю ответ.

Приведенные выше коды вызовут ошибку сегментации, если по какой-то причине строка C пуста. Кроме того, нет необходимости возвращать строку из 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
2 голосов
/ 23 июля 2018

Я также боролся с вызовом подпрограммы 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

Надеюсь, этот пример облегчит работу следующего с той же проблемой.

2 голосов
/ 02 апреля 2012

Я всегда борюсь с этими возможностями взаимодействия. Я думаю, что ваш интерфейс должен объявить

CHARACTER(KIND=C_CHAR),DIMENSION(*) :: getlasterrormessage

и что при вызове функции вы передаете соответствующую символьную переменную Fortran, длина которой равна или превышает длину массива символов C, который вы ожидаете вернуть.

Поскольку у вас, похоже, есть Intel Fortran, просмотрите представленные примеры кода, они дают полный пример для этого.

Полагаю, вы знаете, что то, что вы написали, не является синтаксически правильным Fortran?

1 голос
/ 02 апреля 2012

В Фортране элемент должен быть объявлен как «символ (kind = c_char, len = 1), размерность (255)», а не len = 255. Это создаст массив символов длиной один, который вам нужен на С-стороне. Что может сбить с толку, так это то, что существует исключение, позволяющее Фортрану сопоставлять строки с одномерными массивами.

Вы хотите сказать, что хотите вызвать процедуру на Фортране из C? Смотрите этот пример: Вызов подпрограммы FORTRAN из C .

EDIT: ifort и gfortran говорят, что массивы недопустимы, поскольку функция возвращается в этом контексте. Что делает возврат строк в качестве аргументов функции из C в Fortran сложнее, чем использование строки в качестве аргумента (пример в ссылке выше) ... вам нужно использовать указатель, а затем встроенный в Fortran c_f_pointer для преобразования из 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
0 голосов
/ 15 ноября 2016

Если вы знаете длину строки, то ответ Папы выше может быть значительно упрощен:

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 со строкой и длинойстрока и возвращает копию в виде строки Фортрана.

...