Fortran

Guide To Learn

Initializing the IP address structure

Now that we have a derived type to interface with the ipaddr struct, let’s create an instance of it. libdill provides two functions to instantiate the ipaddr struct: ipaddr _local and ipaddr_remote. The former is used on the server side, where you want to open up a socket to listen to incoming connections. The latter is used on the client side, where you want to connect to a socket on a remote server.

Unlike the ipaddr struct we had to look up in the libdill header file, ipaddr_local is described in the libdill documentation pages (http://libdill.org/documentation). Specifically, for ipaddr_local, here’s the prototype:

int ipaddr_local(           ❶
    struct ipaddr* addr,    ❷
    const char* name,       ❸
    int port,               ❹
    int mode);              ❺

❶ This function returns an int.

❷ The first argument is an ipaddr struct.

❸ The second argument is a character string name.

❹ The third argument is an int port number.

❺ The final argument is an int mode.

This is the header (or, in C terminology, prototype) of the ipaddr_local function. The first word, int, before the function name, states its type. Inside the parentheses, we have a list of arguments, just like in Fortran functions.

The libdill documentation further describes the arguments:

  • addr–Output argument; the IP address object
  • name–Name of the local network interface, such as “eth0”, “192.168.0.111” or “::1”
  • port–Port number (Valid values are 1-65535.)
  • mode–What kind of address to return (IPv4 or IPv6)

The return value is a 0 in case of a success and a -1 in case of an error.

Unlike all the functions we’ve written before, this function has an output argument, addr. This is required so that a variable can be modified in-place. Recall that in Fortran we use subroutines specifically when we need to modify values of arguments in-place. As C has only functions and no separate concept of subroutines, arguments can be modified in-place by passing them by reference instead of by value. Take note that addr and name are declared with an * (asterisk) that immediately follows their types (struct ipaddr* and char*, respectively), while port and mode aren’t. There’s an important difference here that we’ll dig into shortly.

As for the mode argument, libdill defines its possible values as compile-time constants in libdill.h:

#define DILL_IPADDR_IPV4 1
#define DILL_IPADDR_IPV6 2

These constants are defined as C preprocessor macros. When we compiled libdill, the preprocessor first parsed and manipulated the C source files before passing them to the compiler. In this case, the preprocessor replaced any occurrence of DILL_IPADDR _IPV4 with the literal constant 1 (and likewise with DILL_IPADDR_IPV6). For transparency in the Fortran interface with libdill, we’ll define the integer parameter with the same name and value, declaring a compile-time constant for IP address mode:

use iso_c_binding, only: c_char, c_int
 
integer(c_int), parameter :: IPADDR_IPV4 = 1

Here, we encounter another C-equivalent type kind parameter, c_int. This is a Fortran representation of C’s int type, and, like c_char, is also available from iso_c_binding. We’ll now be able to invoke ipaddr_local by passing IPADDR_IPV4 as the mode argument, rather than having to remember to pass the value 1.

If you’re wondering why each argument is listed on a separate line, it’s merely a choice of style. Listing the arguments on individual lines usually makes for more readable documentation. However, the C code itself doesn’t require it.

Types and their representations

We could casually say that C’s int and Fortran’s integer are the same types. However, this isn’t strictly true. They belong to different languages, so even though they seem like they could be similar or equal, they’re not. What matters for us, in practice, is that their representation in memory is the same. Likewise for C’s float and Fortran’s real, or C’s char and Fortran’s character.

As you can probably guess, we need to define a Fortran function interface whose return value and arguments will match those of the C function, as shown in listing 11.2.

Listing 11.2 Defining a Fortran interface to the ipaddr_local C function

module mod_dill
 
  use iso_c_binding, only: c_char, c_int                 ❶
  implicit none
 
  private
  public :: ipaddr, ipaddr_local, IPADDR_IPV4            ❷
 
  integer(c_int), parameter :: IPADDR_IPV4 = 1           ❸
  ...
  interface                                              ❹
 
    integer(c_int)                                  &    ❺
      function ipaddr_local(addr, name, port, mode) &    ❺
      bind(c, name='dill_ipaddr_local')                  ❻
      import :: c_char, c_int, ipaddr                    ❼
      type(ipaddr), intent(out) :: addr                  ❽
      character(c_char), intent(in) :: name(*)           ❾
      integer(c_int), value, intent(in) :: port          ❿
      integer(c_int), value, intent(in) :: mode          ❿
    end function ipaddr_local
 
  end interface
 
end module mod_dill

❶ Imports C-equivalent type kinds from the built-in module

❷ Makes them publicly available

❸ Defines a compile-time constant

❹ Opens an interface block

❺ This function will return a c_int status code.

❻ We’ll bind this to dill_ipaddr _local.

❼ Imports types into the local interface scope

❽ The first argument is an output argument.

❾ The second argument is an input character string.

❿ The third and fourth arguments are c_int input arguments.

There’s a lot to unpack here, so I’ll go slowly. First, we use the function statement to define the function name and its arguments. This works the same way as with all Fortran functions we worked with before. One key difference is that here we add the bind attribute, like we did when defining the ipaddr derived type. Here, we also specify the name keyword argument in the parentheses, which is the name of the C function that we’ll bind our Fortran interface to. Notice that we’re binding to the name dill_ ipaddr_local, rather than just ipaddr_local. This is because libdill by default adds a prefix dill_ to all its functions to avoid name conflicts with any similar library that may export functions with the same name.

Second, recall that Fortran passes arguments by reference, meaning that no new copy of an argument is created inside the function. In contrast, C passes arguments by value, meaning that a new copy is created inside the function scope. This means that when the argument in the C function definition is a pointer, we’ll interface with it from Fortran as is, because of its default pass-by-reference behavior. However, for regular, nonpointer C arguments, Fortran provides a special attribute, value, which instructs the compiler that the argument is to be passed by value, rather than by reference. Figure 11.5 illustrates this rule.

Figure 11.5 Interfacing ipaddr_local from Fortran

Finally, there’s a new statement here, import. This statement makes the listed entities available inside the interface scope, which is otherwise local and isolated from the module scope.

Now that we have the Fortran interface to ipaddr_local defined and ready for use, let’s write a program that will initialize the ipaddr data structure with the IP address at 127.0.0.1 (local host) and port number 5555.

Listing 11.3 Fortran TCP server initializing only the IP address and port number

program server
 
  use iso_c_binding, only: c_int, c_null_char               ❶
  use mod_dill, only: ipaddr, ipaddr_local, IPADDR_IPV4     ❷
 
  implicit none
  integer(c_int) :: rc                                      ❸
  type(ipaddr) :: addr                                      ❹
 
  rc = ipaddr_local(addr, '127.0.0.1' // c_null_char, & 
                    5555_c_int, IPADDR_IPV4)            
 
end program server

❶ Imports C-type kind parameters from the built-in module

❷ Imports Fortran interfaces with C struct and function

❸ Declares a return code as a C int

❹ Calls ipaddr_local to initialize the addr instance

We begin by importing two constants from iso_c_bindingc_int, a C integer equivalent, and c_null_char, a special constant that we’ll use to terminate C strings. From our new module, mod_dill, we import the ipaddr type and our new ipaddr_local interface.

Our Fortran server program so far has only one function call to ipaddr_local, which stores the IP address and port number parameters in an internal representation that libdill uses. It’s at this call to ipaddr_local that we need to be super careful. First, C strings are always terminated with the null character, '\0'. From Fortran, we can do this in a portable way by appending c_null_char to the character string that we pass to the C function. Thus, instead of passing '127.0.0.1' as the second argument to ipaddr_local, we pass '127.0.0.1' // c_null_char. Second, recall that ipaddr_local expects the third and fourth arguments to be of C int type, not Fortran integers! We thus need to cast our arguments to the C int types. We do this by appending the c_int suffix to the value: 5555_c_int. What we’re passing as the mode argument is already declared as an integer(c_int) constant, so no explicit conversion is needed here.

Finally, we assign the result of the ipaddr_local function to rc, the integer return code. The value of the return code indicates whether the function executed correctly or not. For example, in C and many other programming languages, as well as UNIX and Linux command-line tools, the return code value of zero indicates that the function (or program) finished successfully, that is, without errors. In contrast, a nonzero value, positive or negative, indicates an error, and the specific value of the return code can be used to encode what kind of error it is. If we want, we can explicitly check the value of rc to ensure that the function call worked. This is a common way to do exception handling in C, and we can use it in the Fortran interface as well. The libdill documentation website lists all the error codes that you may encounter while using this library.

IP address and port number values

There’s no special meaning behind the value 5555 for the port number. The port number should be no larger than 65535, and not already in use. On most systems, values greater than 1024 are safe to use. Various servers or web frameworks commonly use arbitrary values in development, such as 4000, 5555, or 8080.

You can compile and run this program. If all is good, it will output nothing. We expect this, as the program does nothing but instantiate a data structure and stop. However, currently we don’t know if ipaddr is initialized correctly. We’ll test that the IP address and port number values have been stored correctly in the next section.

Initializing the IP address structure

Leave a Reply

Your email address will not be published. Required fields are marked *

Scroll to top