Clicky

Fortran Wiki
Generating C Interfaces (changes)

Skip the Navigation Links | Home Page | All Pages | Recently Revised | Authors | Feeds | Export |

Showing changes from revision #12 to #13: (追記) Added (追記ここまで) | (削除) Removed (削除ここまで) | (削除) Chan (削除ここまで)(追記) ged (追記ここまで)

Contents


Introduction

Interfacing C is an essential feature in making Fortran a more useful programming tool. With heterogeneous code, Fortran can do what it does best, while leaving other tasks to better suited programming languages.

Developing C interfaces manually is time consuming and requires a significant effort to maintain. The best approach is to develop a good interface-generator tool. In order to develop a successful interface tool, the first step is to decide how C should be interfaced.

Design Requirements

These are Joe Krahn’s suggestions for a good C interface design. Feel free to extend or modify it, or include comments about caveats and alternatives.

  1. The Fortran user API should have minimal exposure to C types.
  2. Conversion of strings between Fortran and C should be handled by auto-generated wrapper functions.
  3. Access to Fortran array dimensions should not use non-portable access to internal compiler data structures.
  4. The process should be as automatic as possible. This is not easy for a proper interface because C prototypes often do not fully describe arguments. Standard Annotation Language is very beneficial, but not widely used outside of Microsoft.

Issues

  • Some C functions that accept string arguments can benefit from prototypes using both CHARACTER passed by reference and TYPE(C_PTR). I don’t know if Fortran 2008 supports multiple INTERFACE specifications bound to the same C function. In Fortran 2003, it was ambiguous.

Wrapper Functions

Many people see (削除) ISO_C_BINDING (削除ここまで)(追記) iso_c_binding (追記ここまで) as a way to avoid wrapper functions when calling C procedures. That is true in some cases, but often leads to verbose code containing explicit ISO-C declarations, rather than presenting a nice Fortran interface to the user.

Wrapper functions are the way to go. Swig is a widely used tool to generate software interfaces. The generated interfaces deal with data conversions. For example, a Swig interface for Fortran would include string conversions, deal with all array data structures, and proxy derived-type information.

Although Swig is a useful approach, building an interface is also a fairly involved process. We need to an interface-generating tool that efficiently builds interfaces for common, simple code interfaces. This will help work out the important design issues. Eventually, Swig can be extended to Fortran to deal with more advanced programming (e.g. OOP and sub-classed derived-types).

Here is an example interface specification for calling an HDF5 C function. The comment at the beginning is the C prototype. In this design, C functions all include a ‘c’ suffix, and could be called directly if the caller wants to avoid the wrapper dependency.

! herr_t H5Lcopy( hid_t src_loc, const char *src_name, hid_t dst_loc, \\
! const char *dst_name, hid_t lcpl_id, hid_t lapl_id);
 interface
 integer(herr_t) &
 function H5Lcopy_c(src_loc, src_name, dst_loc, dst_name, lcpl_id, &
 lapl_id) &
 bind(C,name="H5Lcopy")
 import C_char, herr_t, hid_t
 integer(hid_t), value, intent(in) :: src_loc
 character(len=1,kind=C_char), dimension(*), intent(in) :: src_name
 integer(hid_t), value, intent(in) :: dst_loc
 character(len=1,kind=C_char), dimension(*), intent(in) :: dst_name
 integer(hid_t), value, intent(in) :: lcpl_id
 integer(hid_t), value, intent(in) :: lapl_id
 end function H5Lcopy_c
 end interface

Here is the corresponding wrapper code that handles Fortran-to-C string conversion.


! herr_t H5Lcopy( hid_t src_loc, const char *src_name, hid_t dst_loc, \\
! const char *dst_name, hid_t lcpl_id, hid_t lapl_id);
 subroutine H5Lcopy(src_loc, src_name, dst_loc, dst_name, lcpl_id, &
 lapl_id, hdferr)
 integer(hid_t), intent(in) :: src_loc
 character(len=*), intent(in) :: src_name
 integer(hid_t), intent(in) :: dst_loc
 character(len=*), intent(in) :: dst_name
 integer(hid_t), intent(in) :: lcpl_id
 integer(hid_t), intent(in) :: lapl_id
 integer(herr_t), intent(out), optional :: hdferr
 integer(herr_t) :: hdferr_
 hdferr_ = H5Lcopy_c(src_loc, trim(src_name)//NUL, dst_loc, &
 trim(dst_name)//NUL, lcpl_id, lapl_id)
 if (present(hdferr)) then
 hdferr = hdferr_
 return
 else if (.not.(hdferr_ >= 0)) then
 call hdf5_throw("H5Lcopy",hdferr_)
 end if
 end subroutine H5Lcopy

C++ class wrappers

The main drawback of interfacing C++ classes is that function overloading can combine void and non-void types, whereas Fortran does not allow mixing of function and subroutine types under the same generic name (IMHO, a serious design flaw).

Here is an example of providing an OOP interface to the FLTK C++ library. This shows just the user code, not the interfaces or wrapper code. The design is for all objects to be instantiated under the common generic subroutine new. All OOP functions give the object as the first argument, so there is never a procedure-matching conflict among different types, except for the subroutine/function design flaw.

This is a simple C++ FLTK example:

#include <stdio.h>
#include <FL/Fl_Button.H>
#include <FL/Fl_Window.H>
#include <FL/Fl_Input_Choice.H>

void buttcb(Fl_Widget*,void*data) {
 Fl_Input_Choice *in=(Fl_Input_Choice *)data;
 static int flag = 1;
 flag ^= 1;
 if ( flag ) in->activate();
 else in->deactivate();
}
void input_choice_cb(Fl_Widget*,void*data) {
 Fl_Input_Choice *in=(Fl_Input_Choice *)data;
 fprintf(stderr, "Value='%s'\
", (const char*)in->value());
}
int main(int argc, char **argv) {
 Fl::scheme("plastic"); // optional
 Fl_Window win(300, 200);
 Fl_Input_Choice in(40,40,100,28,"Test");
 in.callback(input_choice_cb, (void*)&in);
 in.add("one");
 in.add("two");
 in.add("three");
 in.value(1);
 Fl_Button onoff(40,150,200,28,"Activate/Deactivate");
 onoff.callback(buttcb, (void*)&in);
 win.end();
 win.resizable(win);
 win.show(argc, argv);
 return Fl::run();
}

This is the Fortran 90 equivalent. To achieve a user-friendly Fortran interface, some procedures require both Fortran and C wrapper routines. The C wrapper routines are mainly proxies between C and C++ call conventions and C++ function overloading (e.g. name-mangled symbols).

With Fortran 2003 type-bound procedures, the Fortran interface will appear even more like the C++ interface. However, generic procedure names do a good job of providing a clean interface.

subroutine button_cb(widget,data)
 use fltk
 type(FL_Widget) :: widget
 type(FL_Void) :: data
 type(Fl_Input_Choice) :: in
 logical, save :: flag = .true.
 in=data
 flag = .not. flag
 if ( flag ) then
 call activate(in)
 else
 call deactivate(in)
 end if
end subroutine button_cb
subroutine input_choice_cb(widget,data)
 use fltk
 type(FL_Widget) :: widget
 type(FL_Void) :: data
 type(Fl_Input_Choice) :: in
 in = data
 write(0,'(2A)') "Value=",get_value(in)
end subroutine input_choice_cb
program main
 use fltk
 type(Fl_Window) :: win
 type(Fl_Input_Choice) :: in
 type(Fl_Button) :: onoff
 external input_choice_cb,button_cb
 call Fl_scheme("plastic") ! optional
 call new(win,300,200)
 call new(in,40,40,100,28,"Test")
 call add(in,"one")
 call add(in,"two")
 call add(in,"three")
 call value(in,1)
 call new(onoff,40,150,200,28,"Activate/Deactivate");
 call callback(onoff, button_cb,FL_Void_type)
 call end(win)
 call resizable(win,win)
 call show(win)
 call Fl_run()
end program main

String Handling

Typical examples of using (削除) ISO_C_BINDING (削除ここまで)(追記) iso_c_binding (追記ここまで) pass strings as follows:

 CALL C_FUNC(TRIM(F_STRING)//C_NULL_CHAR)

The approach above requires F_STRING to be KIND=C_CHAR. In most cases, the C_CHAR kind will be the same as the default kind, but this should not be relied on for portable code. Instead, a Fortran wrapper function can convert from default kind to C_CHAR as follows, using a local temporary copy:

 CHARACTER(LEN=LEN_TRIM(F_STRING)+1,KIND=C_CHAR) :: C_STRING
 C_STRING = TRIM(F_STRING)//C_NULL_CHAR
 CALL C_FUNC(C_STRING)

Note that in both cases, a temporary string is created, so this method adds portability with no overhead. An intrinsic CHAR(_string_,_kind_) function would simplify this. A helper function can convert the character kind specific for Fortran-to-C as follows:

 PURE FUNCTION F_C_STRING_FUNC(F_STRING) RESULT(C_STRING)
 USE, INTRINSIC :: ISO_C_BINDING, ONLY: C_CHAR, C_NULL_CHAR
 IMPLICIT NONE
 CHARACTER(LEN=*), INTENT(IN) :: F_STRING
 CHARACTER(LEN=1,KIND=C_CHAR) :: C_STRING(LEN_TRIM(F_STRING)+1)
 C_STRING = F_STRING // C_NULL_CHAR
 END FUNCTION F_C_STRING_FUNC

Unfortunately, on at least the GNU and Intel compilers, the statement

 C_STRING = F_STRING // C_NULL_CHAR

causes C_STRING to contain LEN_TRIM(F_STRING) copies of F_STRING’s first character followed by a null character. Explicitly copying each character as follows seems to be required:

 PURE FUNCTION F_C_STRING_FUNC (F_STRING) RESULT (C_STRING)
 USE, INTRINSIC :: ISO_C_BINDING, ONLY: C_CHAR, C_NULL_CHAR
 IMPLICIT NONE
 CHARACTER(LEN=*), INTENT(IN) :: F_STRING
 CHARACTER(LEN=1,KIND=C_CHAR) :: C_STRING(LEN_TRIM(F_STRING)+1)
 INTEGER :: N, I
 N = LEN_TRIM(F_STRING)
 DO I = 1, N
 C_STRING(I) = F_STRING(I:I)
 END DO
 C_STRING(N + 1) = C_NULL_CHAR
 END FUNCTION F_C_STRING_FUNC

The DO loop seems mandatory. The trivial slice F_STRING(I:I) admittedly looks odd but is also necessary– F_STRING(I) is parsed as an invalid function call and fails to compile.

An important question is whether to treat strings as blank-padded, or require the caller to pass only the "valid" part of the string. Another problem is how to return strings of varying length. There could be a length return value, like GET_COMMAND_ARGUMENT. A better option is to use allocatable-length strings, but they are not yet widely supported.

The right answer is probably not to use a single method. If a string argument can have significant terminal blanks, the above examples will not work. Otherwise, the automatic-trimming method is a simplification for the caller, at least until variable-length strings become the norm in Fortran.

Discussion

  • Why is C_STRING declared as an array? This is definitely necessitating the loop because you are trying to assign a scalar object to an array. Does the following not work?
    :
    CHARACTER(len=LEN_TRIM(f_String)+1,kind=C_CHAR) :: c_string
    :
    c_string = TRIM(f_string) // C_NULL_CHAR

    I can’t remember if Fortran doesn’t like character functions whose return argument’s len parameter is not a compile time constant. One possible work around would be to make the return variable an allocatable scalar, which should be kosher according F2003. –IzaakBeekman

Revised on March 3, 2023 11:01:14 by Jason Blevins (23.245.217.121) (11685 characters / 5.0 pages)
Edit | Back in time (12 revisions) | Hide changes | History | Views: Print | TeX | Source | Linked from: HomePage, Interoperability, Fortran and Cpp objects

AltStyle によって変換されたページ (->オリジナル) /