2012-04-02 91 views
7

我已經在這裏停留了大約一個星期,並且已經在論壇之後搜索了關於如何從C發送char *的清晰解釋的論壇FORTRAN。爲了使事情更令人沮喪,發送一個char *從FORTRAN到C的說法是直線前進...創建FORTRAN接口到一個返回char *的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 

(我不能隨便使用維度(*),所以我已經過大,以255。 )

This 應該返回一個指向255個C風格字符數組的指針 - 但是如果是這樣,我一直無法將其轉換爲有意義的字符串。在實踐中,它返回一個隨機的字符集,從任何地方宋體的「鍾」字......

我還試圖返回:

  • 指向字符(LEN = 255, KIND = C_CHAR)。
  • 字面字符(LEN = 255,KIND = C_CHAR)。
  • A INTEGER(C_SIZE_T),並試圖將它轉換爲指向字符串數組的指針。
  • A CHARACTER。

如果任何人都可以給我如何做到這一點的例子,我將非常感激......

最好的問候,

邁克

+0

你正在使用什麼工具鏈? – 2012-04-02 08:07:26

+0

我使用VC++編譯器,但在純C中編譯接口。FORTAN是Visual Fortran 2011,我認爲它是FORTRAN 90.但是,這是針對已發佈的API,因此它必須可以從可用的許多種類調用... – 2012-04-02 08:19:15

回答

12

動態長度的字符串與C交互總是有點棘手。一個可能的解決方案是使用指針。

首先是一個簡單的情況,您必須將一個空字符終止的字符串交給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_getfield的interface樣子:

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具有以下interface

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 ]) 
+0

謝謝,heraldkl!上面的例子是接口到一個無效的C函數 - 當C函數的返回類型是char *時,這種方法會工作嗎?我的一般操作範例是爲我的API函數返回結果,而不是通過void子例程傳遞它們,並且在使用所有其他數據類型時工作正常除了字符:-( – 2012-04-02 09:37:31

+0

我猜上面的方法也適用於非void功能,我沒有看到一個理由,爲什麼這應該受到返回值的影響 – haraldkl 2012-04-02 10:10:32

+0

我當然樂意使用指針 - 實際上,這是我的第一種方法。恐怕我遇到了麻煩上面提供的 - 是否有必要將C指針轉換爲一個常量字符數組爲FORTRAN字符串?換句話說,如果我有一個TYPE(C_PTR)和一個字符串長度,我需要的最小值是多少將此轉換爲FORTRAN字符串?對不起,我是鈍的... – 2012-04-02 10:51:04

2

我總是與這些互操作性功能進行鬥爭。我認爲你的接口應該聲明

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

而且,當你調用該函數,你傳遞一個相應的Fortran字符變量比的字符C你希望返回數組的長度等於或大於長度。

由於您似乎擁有英特爾Fortran,請查看提供的代碼示例,他們給出了一個完整的示例。

我想你知道你發佈的內容在語法上不正確Fortran嗎?

+0

你的意思是::之前的逗號?對不起 - 剪切和粘貼錯誤!我會給上面的一個去... – 2012-04-02 09:06:08

+0

我剛剛嘗試過,但我得到的編譯錯誤與我在嘗試類似時發生的編譯錯誤相同:「錯誤錯誤#6688:數組規範沒有POINTER屬性的函數名稱必須是顯式形狀數組(R505.9)。「這就是爲什麼我嘗試基本相同,但255而不是* - 但沒有成功:-( – 2012-04-02 09:09:12

+0

我試過使用「CHARACTER(KIND = C_CHAR),DIMENSION(255):: GetLastErrorMessage」,而不是讀入一個相同規格的字符數組,但是這顯然返回了一個255個隨機字符的數組... – 2012-04-02 09:13:27

1

在Fortran中的項目需要被宣佈爲 「字(KIND = c_char,LEN = 1),尺寸(255)」 拉澤比len = 255。這將創建一個長度爲1的字符數組,這是您在C端需要的。令人困惑的是,有一個例外允許Fortran將字符串與一維數組進行匹配。

你是說你想從C調用一個Fortran程序?看到這個例子:Calling a FORTRAN subroutine from 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 
+0

Hello M.S. - 我嘗試過字符(kind = c_char,len = 1),dimension(255),它似乎返回了一些東西,但它看起來第一個元素是一個NULL字符 - 它轉換爲空字符串。由於它直接從C中調用時返回正確的字符串,所以我一直假定故障在於FORTRAN接口。從C調用FORTRAN看起來並不那麼困難 - 或者至少,我可以很容易地將字符串從FORTRAN轉換爲C. – 2012-04-02 13:44:37

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提供這種解決方案 - 我不知道如何在沒有他的輸入的情況下做到這一點。

+0

完全沒問題,很高興它有幫助;) – haraldkl 2012-04-03 18:53:06

4

此線程有點老,但由於我有類似的問題(可能其他人會),我反正發表一個答案。

如果出於某種原因,C字符串爲空,上面提供的代碼將導致分段錯誤。另外,不需要返回255字符的字符串(在使用之前可能需要修剪),因爲Fortran 2003/2008支持返回可分配實體的函數。使用上面發佈的所有信息,我得到了以下函數,它獲得一個C字符串(指針),並返回相應的Fortran字符串;如果C字符串爲空,則返回「NULL」,類似於C中的「(空)」印刷在類似的情況:

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 
+0

我發佈了一些簡化你的子程序[下](http://stackoverflow.com/a/40618786/479532)。 – 2016-11-15 20:09:38

0

如果知道字符串的長度,然後巴氏的answer以上可大大簡化:

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字符串返回。

+0

我不確定這是現代Fortran,而我的答案不是;實際上這兩個代碼都是Fortran 2003.無論如何,你的代碼比較簡單,但是需要傳遞字符串長度('n')作爲參數。當然,事先知道字符串的長度會使代碼變得更小,但也使得該函數不太有用。在大多數情況下,你只是不知道C字符串是多久。 – Pap 2017-06-15 11:18:33

+0

@Pap,你是對的。我已經澄清了我的答案以反映這一點。事實上,兩者都是F2003。我使用了LHS的自動分配,但這也是F2003。對於我的應用程序,我可以訪問C和Fortran代碼,因此將C字符串的長度傳遞到接口中並不是問題,這大大簡化了事情。 – 2017-06-16 14:34:26