tinch++: Interfacing Erlang from C++

February 2014

Introduction

tinch++ is a platform independent, open-source library for building distributed Erlang nodes in C++. Distributed Erlang nodes provide a high-level model for integrating other languages with Erlang programs. With tinch++, your C++ code will be able to communicate with Erlang processes by means of message passing. To the Erlang processes, your C++ node will look and behave like any Erlang node.

Besides integrating C++ and Erlang programs, tinch++ may be used as a thread-safe bidirectional queue between multiple threads in an application. For an example, check out the example program thread_safe_queue.cpp included in the tinch++ release.

Motivation

tinch++ grew out of my experience with integrating Java and Erlang programs through Jinterface. Jinterface is a mature and stable package, but enforces a low-level programming model; basically a received term has to be deconstructed by means of deep type-switches. tinch++ is my attempt to bring the power of pattern matching to C++ while at the same time provide a type-safe, high-level API in modern C++. While I do believe tinch++ is on the right track, I'm not quite there yet.

My second source of motivation was to reach a better understanding of Erlang's distribution mechanism. And at that point I think I succeeded; tinch++ was fun to write and I hope you'll find this early release useful.

Download

tinch++ is available as a GitHub repository .

Version 0.3.1 introduces support for boost versions up to 1.46.1. tinch++ is still backwards compatible to boost 1.42.1 but I do recommend upgrading to the latest boost version.

The previous version 0.3 introduced the following features:

tinch++ version 0.3 introduces the following breaking API changes in order to avoid naming conflicts with boost and POSIX:

Please note that just like in previous versions, the new types are located in the tinch_pp::erl namespace

License

tinch++ is released under a BSD-style license . That means, you're basically free to do whatever you want with it as long as the copyright notice is kept.

Building tinch++

tinch++ uses CMake, a cross-platform build system, to generate native makefiles for your system. The generated makefiles are used for building and installing tinch++ on your target platform. The CMake documentation is available here .

A tinch++ installation consists of API include files and one static library. The include files are located under <path_to_installation>/tinch_pp-0.2.1/tinch_pp. The lib is located under <path_to_installation>/tinch_pp-0.2.1/lib.

To compile and link tinch++ applications, you'll need boost 1.42 . While most of the boost libraries are header only, some requires a library. tinch++ depends on the following link-time boost libraries:

Tutorials and Examples

I'm planning an in-depth tutorial on distributed Erlang nodes. The tutorial will, hopefully, be available this summer (2010). Until then, check out the example programs included in the tinch++ download and the getting started guide below.

API documentation

The tinch++ download includes a target for generating HTML API documentation based on doxygen .

Getting Started

When establishing connections between tinch++ nodes and Erlang, the Erlang Port Mapper Daemon (epmd) has to run. epmd is included in your Erlang installation. Start epmd by simply typing epmd (or epmd.exe on Windows).

All examples below assume the proper include files (see the API documentation). All tinch++ code resides in the tinch_pp namespace. External terms reside in tinch_pp::erl . For clarity, these namespaces have been omitted below.

Nodes, mailboxes and pattern matching

The first step when implementing a distributed node in C++ is to instantiate the actual node. In the current version, a node requires two parameters:

  1. The name of the node. The name has to be on a form supported by Erlang (a name can be either on the short or long form). Check out the Erlang documentation for a detailed description.
  2. A distribution cookie. Distributed nodes can only communicate with nodes using the same cookie value.

In this example, we're instantiating a node named adam running on the host zarathustra. As cookie, we're using the string abcdef.

        
	const std::string own_node_name("adam@zarathustra"); 
	const std::string cookie("abcdef");

        node_ptr my_node = node::create(own_node_name,  cookie);
      

Once we have a node, tinch++ allows us to instantiate mailboxes. A mailbox is the distributed equivalence to an Erlang process and every mailbox is associated with its own process ID. The code below creates an unnamed mailbox:

        
	mailbox_ptr mbox = my_node->create_mailbox();
      

Messages from other nodes are received through the mailbox and, correspondingly, outgoing messages are sent from the mailbox. Let's try it out; suppose we have an Erlang gen_server , reflect_msg.erl , running on the node erlnode@zarathustra . Further, assume that the gen_server implements the following toy-functionality:

        
	handle_info({echo, Sender, Msg}, State) -> 
	    Sender ! Msg, 
	    {noreply, State}.
      

Our mailbox allows us to communicate with that Erlang process. Let's start by sending a message to it:

        
	const std::string remote_node_name("erlnode@zarathustra"); 
	const std::string to_name("reflect_msg");

	mbox->send(to_name, remote_node_name, make_e_tuple(atom("echo"), 
						           pid(mbox->self()), 
                                                           atom("hello")));
      

The code above assembles a tuple corresponding to {echo, OwnPid, hello} and sends it to the registered process reflect_msg on the remote node. The reflect_msg server will, as its name indicates, reflect the message for us to receive:

        
	const matchable_ptr reply = mbox->receive();
      

The receive function will block until a message is available (tinch++ also allows you to specify a time-out - see the API for details). The received message is returned as a matchable , which allows us to pattern match on the message. In our example, the pattern is as simple as it gets - a single atom. The patterns are typically built-up inline in the match-clause:

        
	if(reply->match(atom("hello"))) 
	    std::cout << "Matched atom(hello)" << std::endl; 
	else 
	    std::cerr << "No match - unexpected message!" << std::endl;
      

The code above will succeed if an atom matching the value hello is received. In case we didn't knew what atom-value to receive, we could assign the matched value to a variable:

        
	std::string value;
	reply->match(atom(&value));
      

tinch++ also includes a mechanism for wildcards in patterns. The code below will match anything received and always succeeds:

        
	reply->match(any());
      

The part of the message matched by a wildcard may be saved for later matching:

        
	matchable_ptr wildcard_part;
	reply->match(any(&wildcard_part));
	...
	if(wildcard_part->match(make_e_tuple(atom("start"), 
					     make_e_tuple(atom("nested"), 
                                                          int_(42)))))
	   ...
      

The match above will succeed if the wildcard refers to an Erlang tuple on the following form: {start, {nested, 42}} .

Calling an Erlang function

Let's return to our previous Erlang program. Suppose the program exports the following API:

        
	echo(Msg) -> 
    		gen_server:call(?SERVER, {echo, Msg}).
      

Upon a successful call, the echo-function returns a tuple on the format {ok, Msg} . tinch++ allows us to call native Erlang functions through an RPC (remote procedure call) class. Instantiate the rpc class with a reference to our existing node:

        
	rpc rpc_invoker(my_node);
      

The rpc class follows the conventions in Erlang's RPC module, where all arguments have to be sent in a list. In the current version of tinch++, the terms have to be heap allocated in order to be stored in the list. The included utility make_* functions hide much of it. Using list_of from boost::assign , we can build-up the list and invoke our RPC like this:

        
	const std::string remote_node_name = "erlnode@zarathustra";
	const std::list<erl::object_ptr> msg_to_echo = list_of(make_tuple_ptr(make_atom("hello"), make_int(42)));

	matchable_ptr reply = rpc_invoker.blocking_rpc(remote_node_name, 
                                                       module_and_function_type("reflect_msg", "echo"), 
                                                       make_list(msg_to_echo));
      

The result of the RPC call is delivered as a matchable , allowing pattern matching as illustrated earlier.

Process Links

Process links allow Erlang processes to monitor each other. In case a process terminates (e.g. normal shutdown or a crash), its linked processes get sent an exit signal. Links in Erlang are bidirectional and it doesn't matter if process A links to process B or the other way around; the result will be the same. tinch++ implements that mechanism, allowing tinch++ mailboxes to be linked to remote Erlang processes or other mailboxes. To create a link in tinch++, just invoke mailbox::link with the remote process ID as argument:

        
	mbox->link(remote_pid);
      

If the remote process terminates, the next a receive on this mailbox will throw a tinch_pp::link_broken exception. Similarly, if this mailbox is closed, the linked process will receive an Erlang exit signal. You remove an existing link by invoking mailbox::unlink :

        
	mbox->unlink(remote_pid);
      

After an unlink, no special relationship with the remote process exists.

Limitations

Version 0.3 of tinch++ comes with the following known limitations:

Planned Releases

I'm currently not performing any active development on tinch++. If/when I return to code in C++ I'd probably pick it up again. My intent then is to follow the roadmap below:

Version 1.0

Version 1.0 will probably be based on the current version, tinch++ 0.3.1. I do plan to add the following additional features:

C++1x and Future versions

I plan to migrate tinch++ to the planned new C++ standard C++1x (where 'x' hopefully stands for '14'...). I do expect to keep the API stable. My focus will be on utilizing the improvements to C++ in the following areas:

Credits

David Ã…berg provided the CMake build system. Further, David was a source of inspiration during the design and provided several useful ideas. Thank you, David!

tinch++ builds on a lot of open-source. The excellent boost libraries are used for all non-portable tasks such as networking, threading, and binary parsing. tinch++ also uses parts of the loki library for its exception-safety mechanisms. Finally, the MD5 implementation comes from Peter L. Deutsch.