Erlang provides mechanism to communicate between Erlang and other programs written by C/C++ or other. This entry introduce how to create simple linked-in driver. Create shared library and Erlang interface to use Erlang's linked-in driver.
Goal: To create HAVAL bindings for Erlang.
HAVAL is fast cryptographic hash algorithm is invented by Yuliang Zheng, Josef Pieprzyk, and Jennifer Seberry in 1992. This library is pretty simple, so this is good choice for this tutorial.
Step 0: Files
config.h (Generated by configure script in HAVAL package)
haval.h
haval.c
haval.erl
haval_drv.cc
Makefile
haval.c and haval.h are included
haval-1.1.tar.gz (C source code).
You can checkout these sample sources with Git. see
haval_erlang.
% git clone git://github.com/rakuto/erlang_haval.git erlang_haval
Step 1: Create Erlang's interfaceErlang provides port mechanism in order to communicate between Erlang and other loaded shared object. See also
Writing an Erlang Port using OTP Principles
-module(haval).
-author('Rakuto Furutani <xri://=rakuto>').
-define(DRV_INFO, 1).
-define(DRV_HAVAL_STRING, 2).
-define(DRV_HAVAL_FILE, 3).
-export([start/0, stop/0]).
-export([info/0, haval_string/1, haval_file/1]).
-ifdef(debug).
-export([test/0, test/1]).
-endif.
start() ->
start("haval_drv"),
ok.
start(SharedLib) ->
case erl_ddll:load_driver(".", SharedLib) of
ok -> ok;
{error, already_loaded} -> ok;
_ -> exit({error, could_not_load_driver})
end,
register_lib(SharedLib).
stop() ->
[{port, Port}| _] = ets:lookup(haval_table, port),
Port ! {close, self()},
ok.
info() -> ok.
haval_string(Str) ->
binary_to_term(control(?DRV_HAVAL_STRING, Str)).
haval_file(FileName) ->
binary_to_term(control(?DRV_HAVAL_FILE, FileName)).
-ifdef(debug).
test() ->
haval:start(),
Str = "I love Erlang.",
FileName = "haval.erl",
Hash1 = haval:haval_string(Str),
Hash2 = haval:haval_file(FileName),
io:format("HAVAL(~p) = ~p ~n", [Str, Hash1]),
io:format("HAVAL(~p) = ~p ~n", [FileName, Hash2]),
haval:stop(),
halt().
test([Str|_]) ->
haval:start(),
Hash = haval:haval_string(Str),
io:format("HAVAL(~p) = ~p ~n", [Str, Hash]),
haval:stop(),
halt().
-endif.
register_lib(SharedLib) ->
Port = open_port({spawn, SharedLib}, []),
Tab = ets:new(haval_table, [set, protected, named_table]),
ets:insert(Tab, {port, Port}).
control(Cmd, Data) ->
[{port, Port}| _] = ets:lookup(haval_table, port),
erlang:port_control(Port, Cmd, Data).
Step 2: Create C/C++ shared libraryShared library writted by C++ is loaded by Erlang interface when call erl_ddll::load_driver/2. This driver uses
ei library in order to manipulate Erlang binary term.
#include <stdio.h>
#include <string.h>
#include <stdarg.h>
#include <new>
#include "ei.h"
#include "erl_driver.h"
#include "erl_interface.h"
extern "C" {
#define PASS 3
#define NUMBER_OF_BLOCKS 5000
#define FPTLEN 256
#ifndef LITTLE_ENDIAN
#define LITTLE_ENDIAN 1
#endif
#include "haval.h"
}
#define DRV_INFO 1
#define DRV_HAVAL_STRING 2
#define DRV_HAVAL_FILE 3
static ErlDrvData start_haval_driver(ErlDrvPort port, char *command);
static void stop_haval_driver(ErlDrvData drv_data);
static int control(ErlDrvData drv_data, unsigned int command, char *buf,
int len, char **rbuf, int rlen);
static void haval_to_hex(unsigned char*, char*);
static ErlDrvBinary* ei_x_to_new_binary(const ei_x_buff*);
static ErlDrvEntry haval_driver_entry = {
NULL,
start_haval_driver,
stop_haval_driver,
NULL,
NULL,
NULL,
"haval_drv",
NULL,
NULL,
control,
NULL,
NULL
};
typedef struct _drv_data {
ErlDrvPort port;
} drv_data_t;
static ErlDrvData start_haval_driver(ErlDrvPort port, char *command)
{
drv_data_t *data;
data = (drv_data_t*) driver_alloc(sizeof(drv_data_t));
data->port = port;
set_port_control_flags(port, PORT_CONTROL_FLAG_BINARY);
return (ErlDrvData) data;
}
static void stop_haval_driver(ErlDrvData drv_data)
{
driver_free((char*) drv_data);
}
static int control(ErlDrvData drv_data, unsigned int command, char *buf, int len, char **rbuf, int rlen)
{
int ret = -1;
char hex[(FPTLEN >> 3) << 1];
unsigned char fingerprint[FPTLEN >> 3];
char *arg1;
ei_x_buff x_buff;
try {
arg1 = new char[len + 1];
strncpy(arg1, buf, len);
strcat(arg1, "�0");
ei_x_new_with_version(&x_buff);
switch(command) {
case DRV_INFO:
ei_x_encode_string(&x_buff, "info");
ret = sizeof("info") * sizeof(char);
break;
case DRV_HAVAL_STRING:
haval_string(arg1, fingerprint);
haval_to_hex(&fingerprint[0], &hex[0]);
ret = sizeof(hex);
ei_x_encode_string(&x_buff, hex);
break;
case DRV_HAVAL_FILE:
if(!haval_file(arg1, fingerprint)) {
haval_to_hex(&fingerprint[0], &hex[0]);
ret = sizeof(hex);
ei_x_encode_string(&x_buff, hex);
} else {
erl_err_sys("haval_file");
}
break;
}
if(ret > 0) *rbuf = reinterpret_cast<char*>(ei_x_to_new_binary(&x_buff));
ei_x_free(&x_buff);
} catch(std::bad_alloc) {
erl_err_sys("can not allocate memory");
}
return ret;
}
static void haval_to_hex(unsigned char *fingerprint, char *hex)
{
for(int i=0; i < FPTLEN >> 3; ++i) {
sprintf(&hex[i << 1], "%02X", fingerprint[i]);
}
}
extern "C" DRIVER_INIT(haval_drv)
{
return &haval_driver_entry;
}
static ErlDrvBinary* ei_x_to_new_binary(const ei_x_buff *x_buff)
{
ErlDrvBinary *bin = driver_alloc_binary(x_buff->index);
if(bin != NULL) {
memcpy(bin->orig_bytes, x_buff->buff, x_buff->index);
}
return bin;
}
Step 3: Compile all sourcesCreate Makefile for build.
FYI: If you want to build linked-in driveron Mac OS, see
Compile Erlang linked-in driver on Mac OSX (Darwin)
.PHONY: clean
.SUFFIXES: .o .c .cc .erl .beam
OS= ${shell uname}
CC=gcc
CXX=g++
CXXFLAGS=-Wall -g
ERL_INCLUDE = -I/usr/local/lib/erlang/usr/include
ERL_LIBS = -L/usr/local/lib/erlang/usr/lib \
-lerts
EI_INCLUDE = -I/usr/local/lib/erlang/lib/erl_interface-3.5.6/include
EI_LIBS = -L/usr/local/lib/erlang/lib/erl_interface-3.5.6/lib \
-lei \
-lerl_interface
TARGET_LIB = haval_drv.so
ifeq ($(OS), Darwin)
EXTRA_OPTIONS = -fno-common -bundle -undefined suppress -flat_namespace
endif
ALL: $(TARGET_LIB) haval.beam
.erl.beam:
erlc -W -Ddebug $<
.c.o:
$(CC) $(CFLAGS) -c $<
.cc.o:
$(CXX) $(CXXFLAGS) $(ERL_INCLUDE) $(EI_INCLUDE) -c $<
haval_drv.so: haval.o haval_drv.o
$(CXX) -o $@ $^ $(ERL_LIBS) $(EI_LIBS) $(EXTRA_OPTIONS) -fpic -O2
clean:
rm -f *.beam *.o *.so
Step 4: Run and test
% erl -noshell -run haval test
HAVAL("I love Erlang.") = "3AECEDF5133ED147C704A25C2CCA9A994FBB984FBCB22C1A523F31963E418498"
HAVAL("haval.erl") = "E006098555DFF788E73C7A263615DF60CC82396B1BCE7AEB5FB7050C376F2B03"