11

我已经为此困扰了大约一个星期,并且已经在一个又一个论坛中搜索了一个关于如何将 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);

来自 FORTRAN:

! 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 返回一个 char* 时,我遇到一个又一个问题。我认为应该奏效的一种尝试是:

// 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

(我不能从字面上使用 DIMENSION(*),所以我已经超大到 255。)

应该返回一个指向 255 个 C 样式字符的数组的指针 - 但如果是这样,我无法将其转换为有意义的字符串。在实践中,它会返回一组随机字符,从 Wingdings 到“bell”字符的任何位置......

我也试图返回:

  • 指向 CHARACTER (LEN=255, KIND=C_CHAR) 的指针。
  • 字面意思是 CHARACTER(LEN=255, KIND=C_CHAR)。
  • 一个 INTEGER(C_SIZE_T),并试图将其巧妙地转换为一个指向字符串数组的指针。
  • 一个人物。
  • 等等

如果有人可以给我一个如何做到这一点的例子,我将非常感激......

最好的祝福,

麦克风

4

7 回答 7

14

动态长度的字符串在 C 交互中总是有点棘手。一个可能的解决方案是使用指针。

首先是一个简单的案例,您必须将一个以空字符结尾的字符串交给 C-Function。如果你真的只传递字符串,你必须确保用 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-Routine 给出(也来自上面提到的 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 解释为 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 ])
于 2012-04-02T09:23:33.297 回答
5

感谢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提供此解决方案 - 如果没有他的意见,我将不知道如何做到这一点。

于 2012-04-03T08:20:07.640 回答
5

该线程有点旧,但是由于我遇到了类似的问题(可能其他人也会遇到),所以我还是发布了答案。

如果由于某种原因 C 字符串为空,则上面发布的代码将导致分段错误。此外,由于 Fortran 2003/2008 支持返回可分配实体的函数,因此无需返回 255 个字符的字符串(在使用前可能需要对其进行修剪)。使用上面发布的所有信息,我最终得到了以下函数,它获取一个 C 字符串(指针),并返回相应的 Fortran 字符串;如果 C 字符串为空,则返回“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
于 2013-11-21T12:43:42.087 回答
2

我一直在为这些互操作性特性而苦恼。我认为你的界面应该声明

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

并且,当您调用该函数时,您传递了一个相应的 Fortran 字符变量,其长度等于或大于您希望返回的 C 字符数组的长度。

由于您似乎拥有 Intel Fortran,请查看提供的代码示例,它们为此提供了完整的示例。

我想您知道您发布的内容在语法上不正确 Fortran 吗?

于 2012-04-02T08:25:14.770 回答
2

我也一直在努力调用一个返回字符串的 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

希望这个例子能让下一个有同样问题的例子更容易。

于 2018-07-23T00:39:45.527 回答
1

在 Fortran 中,该项目需要声明为“字符 (kind=c_char,len=1),维度 (255)”,而不是 len=255。这将创建一个长度为 1 的字符数组,这是您在 C 端所需要的。令人困惑的是,有一个例外允许 Fortran 将字符串与一维数组进行匹配。

你的意思是你想从C调用一个Fortran过程?请参阅此示例:从 C 调用 FORTRAN 子例程

编辑: 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
于 2012-04-02T12:38:48.053 回答
0

如果您知道字符串的长度,那么上面 Pap 的回答可以大大简化:

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 字符串的副本。

于 2016-11-15T20:09:06.650 回答