erl_interface メモ

C と Erlang ノード上のプロセスの間で、直接メッセージを送受信できる便利なライブラリ erl_interface を試したみた。
以下、メモと言うより code snippets。

pingpong.c

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include "erl_interface.h"
#include "ei.h"

extern const char *erl_thisnodename(void);
extern short erl_thiscreation(void);
#define SELF(fd) erl_mk_pid(erl_thisnodename(), fd, 0, erl_thiscreation())

void init();
int  connect_and_get_sockfd();
void send_ping(int);
void recv_pong(int);
int  validate_message(ETERM*);

int main() {
    init();

    int sockfd = connect_and_get_sockfd();
    printf("Connect OK\n");

    send_ping(sockfd);
    recv_pong(sockfd);

    erl_close_connection(sockfd);
    return 0;
}

void init() {
    erl_init(NULL, 0);

    int  creation = 1;
    char *cookie = "foobar";

    struct in_addr addr;
    addr.s_addr = inet_addr("0.0.0.0");
    char *host  = "localhost";
    char *alive = "ctest";
    char node[strlen(alive) + strlen(host) + 1];
    sprintf(node, "%s@%s", alive, host);
    if (!erl_connect_xinit(host, alive, node, &addr, cookie, creation))
        erl_err_quit("erl_connect_xinit failed");
}

int connect_and_get_sockfd() {
    int  sockfd;
    char *alive = "test";
    struct in_addr addr;

    addr.s_addr = inet_addr("0.0.0.0");
    if ((sockfd = erl_xconnect(&addr, alive)) < 0)
        erl_err_quit("erl_connect failed");

    return sockfd;
}

void send_ping(int sockfd) {
    ETERM *arr[3], *emsg;

    arr[0] = erl_mk_atom("message");
    arr[1] = SELF(sockfd);
    arr[2] = erl_format("~s", "ping");
    emsg   = erl_mk_tuple(arr, 3);

    erl_reg_send(sockfd, "pingpong", emsg);

    for (int i=0; i < 3; i++)
        erl_free_term(arr[i]);
    erl_free_term(emsg);
}

void recv_pong(int sockfd) {
    int rc;
    int size = 1024;
    unsigned char *buf = malloc(size);
    ErlMessage emsg;

    if (
      (rc = erl_xreceive_msg(sockfd, &buf, &size, &emsg)) == ERL_MSG
    ) {
        if (!validate_message(emsg.msg))
            erl_err_quit("validate error.");

        printf(
            "Message: {%s, %d, %s}\n",
            ERL_ATOM_PTR(erl_element(1, emsg.msg)),
            ERL_PID_NUMBER(erl_element(2, emsg.msg)),
            erl_iolist_to_string(erl_element(3, emsg.msg))
        );

        erl_free_term(emsg.msg);
        erl_free_term(emsg.to);
    }

    free(buf);
}

int validate_message(ETERM *message) {
    if (!ERL_IS_TUPLE(message)) {
        puts("Message is't TUPLE.");
        return 0;
    }

    if (erl_size(message) != 3) {
        puts("TUPLE size is't 3.");
        return 0;
    }

    if (!ERL_IS_ATOM(erl_element(1, message))) {
        puts("Elem of 1st in TUPLE is't ATOM.");
        return 0;
    }

    if (!ERL_IS_PID(erl_element(2, message))) {
        puts("Elem of 2nd in TUPLE is't PID.");
        return 0;
    }

    if (!ERL_IS_LIST(erl_element(3, message))) {
        puts("Elem of 3rd in TUPLE is't LIST.");
        return 0;
    }

    ETERM *head, *list = erl_element(3, message);
    while (!ERL_IS_EMPTY_LIST(list)) {
        head = ERL_CONS_HEAD(list);
        list = ERL_CONS_TAIL(list);
        if (ERL_IS_INTEGER(head)) continue;
        puts("Elem of 3rd in TUPLE is't STRING.");
        return 0;
    }

    return 1;
}

Makefile

CC=gcc
ERL=/path/to/erlang/lib/erl_interface-X.X.X.X

ALL : pingpong

pingpong : pingpong.c
        $(CC) -std=c99 -o hello hello.c \
                -I$(ERL)/include \
                -L$(ERL)/lib -lerl_interface -lei

pingpong.erl

-module(pingpong).
-compile(export_all).

start() -> register(pingpong, spawn(?MODULE, loop, [])).
stop()  -> pingpong ! stop.

loop() ->
  receive
    stop ->
      io:fwrite("~w:stop.~n", [self()]),
      exit(ok);
    {message, Pid, Message} ->
      io:fwrite("Pid:~w~nMessage:~s~n", [Pid, Message]),
      Pid ! {message, self(), "pong"};
    Other ->
      io:fwrite("Other:~w~n", [Other])
  end,
  loop().
% erl -name test@localhost -s pingpong start
% make
% ./pingpong
Connect OK
Message: {message, 45, pong}

Inline::C を使って perl から使える事を確認した。後でそっちの module もさらす。
さらした -> id:cooldaemon:20080214