Хобрук: Ваш путь к мастерству в программировании

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

При попытке использовать аналогичную логику для возврата символа * из 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) и попытался преобразовать его в указатель на массив строк.
  • ХАРАКТЕР.
  • и Т. Д.

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

С наилучшими пожеланиями,

Майк


  • Какие наборы инструментов вы используете? 02.04.2012
  • Я использую компилятор VC ++, но компилирую интерфейс на чистом языке C. FORTAN - это Visual Fortran 2011, который, как мне кажется, является FORTRAN 90. Однако он предназначен для опубликованного API, поэтому его нужно вызывать из максимально возможного количества вариантов. .. 02.04.2012

Ответы:


1

Строки динамической длины всегда немного сложны с взаимодействием 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-Code:

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 ])
02.04.2012
  • Спасибо, геральдкл! Приведенный выше пример предназначен для взаимодействия с функцией void C - будет ли этот подход работать, когда типом возвращаемого значения функции C является char *? Моя общая операционная парадигма заключается в том, что мои функции API возвращают результат, а не передают их через подпрограммы void, и это отлично работает при использовании всех других типов данных, ЗА ИСКЛЮЧЕНИЕМ символов :-( 02.04.2012
  • Я предполагаю, что описанный выше подход также будет работать с непустыми функциями. Я не вижу причины, почему на это должно влиять возвращаемое значение. 02.04.2012
  • Я, конечно, счастлив использовать указатели - действительно, это был мой первый подход. Боюсь, у меня возникнут проблемы с выполнением приведенных выше фрагментов - все ли необходимо для преобразования указателя C на массив const char в строку FORTRAN? Другими словами, если у меня есть TYPE (C_PTR) и длина строки, каков минимум, который мне нужно преобразовать в строку FORTRAN? Извините за тупой ... 02.04.2012
  • сейчас попробовал добавить к моему ответу какой-нибудь пример перевода;) 02.04.2012
  • ОНО РАБОТАЕТ! Большое спасибо heraldkl - я был близок к мысли, что это невозможно. Спасибо всем ребятам, которые помогали и комментировали - это помогало мне работать. 02.04.2012
  • Я добавил «ответ» ниже, показывая окончательную реализацию. Это должен быть довольно общий способ взаимодействия с char * с помощью решения heraldkl. Я надеюсь, что я не нарушу этикет, отвечая на свой вопрос - даже если я хочу отдать дань уважения лучшему решению ... 03.04.2012

  • 2

    Благодарю 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 за предоставление этого решения - я бы и понятия не имел, как это сделать без его участия.

    03.04.2012
  • Совершенно без проблем, рад, что помогло;) 03.04.2012

  • 3

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

    Приведенные выше коды вызовут ошибку сегментации, если по какой-то причине строка 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
    
    21.11.2013
  • Я опубликовал небольшое упрощение вашей подпрограммы ниже. 15.11.2016

  • 4

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

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

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

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

    Я думаю, вы знаете, что то, что вы опубликовали, не является синтаксически правильным Фортраном?

    02.04.2012
  • Вы имеете в виду запятую перед ::? Извините - ошибка вырезания и вставки! Я попробую описанное выше ... 02.04.2012
  • Я только что пробовал, но получаю ту же ошибку компиляции, что и при попытке что-то подобное: Ошибка 1, ошибка # 6688: спецификация массива для имени функции без атрибута POINTER должна быть явным массивом формы (R505. 9). Вот почему я пробовал в основном то же самое, но с 255 вместо * - но безуспешно :-( 02.04.2012
  • Я пробовал использовать вместо этого CHARACTER (KIND = C_CHAR), DIMENSION (255) :: GetLastErrorMessage и считывать массив символов той же спецификации, но это, по-видимому, возвращает массив из 255 случайных символов ... 02.04.2012
  • Хммм, предлагаю вам проверить образцы Intel. Я продолжу чесать голову, но ... 02.04.2012
  • Спасибо, Марк, я просмотрел каталоги Intel, но нашел только схему интерфейса. Возможно, мне не хватает примеров в моей установке ... 02.04.2012
  • Я думаю, он имеет в виду, что подпись функции fortran в интерфейсном блоке недействительна, в частности, обозначение квадратных скобок. Вероятно, это расширение для конкретного компилятора. Строка, о которой идет речь, должна быть следующей: FUNCTION GetLastErrorMessagePtr () bind(c,name='GetLastErrorMessagePtr') Я думаю, что подчеркивание не требуется, поскольку bind(c) должен позаботиться о искажении имени, если не происходит чего-то странного, например, взаимодействия с C ++ или использования другого компилятора для вашего кода C. В общем, function func_name [stuff here] () не является допустимым объявлением функции Fortran. 16.07.2013

  • 5

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

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

    23.07.2018

    6

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

    Вы имеете в виду, что хотите вызвать процедуру Fortran из C? См. Этот пример: Вызов подпрограммы FORTRAN из C.

    РЕДАКТИРОВАТЬ: И 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
    
    02.04.2012
  • Здравствуйте, M.S.B. - Я пробовал символ (kind = c_char, len = 1), Dimension (255), который, кажется, что-то возвращает, но похоже, что первый элемент - это NULL char, что переводится как пустая строка. Поскольку он возвращает правильную строку при вызове непосредственно из C, я предполагал, что ошибка кроется в интерфейсе FORTRAN. Вызов FORTRAN из C не кажется таким сложным - по крайней мере, я могу достаточно легко переносить строки из FORTRAN в C. 02.04.2012

  • 7

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

    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.

    15.11.2016
  • Я не уверен, что это современный Фортран, а мой ответ - нет; на самом деле оба кода - это Fortran 2003. В любом случае ваш код проще, НО требует передачи длины строки (n) в качестве аргумента. Конечно, если заранее знать длину строки, код будет намного меньше, но и функция станет менее полезной. В большинстве случаев вы просто не знаете, какова длина строки C. 15.06.2017
  • @ Пап, ты прав. Я пояснил свой ответ, чтобы отразить это. Действительно, оба F2003. Я использовал автоматическое выделение LHS, но это тоже F2003. Для моего приложения у меня есть доступ к кодам C и Fortran, поэтому передать длину строки C в интерфейс не проблема, что значительно упрощает работу. 16.06.2017
  • Новые материалы

    Создание кнопочного меню с использованием HTML, CSS и JavaScript
    Вы будете создавать кнопочное меню, которое имеет состояние наведения, а также позволяет вам выбирать кнопку при нажатии на нее. Финальный проект можно увидеть в этом Codepen . Шаг 1..

    Внедрите OAuth в свои веб-приложения для повышения безопасности
    OAuth — это широко распространенный стандарт авторизации, который позволяет приложениям получать доступ к ресурсам от имени пользователя, не раскрывая его пароль. Это позволяет пользователям..

    Классы в JavaScript
    class является образцом java Script Object. Конструкция «class» позволяет определять классы на основе прототипов с чистым, красивым синтаксисом. // define class Human class Human {..

    Как свинг-трейдеры могут использовать ИИ для больших выигрышей
    По мере того как все больше и больше профессиональных трейдеров и активных розничных трейдеров узнают о возможностях, которые предоставляет искусственный интеллект и машинное обучение для улучшения..

    Как построить любой стол
    Я разработчик программного обеспечения. Я люблю делать вещи и всегда любил. Для меня программирование всегда было способом создавать вещи, используя только компьютер и мое воображение...

    Обзор: Машинное обучение: классификация
    Только что закончил третий курс курса 4 часть специализации по машинному обучению . Как и второй курс, он был посвящен низкоуровневой работе алгоритмов машинного обучения. Что касается..

    Разработка расширений Qlik Sense с qExt
    Использование современных инструментов веб-разработки для разработки крутых расширений Вы когда-нибудь хотели кнопку для установки переменной в приложении Qlik Sense? Когда-нибудь просили..