Ferret Lisp FFI Notes

This posts serves as documentation on how to interact with third party C,C++ libraries when using Ferret Lisp, a free software Clojure implementation, it compiles a restricted subset of the Clojure language to self contained ISO C++11 which allows for the use of Clojure in real time embedded control systems. It has been verified to run on architectures ranging from microcontrollers with as little as 2KB of RAM to general purpose computers running Linux/Mac OS X/Windows.

Following Clojure's design philosophy of embracing the host. Ferret provides multiple ways of interacting with the host language. Currently there are 4 methods that can be used to pass data back and forth between Ferret and C,C++,

Value

A value Ferret object can used to capture any C++ type T. This is useful when interacting with modern C++ libraries where instead of using pointers they use smart pointers. One example for these types of libraries is the OpenCV computer vision library. Following is the Hello World of OpenCV, a simple app to open the first attach camera, grab a frame from it and show that frame on a GUI element.

#include "opencv2/opencv.hpp"

using namespace cv;

int main(int argc, char** argv){

  VideoCapture cap;

  if(!cap.open(0))
    return 0;

  for(;;){
    Mat frame;
    cap >> frame;

    imshow("Cam 0", frame);
    waitKey(1);
  }

  return 0;
}

For converting the above program we need to create 4 functions,

  • A function to return a handle to a VideoCapture object.
  • A function to grab a frame from the capture.
  • A function to show the grabbed frame on a OpenCV window.
  • A function to tell OpenCV to update GUI.
(defnative video-capture [n]
  (on "defined FERRET_STD_LIB"
      ("opencv2/opencv.hpp")
      "using namespace cv;
       VideoCapture cap;

       cap.open(number::to<number_t>(n));

       if (!cap.isOpened())
         return nil();

       __result = obj<value<VideoCapture>>(cap);"))

Converting Ferret types to C++ types are handled by ::to= functions. Every built in type has a static ::to= function to convert the type to its native counter part. Above function takes the camera id as a parameter and can be used in the following way,

(def capture (video-capture 0))

In order to convert camera id to a native number one can use the number objects ::to= method.

int cam_id = number::to<int>(n);

Once we have the camera id we can open the attached camera, check if it is correctly initialized using isOpened() method, if not return nil otherwise we use a value Ferret object to return the OpenCV VideoCapture object back to Ferret side. A value Ferret object can hold any type T.

var dev = obj<value<VideoCapture>>(0);

Any arguments passed to value constructor will be forwarded to the type T's constructor. A value object acts just like any other Ferret object type, and its lifetime is handled by the active GC. (Ferret supports various schemes for garbage collection.)

(defn query-capture [c]
  "using namespace cv;
   Mat frame;
   value<VideoCapture>::to_value(c) >> frame;
   __result = obj<value<Mat>>(frame);")

Type T contined within a value type can be accessed using the ::to_value static function of the value class.

VideoCapture dev = value<VideoCapture>::to_value(c);

A value object is used to return the latest frame from the camera back to Ferret.

(def frame (query-capture capture))

Two other function needs to be wrapped to convert the above C++ program in to a Ferret program.

(defnative imshow [n f]
  (on "defined FERRET_STD_LIB"
      ("opencv2/highgui/highgui.hpp")
      "using namespace cv;
       imshow(string::to<std::string>(n), value<Mat>::to_value(f));"))

(defn wait-key [n]
  "__result = obj<number>(cv::waitKey(number::to<number_t>(n)));")

Once all the required API functions are wrapped above C++ program can be represented using the following Ferret program.

(def capture (video-capture 0))

(forever
 (->> capture
      query-capture
      (imshow "Cam 0"))
 (wait-key 1))

Extending Ferret Objects

For tighter integration with Ferret, users can define their own types. While a value object is just a container holding a type T user defined objects can implement built in interfaces making them seekable and/or callable. i.e, instead of using the built in D-List based maps, one can use any map implementation from any C,C++ library and it will behave just like the built in type. In order to create a user defined object you have to define a class that extends object and implement some methods required by Ferret.(stream_console, equals, type). When using this method garbage collection is also automatically handled by the active GC scheme. No manual allocation deallocation is necessary other than what is required by the native library.

Lets assume you are doing embedded work using an Arduino, want to interface with a humidity and temperature sensor (HIH6130) and you want your sensor object to be callable. Giving you a Ferret API that can be used like the following,

(def sensor (new-hih 0x27))

(let [[temprature humidity] (sensor)]
  (println temprature humidity))

First we need to define a class that contains the implementation for the object. You can make any user defined object callable by implementing the lambda_i and overriding invoke method.

#include <HIH61XX.h>

class hih : public lambda_i {
  HIH61XX hih;

public:

  type_t type() const final { return type_id<hih>; }

  bool equals(var const & o) const { return (this == o.get()); }

#if !defined(FERRET_DISABLE_STD_OUT)
  void stream_console() const {
    runtime::print("HIH Temrature Sensor");
  }
#endif

  explicit hih() : hih(0x27) { }

  ~hih(){ }

  var invoke(var const & args) const {
    char status;
    double T,P,p0,a;

    status = pressure.startTemperature();
    if (status != 0){
      delay(status);
      status = pressure.getTemperature(T);
      if (status != 0){
        status = pressure.startPressure(3);
        if (status != 0){
          delay(status);
          status = pressure.getPressure(P,T);
          if (status != 0){
            p0 = pressure.sealevel(P, default_altitude);
            __result = runtime::list(obj<number>(T), obj<number>(P));
          }
        }
      }
    }

    return nil();
  }
};

And somewhere in your program tell Ferret where the implementation for the object is,

(defobject hih "/path/to/hih_implementation.h")

(defn new-hih [id]
  "__result = obj<hih>(number::to<int>(id));")

Pointer

For interacting with legacy C++ libraries or C libraries Ferret provides a pointer type. It holds a pointer to a type T. Unlike a value type or user defined object, user is responsible for garbage collection.

(defn new-int [i]
  "__result = obj<pointer>(new int(number::to<int>(i)));")

(defn print-int [i]
  "std::cout << pointer::to_pointer<int>(i) << std::endl;")

Macros for C++ Code Generation

Once in a while you may come across a library that does not play well with any of the above methods and custom C++ code should be generated for each invocation. i.e You want to register a Ferret function as a interrupt service routine on a Raspberry PI via WiringPi library. wiringPiISR function expects a C style callback function, a Ferret function can not be passed as is. What we can do is for every Ferret function we want to register as callback, define a native function that in turn invokes the Ferret function.

(defmacro attach-isr [pin callback]
  (let [fn (gensym)]
    `(~'do
      (~'def ~fn ~callback)
      (~'cxx ~(str "wiringPiISR (" pin ", INT_EDGE_FALLING, [](){ run(" fn ");})")))))

wiringPiISR expects a function with the signature void (*function)(void)) so C++ lambdas can not capture the Ferret function variable passed. What the above macro does is define a unique variable for the function passed and using a non capturing lambda execute the function pointed by the unique variable.

(attach-isr 0 #(println "Interrupted!")) ;; attach interrupt in WiringPI pin 0