Tuesday, February 2, 2016

Swift for Linux part 2 – Using C libraries with Swift

Swift developers coming from Apple’s iOS and OS X environments are use to using the Cocoa and Cocoa Touch frameworks however these frameworks are not available in the Linux environment.  When we develop Linux applications and utilities with Swift we need to use the system libraries provided by the Linux environment.  In this post will look at how we can use these system libraries with our Swift code to build useful applications and utilities. 

In this post we will look at the Glibc module that Apple provides for us which includes the majority of the Linux Standard Library.  We will also look at how we could create our own modules to add additional system libraries not included in the Glibc module.  To demonstrate the concepts discussed we will conclude this post by building a simple command line utility that will list the IP addresses of the device that it is run on.

Modules


A module in Swift is code that is distributed as a single unit that can then be imported into other modules using Swift’s import keyword.  Frameworks and applications are examples of modules in Swift. In this post we are going to be looking at a special kind of module that can be used to map system libraries so we can import and use them into our Swift code. 

The Linux port of Swift comes with a predefined module named Glibc that contains most of the Linux standard library however there are numerous headers that have not been imported in it.  This module is similar to the Darwin module on Apple platforms.  Lets start off by looking at tthe Glibc module and then we will look at defining our own modules to map other system libraries that we may need.

To see what headers are defined in the Glibc module view the module.map file located in the usr/lib/swift/glibc directory of your Swift installation.  Don’t worry if you do not fully understand the format of this file at this time, we will be looking at how to create module.map files later on in this post.  For right now, knowing that any header that is defined in this file will be included simply by importing the Glibc module is enough.

In my previous post, Swift for Linux part 1, we saw an example of how to use the Glibc module.  In that example we created an extension to the Array type that would randomly select an element from the array.  We used the random() function provided by the system to generate the random number in the extension.  Lets take a look at the code for this example again so we can see how it worked.  The following code shows the Array extension:

import Foundation
import Glibc

extension Array {
   
    func getRandomElement() -> Element {
           let index = Int(random() % self.count)
           return self[index]
    }
} 

In this example the code starts off with two import statements.  The first import statement imports the Foundation framework.  The Foundation framework defines the basic functionality that is needed for most applications.  Most if not all of your Swift source files will need to import the Foundation framework.

The second import statement imports the Glibc framework.  This import statement allows us to use the system libraries that are defined in the Glibc framework.  If we tried to build the code without importing the Glibc framework we would receive the following error.

/xxx/xxx/xxx/xxx/MakeFtpFile/Sources/ArrayExtension.swift:6:19: error: use of unresolved identifier 'random'                
let index = Int(random() % self.count)
                 ^~~~~~

What this error tells us is the compiler does not know anything about the random() function.  If we look at the man page for the random() function (command: man random) we see that we need to import the stdlib.h header if we want to use this function.  If we look at the headers that are imported in the Glibc framework we will see that the Glibc framework does include the stdlib.h header.  By importing the Glibc Framework we are essentially importing all of the header files defined within it therefore we are importing the stdlib.h header which defines the random() function.  This will allows us to use the random() function within our code.

If you are new to developing applications and utilities in the Linux environment you will want to get use to using the Linux man pages to retrieve information about the system libraries and the functionality they provide.  These man pages will give you a wealth of knowledge about the functions that you are using.

Earlier we mentioned that the Glibc framework contains most of the Linux standard library so what do we do if we want include libraries that are not in the Glibc framework?  These libraries could be part of the Linux standard library that are not currently defined in the Glibc framework or libraries that are not part of the Linux standard library itself.  Lets look at how to include these libraries and also how to use the functionality they provide in our code by creating a custom module.

Creating a custom module

To create a custom module we will begin by creating a directory to put the files for the module in.  This will be the module’s main directory.  Within this directory we will need two files.  The first is an empty file named Package.swift and the second is named module.modulemap. 

Within the module.modulemap file we will define the headers we want to import and the libraries that contain the functionality defined in the headers.  The example below shows the format of the module.modulemap file

module CMyModule [system] {
    header "/usr/include/mylibheader.h"
    link "mylib"
    export *
}

The first line defines the name for our module.  This name is what we will import in our Swift files.  In this sample the module’s name is CMyModule.  The next line defines the full path to the header file that we want to import.  The third line tells the compiler that the functionality defined in the header can be found in the mylib library so we will need to link it.  The last line says to export all of the functionality.

The Swift package manager uses git and git tags to manage packages and modules therefore once we create both files we will want to create a git repository for our module.  To do this we run the following commands in the main directory for the module.

git init
git add .
git commit -m "Initial Import"
git tag 0.1.0

Before we show how to use a module, lets go ahead and create the module needed for our example.

Creating the Cifaddrs module

In this post we will be creating a utility that will list the network addresses of the device it is running on.  For this utility we will use the getifaddrs() function.  The man page for the getiffaddrs() function shows that we will need to import sys/types.h and ifaddrs.h header files.  In addition to these two headers we will also need to import the netdb.h, sys/socket.h and arpa/inet.h headers for other functions that we will be using in our code.  

Since some of the headers that we need for our project are not defined in the Glibc framework we will create our own module so we can import them.  Lets begin by creating a directory name Cifaddrs and the two files that we need:

mkdir  Cifaddrs
cd Cifaddrs
touch Package.swift
touch module.modulemap

Now we will need to define the five headers in the module.modulemap file.  To do this we would put the following code into module.modulemap file.

module Cifaddrs [system] {
    module types {
        header "/usr/include/x86_64-linux-gnu/sys/types.h"
        export *
    }

    module ifaddrs {
        header "/usr/include/ifaddrs.h"
        export *
    }

    module Socket {
        header "/usr/include/x86_64-linux-gnu/sys/socket.h"
        export *
    }
   
    module inet {
        header "/usr/include/arpa/inet.h"
        export *
    }

    module netdb {
        header "/usr/include/netdb.h"
        export *
    }
}

Now we need to create our git repository by running the following commands in the module’s main directory.

git init
git add .
git commit -m "Initial Import"
git tag 0.1.0

Since the swift package manager uses the tag for versioning, you will want to update the tag whenever you update code.  To make it easier to create the initial structure for the module, I created a shell scripted named createmodule.sh.  This script is very similar to the createproject.sh that I created in my last post to create the structure for a project.  The following is the code for the createmodule.sh script.

#!/bin/bash
#title:        createmodule.sh
#author:       Jon Hoffman
#description:  Creates the directories and files need for a Swift module
#date:         012816
#version:      1.0
#usage:        createmodule.sh {module name} {optional: dir name}

MODULENAME=""
DIRNAME=""
PACKAGEFILENAME="Package.swift"
MODULEFILENAME="module.modulemap"

#Check to make sure at least one command
#line arg is present otherwise exit script
if [ $# -le 0 ]; then
    echo "Usage: creatproject {Name for Module} {Optional directory name}"
    exit 1
fi

#Assign the value of the first command line arg to the module name
#if a second command line arg is present assign that value to the
#the directory name otheerwise use the module name
MODULENAME=$1
if [ "$1" != "" ]; then
    DIRNAME=$1
else
    DIRNAME=$MODULENAME
fi

#Check to see if the directory exists and if so display an error
#and exit
if [ -d "$DIRNAME" ]; then
    echo "Directory already exists, please choose another name"
    exit 1
fi

#Make the directory structue and create the neccessary files
mkdir -p $DIRNAME

cd $DIRNAME
touch $PACKAGEFILENAME
touch $MODULEFILENAME

echo "module $MODULENAME [system] {" >> $MODULEFILENAME
echo "" >> $MODULEFILENAME
echo "}" >> $MODULEFILENAME

git init
git add .
git commit -m "Initial Import"
git tag 0.0.1

The createmodule.sh script takes one required and one optional command line argument.  The first (required) command line argument is the name of the module and is used to create the module.modulemap file.    The second (optional) command line argument is the name for the module directory.  If the second command line argument is not present then we use the name of the module (first command line argument) for the directory name.  The following examples show how we would use the createmodule.sh script to create a module named Clib in a directory named Clib.

./createmodule.sh Clib

The previous command would create a directory named Clib.  It would also create the Package.swift and module.modulemap files.  The following code shows what the newly created module.modulemap file would look like.

import PackageDescription 

module Clib [system] {

}

We used the module name (first command line argument) to define the module name in this module.modulemap file.  You can find the code for the createmodule.sh file on my Scripts for Swift Linux development github page.

Now that we have created our module, lets see how to use it in a project.

Using the Cifaddrs module


Now that we have our module created, lets look at how we would use it in a project.  The first thing we will want to do is to create the project.  For this I will use my createproject.sh script like this:
./createproject.sh getifaddrs
This will create the directory structure and files needed to start the project with.  To tell the compiler to use our newly created module we will need to add a dependency to the Package.swift file.  We would add this dependency as shown with the following code:
import PackageDescription

let package = Package(
    name:  "getifaddrs",
    dependencies: [.Package(url: "../Cifaddrs", majorVersion: 0, minor: 1)]
)

The url defines the path to the module.  This can be the full file system path as shown in our example or an Internet path to a github repository.  We can also define multiple dependencies by separating the packages by a comma as shown here:

let package = Package(
    name:  "getifaddrs",
    dependencies: [.Package(url: "../modOne", majorVersion: 0, minor: 1),
 dependencies: [.Package(url: "../modTwo", majorVersion: 0, minor: 1)]
)

We are now ready to use the libraries defined in the module within our application.  The following code shows the main.swift file that will retrieve the list of IP addresses and print them out.

import Foundation
import Cifaddrs

// Get list of all interfaces
var ifaddr : UnsafeMutablePointer<ifaddrs> = nil
if getifaddrs(&ifaddr) == 0 {

    // Loop through all interfaces
    var ptr = ifaddr
    while (ptr != nil) {
       
        // Get address and interface name
       var addr = ptr.memory.ifa_addr.memory
        var ifname = String.fromCString(ptr.memory.ifa_name)

        // If addr is IPv4 or IPv6
        if addr.sa_family == UInt16(AF_INET)
|| addr.sa_family == UInt16(AF_INET6) {

            // Convert interface address to a string and print it
            var ad = [CChar](count: Int(NI_MAXHOST),
repeatedValue: 0)      
            if (getnameinfo(&addr, socklen_t(32), &ad,
socklen_t(ad.count), nil, socklen_t(0), NI_NUMERICHOST) == 0) {
                   
                if let address = String.fromCString(ad) {
                      print("\(ifname):  \(address)")
                }
            }
        }

        ptr = ptr.memory.ifa_next
    }
    freeifaddrs(ifaddr)
}

Notice that in the second line we import the Cifaddrs module using the import keyword.   This essentially imports all of the headers that are defined in the Cifaddrs modeul.modulemap file.
The code is commented so you can see what is going on but I do want to point out a couple of items that will help you when it comes to use Linux system libraries with swift.  The first item is how to use C pointers.  The following code show how we would use the getifaddrs() function in normal C code:

struct ifaddrs *ifap;
getifaddrs (&ifap);

In this code we create a pointer to an ifaddrs structure.   In Swift we would write this same code like this:

var ifap : UnsafeMutablePointer<ifaddrs> = nil
if getifaddrs(&ifap)

Notice in Swift we use the UnsafeMutablePointer structure to declare a pointer to an object type in memory.  In this case the object type is the ifaddrs structure. 

The other item that I want to point is how we are accessing the information within the ifaddrs structure using pointers.  In C we would access the information like this:

ifa->ifa_addr

In Swift we access the information like this:

var addr = ifa.memory.ifa_addr.memory

It took me a little bit of time to wrap my brain around this.  Having a pretty good C background this line of code seemed just wrong to me but once I really wrapped my brain around the basic concepts here it really made since.  Basically in Swift we want to avoid using pointers if we can so there isn’t a simple interface to use them.  Notice in our Swift line of code we have ifa.memory.ifa_addr.memory.  The ifa.memory part of this line gets the value in memory that the ifa pointer is pointing too.  Then the second part of this line ifa_addr.memory gets the value in memory that the ifa_addr pointer is pointing too.

If we wanted to get the pointer rather than the actual value, we would use this line instead:

var addr = ifa.memory.ifa_addr

In this line the addr variable would contain an UnsafeMutablePointer.

It does take a little bit of work to include the Linux system libraries in our Swift code but overall I think Apple did a great job making it as easy as possible while avoid a lot of the complexity of C.  I will hopefully writing more posts on how to use the Swift port for Linux.



2 comments:

  1. Nice tutorial, but i'm getting unresolved identifier for all the constants: AF_INET, AF_INET6, NI_MAXHOST, NI_NUMERICHOST - any ideas why it can't find or doesn't like c constants?

    ReplyDelete
  2. Thanks for providing this informative information you may also refer.
    http://www.s4techno.com/blog/2016/07/12/everything-about-ftp/

    ReplyDelete