2012-04-02 3 views
7

Ich habe seit etwa einer Woche hier gehalten, und Forum for Forum für eine klare Erklärung, wie man ein Zeichen * von C senden nach Fortran. Um die Sache mehr frustrierend zu machen, das Senden ein char * Argument von Fortran nach C war geradlinig ...Erstellen einer FORTRAN-Schnittstelle zu einer C-Funktion, die ein Zeichen zurückgibt *

ein char * Argument von Fortran nach C Senden (das funktioniert gut):

// The C header declaration (using __cdecl in a def file): 
extern "C" double GetLoggingValue(char* name); 

Und von FORTRAN:

Wenn ich versuche, analoge Logik zu verwenden, um ein char * von C zurückzugeben, bekomme ich Problem nach Problem. Ein Versuch, den ich fühlte, sollte funktionieren ist:

// The C declaration header (using __cdecl in a def file): 
extern "C" const char* GetLastErrorMessage(); 

Und die Fortran-Schnittstelle:

INTERFACE 
    FUNCTION GetLastErrorMessage [C, ALIAS: '_GetLastErrorMessage']() 
     USE ISO_C_BINDING 
     CHARACTER(LEN=1, KIND=C_CHAR), DIMENSION(255), :: GetLastErrorMessage 
    END FUNCTION GetLastErrorMessage 
END INTERFACE 

(Ich kann buchstäblich nicht die Dimension (*) verwenden, also habe ich zu 255 Übermaß gegangen.)

Diese sollte einen Zeiger auf ein Array von 255 C-style-Zeichen zurückgeben - aber wenn dies der Fall ist, konnte ich dies nicht in eine sinnvolle Zeichenfolge konvertieren. In der Praxis gibt es eine zufällige Menge von Zeichen, überall von Wingdings auf den 'Glocke' Charakter ...

Ich habe auch versucht, zurückzukehren:

  • Ein Zeiger auf CHARACTER (LEN = 255, KIND = C_CHAR).
  • Wörtlich CHARACTER (LEN = 255, KIND = C_CHAR).
  • Ein INTEGER (C_SIZE_T), und versucht, das in einen Zeiger auf ein String-Array zu verfeinern.
  • A CHARACTER.
  • usw.

Wenn jemand mir ein Beispiel dafür, wie dies zu tun geben kann, wäre ich sehr dankbar ...

Mit freundlichen Grüßen,

Mike

+0

Welche Toolchains verwenden Sie? –

+0

Ich benutze VC++ Compiler, aber kompilieren die Schnittstelle in pure C. Die FORTAN ist Visual Fortran 2011, die ich glaube, ist FORTRAN 90. Dies ist jedoch für eine veröffentlichte API, so muss es von so vielen Geschmacksrichtungen wie möglich aufrufbar sein ... –

Antwort

12

Strings mit dynamischer Länge sind mit der C-Interaktion immer ein bisschen schwierig. Eine mögliche Lösung ist die Verwendung von Zeigern.

Zuerst ein einfacher Fall, in dem Sie eine mit Nullzeichen abgeschlossene Zeichenfolge an eine C-Funktion übergeben müssen. Wenn Sie die Zeichenfolge wirklich nur übergeben, müssen Sie sicherstellen, dass sie mit dem c_null_char abgeschlossen wird, daher ist diese Richtung ziemlich einfach. Hier sind einige Beispiele aus einer 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 

Und die interface von lua_getfield wie folgt aussieht:

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 

Und die C-Code-Schnittstelle ist:

void lua_getfield (lua_State *L, int idx, const char *k) 

Nun ist die etwas komplexer Fall , wo wir mit einer zurückgegebenen Zeichenfolge von C mit einer dynamischen Länge umgehen müssen. Die portabelste Lösung, die ich bisher gefunden habe, ist die Verwendung von Zeigern. Hier ist ein Beispiel mit einem Zeiger, wobei die Zeichenkette durch die C-Routine gegeben ist (auch von der Aotus Bibliothek wie oben erwähnt):

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 

wo lua_tolstring hat folgende 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 

Schließlich hier ist ein Versuch, zu klären, wie ein c_ptr kann als Fortran Zeichenfolge interpretiert werden: Angenommen, Sie eine c_ptr bekam auf den String zeigen:

type(c_ptr) :: a_c_string 

und die Länge davon durch eine len Variable mit folgenden Art gegeben:

integer(kind=c_size_t) :: stringlen 

Sie wollen in Fortran diese Zeichenfolge in einem Zeiger auf eine Zeichenkette erhalten: So

character,pointer,dimension(:) :: string 

Sie die Zuordnung zu tun:

call c_f_pointer(a_c_string, string, [ stringlen ]) 
+0

Danke, Heraldkl!Das obige Beispiel soll eine Schnittstelle zu einer void C-Funktion sein - würde dieser Ansatz funktionieren, wenn der Rückgabetyp der C-Funktion ein char * ist? Mein allgemeines Betriebsparadigma ist, dass meine API-Funktionen das Ergebnis zurückgeben, anstatt sie über Void-Subroutinen zu übergeben, und dies funktioniert auch bei Verwendung aller anderen Datentypen. AUSSER chars :-( –

+0

Ich schätze, der obige Ansatz würde auch mit nicht-void funktionieren Ich sehe keinen Grund, warum dies durch den Rückgabewert beeinflusst werden sollte – haraldkl

+0

Ich bin sicher froh Zeiger zu verwenden - das war meine erste Annäherung Ich habe Angst, dass ich Probleme habe, dir die Schnipsel zu folgen habe oben - ist es alles notwendig, um einen C-Zeiger zu einem CONST-Char-Array zu einem FORTRAN-String zu konvertieren? Um es anders auszudrücken, wenn ich einen TYPE (C_PTR) und eine String-Länge habe, was ist das Minimum, das ich brauche konvertiert dies in eine FORTRAN-Zeichenfolge? Sorry, stumpf zu sein ... –

2

ich immer kämpfen mit diesen Interoperabilitätsfunktionen. Ich denke, dass Ihre Schnittstelle

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

erklären sollte, und dass, wenn Sie die Funktion aufrufen, übergeben Sie eine entsprechende Fortran Zeichenvariable mit einer Länge gleich oder größer als die Länge des Arrays von C Zeichen, die Sie erwarten, zurückzukehren .

Da Sie scheinen, Intel Fortran zu haben, sehen Sie sich die bereitgestellten Codebeispiele an, sie geben ein vollständiges Beispiel dafür.

Ich nehme an, Sie wissen, dass das, was Sie geschrieben haben, nicht syntaktisch korrekt Fortran ist?

+0

Du meinst das Komma vor dem ::? Entschuldigung - ein Fehler beim Ausschneiden und Einfügen! Ich gebe das oben eine Go ... –

+0

Ich habe es gerade versucht, aber ich bekomme den gleichen Kompilierungsfehler wie ich, als ich etwas ähnliches versucht habe: "Fehler Fehler # 6688: Die Array-Spezifikation für eine Funktionsname ohne POINTER-Attribut muss ein explizites Shape-Array (R505.9) sein. " Deshalb habe ich im Prinzip das selbe versucht, aber mit 255 statt * - aber mit keinem Erfolg :-( –

+0

habe ich versucht mit "CHARACTER (KIND = C_CHAR), DIMENSION (255) :: GetLastErrorMessage" statt, und Ein Array von 255 zufälligen Zeichen wird zurückgegeben ... –

1

In Fortran das Element als deklariert werden muss "Charakter (Art = c_char, len = 1), Dimension (255)" Rathe r als len = 255. Dadurch wird ein Array von Zeichen der Länge eins erstellt, das Sie auf der C-Seite benötigen. Was verwirrend sein kann, ist, dass es eine Ausnahme gibt, die es Fortran erlaubt, Strings mit eindimensionalen Arrays zu vergleichen.

Sie meinen, dass Sie eine Fortran-Prozedur von C aufrufen möchten? Siehe dieses Beispiel: Calling a FORTRAN subroutine from C.

BEARBEITEN: Sowohl ifort als auch gfortran sagen, dass Arrays nicht erlaubt sind, da die Funktion in diesem Kontext zurückkehrt. Das macht das Zurückgeben von Strings als Funktionsargumente von C nach Fortran schwieriger als das Verwenden einer Zeichenfolge als Argument (Beispiel in der obigen Verknüpfung) ... Sie müssen den Zeiger und dann den intrinsischen c_f_pointer Fortran verwenden, um von der C-Zeichenfolge in eine Fortran-Zeichenfolge zu konvertieren , wie von haraldkl erklärt. Hier ist ein weiteres Codebeispiel:

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

Hallo M.S.B. - Ich habe versucht, character (kind = c_char, len = 1), dimension (255), die etwas zurückzugeben scheint, aber es scheint, dass das erste Element ein NULL-char ist - was zu einer leeren Zeichenfolge. Da es beim Aufruf direkt von C die korrekte Zeichenfolge zurückgibt, habe ich angenommen, dass der Fehler in der FORTRAN-Schnittstelle liegt. FORTRAN von C aufzurufen scheint nicht so schwierig zu sein - oder zumindest kann ich einfach Strings von FORTRAN nach C übertragen. –

5

Mein Dank geht an heraldkl für mich die Lösung für dieses sehr frustrierend Problem geben.Ich poste, was ich schließlich implementiert haben, welche Rollen die Zeiger Umwandlung in die Schnittstelle, die Endanwendung Sinn können die C-Funktion aufrufen, ohne sich um den Zeiger Umwandlung kennen:

Die C-Funktion:

// The C declaration header (using __cdecl in a def file): 
extern "C" const char* GetLastErrorMessage(); 

Die Fortran-Interface-Modul:

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 

Und diese Funktion von einem Fortran-Programm zu nennen:

USE MINTERFACES 

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

Mein Dank noch einmal an heraldkl für die Bereitstellung dieser Lösung - ich hätte keine Ahnung gehabt, wie dies ohne seine Eingabe tun.

+0

Kein Problem, froh, dass es geholfen hat;) – haraldkl

4

Dieser Thread ist ein wenig alt, aber da ich ein ähnliches Problem hatte (und wahrscheinlich auch andere), poste ich trotzdem eine Antwort.

Die oben angegebenen Codes verursachen einen Segmentierungsfehler, wenn die C-Zeichenfolge aus irgendeinem Grund null ist. Darüber hinaus ist es nicht erforderlich, eine Zeichenfolge mit 255 Zeichen zurückzugeben (die wahrscheinlich vor der Verwendung getrimmt werden muss), da Fortran 2003/2008 Funktionen unterstützt, die zuweisbare Entitäten zurückgeben. Unter Verwendung aller oben angegebenen Informationen habe ich die folgende Funktion gefunden, die eine C-Zeichenfolge (Zeiger) erhält und die entsprechende Fortran-Zeichenfolge zurückgibt; Wenn das C-String null ist, ist es „NULL“ zurück, ähnlich wie C-Schema „(null)“ in ähnlichen Fällen gedruckt:

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

Ich habe eine kleine Vereinfachung Ihres Unterprogramms [unten] (http://stackoverflow.com/a/40618786/479532) veröffentlicht. –

0

Wenn Sie die Länge der Zeichenfolge kennen, dann Paps answer oben stark vereinfacht werden kann :

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 

die obige Funktion nimmt einen Zeiger C mit der Zeichenkette und der Länge der Zeichenfolge und gibt eine Kopie als Fortran-String.

+0

Ich bin mir nicht sicher, dass das moderne Fortran ist, während meine Antwort nicht ist; in der Tat sind beide Codes Fortran 2003. Wie auch immer, Ihr Code ist einfacher, aber erfordert die Länge der Zeichenfolge ("n") als Argument übergeben. Wenn Sie die Länge der Zeichenfolge im Voraus kennen, ist der Code natürlich viel kleiner, aber die Funktion ist auch weniger nützlich. In den meisten Fällen wissen Sie einfach nicht, wie lang die C-Saite ist. – Pap

+0

@Pap, du hast Recht. Ich habe meine Antwort geklärt, um das zu reflektieren. In der Tat sind beide F2003. Ich habe die automatische Zuordnung der LHS verwendet, aber das ist auch F2003. Für meine Anwendung habe ich Zugriff auf C- und Fortran-Codes, und so ist es kein Problem, die Länge der C-Zeichenfolge in die Schnittstelle zu übertragen, was die Dinge erheblich vereinfacht. –

Verwandte Themen