Small library that interfaces C-code with Erlang/Elixir using Ports.
Clone or download
Fetching latest commit…
Cannot retrieve the latest commit at this time.
Permalink
Type Name Latest commit message Commit time
Failed to load latest commit information.
c_src
config
lib
test
.gitignore
LICENSE
README.md
mix.exs
mix.lock

README.md

Cure

A small library that interfaces Elixir-code with C/C++ programs using Erlang/Elixir Ports. Provides Mix tasks to kickstart the development process.

Example

The following example loads a program called "program" which is located in the ./c_src/ directory of your project.

# Open the Port to the C/C++ program:
{:ok, server} = Cure.load "./c_src/program"

# Depending on the kind of communication you need, there are several modes for
# sending and receiving messages:

# Option 1 (once, asynchronous):
# Useful for messages that generate a single response

# without callback function
server |> Cure.send_data "any binary can be transmitted to the C/C++ side!", :once
receive do
  {:cure_data, response} ->
    # Process response here..
end

# with callback function
server |> Cure.send_data <<1, 2, 3>>, :once, fn(response) ->
  # Process response here..
end


# Option 2 (noreply, asynchronous):
# Useful if you don't need a response from the C/C++ side or if you are
# already subscribed to the Cure process.
server |> Cure.send_data "more data..", :noreply


# Option 3 (permanent, asynchronous)
# Useful when you want to keep processing responses after you send an initial message
# (NOTE: After this function is used once, you can use :noreply and
# still keep getting responses)

# without callback function
server |> Cure.send_data "abcdef", :permanent
receive do
  {:cure_data, msg} ->
    # Process response here...
end

# with callback function
server |> Cure.send_data "...", :permanent, fn(response) ->
  # Process response here..
end


# Option 4 (synchronous):
# (a timeout can also be added as last argument)
result1 = server |> Cure.send_data "testdata", :sync
server |> Cure.send_data <<1,2,3>>, :sync, fn(response) ->
    IO.inspect response
end


# Close the program:
server |> Cure.stop # stops the supervised server

By default, Cure starts a supervisor which supervises all of its children (a child in this case is a GenServer that communicates with a C/C++ program). A child is added to the supervision tree with Cure.load(program_name). If you don't want this behaviour, you can also directly start a server with one of the following lines of code:

# Option 1:
{:ok, server} = Cure.Server.start_link "program_name"

# Option 2:
{:ok, server} = Cure.Server.start "program_name"

# Stopping the server:
:ok = Cure.Server.stop(server)

A process can also (un)subscribe to responses coming from the C/C++ side using the following functions:

# Option 1: receives responses as {:cure_data, ...}
server |> Cure.subscribe
server |> Cure.unsubscribe

# Option 2: passes every response to a function that processes it
fun = fn(response) -> IO.inspect response end
server |> Cure.subscribe fun
server |> Cure.unsubscribe fun

Examples that use Cure can be found at the following links:

Getting started

Add the Cure dependency to your mix.exs file:

def deps do
	[{:cure, "~> 0.4.0"}]
end

If you're using Cure together with another Elixir or Erlang OTP application, you can add Cure to the list of applications that have to be started when your app also starts. This will in turn automatically start the top Cure supervisor that can supervise Cure.Server processes.

def application do
    [mod: {YourApp, []},
     applications: [:cure, ...]]
end

For a Phoenix application (also an OTP application!) this could look as follows:

def application do
  [mod: {YourApp, []},
   applications: [:phoenix, :phoenix_html, :cowboy, :logger, :gettext,
                  :phoenix_ecto, :postgrex, :cure, ...]]
end

Fetch & compile dependencies

mix deps.get
mix deps.compile

Start developing in C/C++

  • Generate the necessary base files to communicate between C/C++ and Elixir:
mix cure.bootstrap
  • Compile your C/C++ code (needed after each modification of your code)
mix compile.cure
  • If you have dependencies that also use Cure:
mix compile.cure.deps

Another option is to add the last 2 tasks to your mix.exs to compile all code automatically when you type mix.compile:

def project do
  [...,
    compilers: Mix.compilers ++ [:cure, :"cure.deps"],
    ...]
end

C/C++ code

C/C++ code is currently placed in the c_src directory of your application. It can interface with Elixir-code based on 2 important functions:

  1. read_msg to read data coming from Elixir;
  2. send_msg to send data to Elixir.
  • These helper-functions interface with Elixir by sending/receiving data via stdin or stdout. (Right now it's only possible to send messages up to 64KiB.)
  • To be able to use the send and receive functions, you need to add the following include:
#include <elixir_comm.h>
  • The code for these functions is mostly based on the following link.

Makefile

The command "mix cure.bootstrap" generates a basic Makefile (in ./c_src/) that handles the compilation of all your C-code. This file is only generated if it doesn't exist yet so it's safe to add modifications for when your C-files need extra includes to compile properly.

The command "mix cure.make" uses the Makefile to compile all your C/C++ code.

More information regarding Ports