Erl-interface

Robert Raschke r.raschke@REDACTED
Wed Dec 11 18:13:08 CET 2002


Hi,

here is some of my work in progress trying to wrap up a larger library
(reduced to math.h's sin() and pow() for demonstration purposes :-)
that I would like to access from Erlang. Part of what I want to
achieve is to have the library accessible from anywhere in a Erlang
network of nodes. So, I wrote some support code to enable me to start
the library as a node itself. Both starting it on its own as a node
registering with epmd and starting it with a connection to an already
existing node is supported.

I currently only deal with simple messages, once I've read up on
linking and such, I might want to extend things to deal with that as
well.

Ages ago I tried playing with IG, but never really got off the ground
because the data structures I am dealing with are very complex. I have
to write my own translation routines anyway, so just writing the whole
thing from scratch, bypassing any automatic code generation, was the
way forward for me. (I did try to understand Corba at some point, but
my brain wasn't big enough, so I gave up.)

I have tried this running in a variety of configurations under Windows
NT and Solaris (the C node on either and the Erlang node on either),
and everything looks very transparent.

I hope this may prove to be interesting to people. And, of course,
feedback will be gratefully received.

Oh, compiling I do roughly like this:
NT:
cl -c -I%ERL_HOME%/erl_interface-3.3.2/include -D_WINDOWS simple.c
link /NODEFAULTLIB:libc.lib /NODEFAULTLIB:msvcrtd.lib /SUBSYSTEM:console /LIBPATH:c:/progra~1/micros~3/vc98/lib simple.obj /LIBPATH:%ERL_HOME%/erl_interface-3.3.2/lib liberl_interface.lib libei.lib WSock32.lib

Solaris:
cc -c -I$ERL_HOME/erl_interface-3.3.2/include simple.c
cc -o simple simple.o -L$ERL_HOME/erl_interface-3.3.2/lib -lerl_interface -lei -lsocket -lnsl -lresolv

And running as a standalone node (randomly chosen port no) (epmd needs
to be up for this):
; simple nodeid -server 9898

Or connecting to existing node:
; simple nodeid -client othernode@REDACTED

The Erlang side I start like this:
; erl -name othernode -setcookie secretcookie

Obviously, you'll need to adapt the Erlang code to reflect your node
and machine names.

Next on my list is to make the Erlang side be a gen_server proper.

Robby
-------------- next part --------------
-module(simple).
-export([sin/1, pow/2]).

sin(X) when number(X) ->
	call_simple(e_sin, {X}).

pow(X, Y)  when number(X), number(Y)->
	call_simple(e_pow, {X, Y}).


call_simple(Fn, Params) ->
	{any, 'nodeid@REDACTED'} ! {call, self(), Fn, Params},
	receive
		{nodeid, ok, Result} ->
			{ok, Result};
		{nodeid, error, Reason, Details} ->
			{error, Reason, Details}
	end.

-------------- next part --------------
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#if defined(_WINDOWS)
#	include <winsock2.h>
#else
#	include <unistd.h>
#	include <sys/socket.h>
#	include <netdb.h>
#	include <netinet/in.h>
#	include <arpa/inet.h>
#endif

#ifndef MAXHOSTNAMELEN
#	define MAXHOSTNAMELEN 256	/* Sensible value */
#endif

#include "erl_interface.h"
#include "ei.h"

#define BUFSIZE 1000

static void
usage(void)
{
	fprintf(stderr, "simple <nodeid> {-client <node>|-server <port>}\n");
	exit(1);
}


/* wrapped functions */

#include <math.h>

static void e_sin(ETERM *from, ETERM *params);
static void e_pow(ETERM *from, ETERM *params);

static struct {
	const char *name;
	void (*function)(ETERM *from, ETERM *params);
} function_list[] = {
	{ "e_sin", e_sin },
	{ "e_pow", e_pow },
};
#define N_FUNCS (sizeof function_list / sizeof function_list[0])


/* some support functions */
#if defined(_WINDOWS)
	static int initWinsock();
#endif
static int my_listen(unsigned short port);
static void main_message_loop();
static void handle_regular_message(ETERM *msg);


/* GLOBAL STATE! */
static char *nodeid;	/* used in error messages */
static int fd;			/* socket to Erlang node */


int
main(int argc, char **argv)
{
	unsigned short port = 0;
	char *node = NULL;
	char *fullnodeid;		/* <nodeid>@<fully qualified hostname> */

	char hostname[MAXHOSTNAMELEN]; /* name of machine we're running on */
	struct hostent *host;	/* host info on machine */
	struct in_addr *addr;	/* 32-bit IP number of host */
	int listen_socket;
	ErlConnect conn;		/* Connection data */

	if (argc != 4)
		usage();

	nodeid = (char *) malloc(strlen(argv[1]) + 1);
	strcpy(nodeid, argv[1]);
	if (strncmp(argv[2], "-client", 7) == 0)
		node = argv[3];
	else if (strncmp(argv[2], "-server", 7) == 0)
		port = (unsigned short) atoi(argv[3]);
	else
		usage();

#if defined(_WINDOWS)
	if (! initWinsock())
		return -1;
#endif

	erl_init(NULL, 0);

	if (gethostname(hostname, MAXHOSTNAMELEN) != 0)
		erl_err_sys("Error: cannot determine host name");
	if ((host = gethostbyname(hostname)) == NULL)
		erl_err_sys("Error: cannot retrieve host information");

	/* The name in the hostent structure is guaranteed to be the
	fully qualified name. */
	if (strlen(host->h_name) >= MAXHOSTNAMELEN)
		erl_err_quit("Error: cannot deal with overlong fully qualified host name.");
	sprintf(hostname, "%s", host->h_name);

	if ((fullnodeid = (char *) malloc(strlen(nodeid) + 1 + strlen(hostname) + 1)) == NULL)
		erl_err_quit("Error: out of memory.");
	sprintf(fullnodeid, "%s@%s", nodeid, hostname);
	addr = (struct in_addr *) host->h_addr;

	if (! erl_connect_xinit(hostname, nodeid, fullnodeid, addr, "secretcookie", 0))
		erl_err_sys("Error: initialisation failed");

	if (port != 0) {
		if ((listen_socket = my_listen(port)) <= 0)
			erl_err_sys("Error: cannot open socket");
		if (erl_publish(port) == -1)
			erl_err_sys("Error: cannot register with local name server");
		if ((fd = erl_accept(listen_socket, &conn)) == ERL_ERROR)
			erl_err_sys("Error: cannot accept connection");
		erl_err_msg("Connected to %s .", conn.nodename);
	} else if (node != NULL) {
		if ((fd = erl_connect(node)) < 0)
			erl_err_sys("Error: cannot connect to node");
		erl_err_msg("Connected to %s .", node);
	} else {
		erl_err_quit("Error: bad connection information.");
	}

	main_message_loop();

	erl_err_msg("Disconnecting.");

	if (erl_close_connection(fd) != 0)
		erl_err_sys("Error: failed to close Erlang connection");

#if defined(_WINDOWS)
	closesocket(listen_socket);
#else
	close(listen_socket);
#endif

	return 0;
}

#if defined(_WINDOWS)
static int
initWinsock()
{
	WORD wVersionRequested;
	WSADATA wsaData;
	int err;

	wVersionRequested = MAKEWORD(2, 0);

	err = WSAStartup(wVersionRequested, &wsaData);
	if (err != 0) {
		/* Tell the user that we couldn't find a usable WinSock DLL. */
		return 0;
	}

	/* Confirm that the WinSock DLL supports 2.0. Note that if the
	DLL supports versions greater than 2.0 in addition to 2.0, it
	will still return 2.0 in wVersion since that is the version we
	requested. */
	if (LOBYTE(wsaData.wVersion) != 2 || HIBYTE(wsaData.wVersion) != 0) {
		/* Tell the user that we couldn't find a usable WinSock DLL. */
		WSACleanup();
		return 0;
	}

	/* The WinSock DLL is acceptable. Proceed. */
	return 1;
}
#endif

static int
my_listen(unsigned short port)
{
	int listen_socket;
	struct sockaddr_in addr;
	int on = 1;

	if ((listen_socket = socket(AF_INET, SOCK_STREAM, 0)) < 0)
		return -1;

	setsockopt(listen_socket, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on));

	memset((void *) &addr, 0, (size_t) sizeof(addr));
	addr.sin_family = AF_INET;
	addr.sin_port = htons(port);
	addr.sin_addr.s_addr = htonl(INADDR_ANY);

	if (bind(listen_socket, (struct sockaddr *) &addr, sizeof(addr)) != 0)
		return -1;
	if (listen(listen_socket, 5) != 0)
		return -1;

	return listen_socket;
}


static void
main_message_loop()
{
	int got;						/* Result of receive */
	unsigned char buf[BUFSIZE];	/* Buffer for incoming message */
	ErlMessage emsg;				/* Incoming message */

	while (1) {
		got = erl_receive_msg(fd, buf, BUFSIZE, &emsg);
		if (got == ERL_TICK) {
			/* ignore */
		} else if (got == ERL_ERROR) {
			break; /* leave the loop */
		} else {
			switch (emsg.type) {
				case ERL_REG_SEND:
					handle_regular_message(emsg.msg);
					erl_free_term(emsg.from); erl_free_term(emsg.msg);
					break;
				case ERL_SEND:
					erl_err_msg("Ignoring SEND message type.");
					erl_free_term(emsg.to); erl_free_term(emsg.msg);
					break;
				case ERL_LINK:
					erl_err_msg("Ignoring LINK message type.");
					erl_free_term(emsg.from); erl_free_term(emsg.to);
					break;
				case ERL_UNLINK:
					erl_err_msg("Ignoring UNLINK message type.");
					erl_free_term(emsg.from); erl_free_term(emsg.to);
					break;
				case ERL_EXIT:
					erl_err_msg("Ignoring EXIT message type.");
					erl_free_term(emsg.from); erl_free_term(emsg.to);
					erl_free_term(emsg.msg);
					break;
				default:
					erl_err_msg("Ignoring unknown message type.");
					break;
			}
		}
	}
}


static void
handle_regular_message(ETERM *msg)
{
	ETERM *call_pattern;
	ETERM *from, *params;
	ETERM *fn, *res;
	int i;

	call_pattern = erl_format("{call, Pid, Fn, Params}");
	if (! erl_match(call_pattern, msg)) {
		erl_err_msg("Ignoring malformed message (not {call, Pid, Fn, Params}).");
	} else {
		from = erl_var_content(call_pattern, "Pid");
		fn = erl_var_content(call_pattern, "Fn");
		params = erl_var_content(call_pattern, "Params");

		if (! ERL_IS_PID(from)) {
			erl_err_msg("Ignoring malformed message (no return PID).");
		} else {
			if (! (ERL_IS_ATOM(fn) && ERL_IS_TUPLE(params))) {
				res = erl_format("{~a, error, unrecognized_message, ~w}", nodeid, msg);
				erl_send(fd, from, res);
				erl_free_term(res);
			} else {
				/* There is potential for optimising this to not use a
				string lookup, but use array indices instead. That
				would mean amending the caller (Erlang) to pass
				through function id's as numbers rather than strings.
				The numbers being indices into the above array. */
				for (i = 0; i < N_FUNCS; i++) {
					if (strcmp(ERL_ATOM_PTR(fn), function_list[i].name) == 0) {
						(*(function_list[i].function))(from, params);
						break; /* leave loop */
					}
				}
				if (i == N_FUNCS) {
					res = erl_format("{~a, error, unrecognized_function, ~w}", nodeid, fn);
					erl_send(fd, from, res);
					erl_free_term(res);
				}
			}
		}

		erl_free_term(from);
		erl_free_term(params);
		erl_free_term(fn);
	}
	erl_free_term(call_pattern);
}


static void
send_badarg_error(ETERM *from, char *fn_name, ETERM *params)
{
	ETERM *res;

	res = erl_format("{~a, error, badarg, [~a, ~w]}", nodeid, fn_name, params);
	erl_send(fd, from, res);
	erl_free_term(res);
}

static void
e_sin(ETERM *from, ETERM *params)
{
	ETERM *arg_term;
	ETERM *res_term;
	double arg;
	double res;

	if (ERL_TUPLE_SIZE(params) != 1) {
		send_badarg_error(from, "e_sin", params);
		return;
	}

	arg_term = erl_element(1, params);
	if (ERL_IS_FLOAT(arg_term))
		arg = ERL_FLOAT_VALUE(arg_term);
	else if (ERL_IS_INTEGER(arg_term))
		arg = ERL_INT_VALUE(arg_term);
	else if (ERL_IS_UNSIGNED_INTEGER(arg_term))
		arg = ERL_INT_UVALUE(arg_term);
	else {
		send_badarg_error(from, "e_sin", params);
		return;
	}
	erl_free_term(arg_term);

	res = sin(arg);
	/* Need to check errno here and do the right thing! */

	res_term = erl_format("{~a, ok, ~f}", nodeid, res);
	erl_send(fd, from, res_term);
	erl_free_term(res_term);
}

static void
e_pow(ETERM *from, ETERM *params)
{
	ETERM *x_term;
	ETERM *y_term;
	ETERM *res_term;
	double x;
	double y;
	double res;

	if (ERL_TUPLE_SIZE(params) != 2) {
		send_badarg_error(from, "e_pow", params);
		return;
	}

	x_term = erl_element(1, params);
	y_term = erl_element(2, params);
	if (ERL_IS_FLOAT(x_term))
		x = ERL_FLOAT_VALUE(x_term);
	else if (ERL_IS_INTEGER(x_term))
		x = (double) ERL_INT_VALUE(x_term);
	else if (ERL_IS_UNSIGNED_INTEGER(x_term))
		x = (double) ERL_INT_UVALUE(x_term);
	else {
		send_badarg_error(from, "e_pow", params);
		return;
	}
	if (ERL_IS_FLOAT(y_term))
		y = ERL_FLOAT_VALUE(y_term);
	else if (ERL_IS_INTEGER(y_term))
		y = (double) ERL_INT_VALUE(y_term);
	else if (ERL_IS_UNSIGNED_INTEGER(y_term))
		y = (double) ERL_INT_UVALUE(y_term);
	else {
		send_badarg_error(from, "e_pow", params);
		return;
	}
	erl_free_term(x_term);
	erl_free_term(y_term);

	res = pow(x, y);
	/* Need to check errno here and do the right thing! */

	res_term = erl_format("{~a, ok, ~f}", nodeid, res);
	erl_send(fd, from, res_term);
	erl_free_term(res_term);
}




More information about the erlang-questions mailing list