This section was written a long time ago. Most of it is still
valid, as it explains important concepts, but this was
written for an older driver interface so the examples do not
work anymore. The reader is encouraged to read the
This section describes how to build your own driver for Erlang.
A driver in Erlang is a library written in C, which is linked to the Erlang emulator and called from Erlang. Drivers can be used when C is more suitable than Erlang, to speed up things, or to provide access to OS resources not directly accessible from Erlang.
A driver can be dynamically loaded, as a shared library (known as a DLL on Windows), or statically loaded, linked with the emulator when it is compiled and linked. Only dynamically loaded drivers are described here, statically linked drivers are beyond the scope of this section.
When a driver is loaded it is executed in the context of the emulator, shares the same memory and the same thread. This means that all operations in the driver must be non-blocking, and that any crash in the driver brings the whole emulator down. In short, be careful.
This section describes a simple driver for accessing a postgres
database using the libpq C client library. Postgres
is used because it is free and open source. For information on postgres,
see
The driver is synchronous, it uses the synchronous calls of the client library. This is only for simplicity, but not good, as it halts the emulator while waiting for the database. This is improved below with an asynchronous sample driver.
The code is straightforward: all
communication between Erlang and the driver
is done with
An Erlang driver only exports one function: the driver
entry function. This is defined with a macro,
The
An entry
The
The
The code is available in
The driver entry contains the functions that
will be called by the emulator. In this example,
only
We have a structure to store state needed by the driver, in this case we only need to keep the database connection:
The control codes that we have defined are as follows:
This returns the driver structure. The macro
Here some initialization is done,
conn = NULL;
set_port_control_flags(port, PORT_CONTROL_FLAG_BINARY);
return (ErlDrvData)data;
}
]]>
We call disconnect to log out from the database. (This should have been done from Erlang, but just in case.)
We use the binary format only to return data to the emulator;
input data is a string parameter for
The functions
conn = conn;
return 0;
}
]]>
If we are connected (and if the connection handle is not
conn == NULL)
return 0;
PQfinish(data->conn);
data->conn = NULL;
if (x != NULL)
encode_ok(x);
return 0;
}
]]>
We execute a query and encode the result. Encoding is done in
another C module,
conn, s);
encode_result(x, res, data->conn);
PQclear(res);
return 0;
}
]]>
Here we check the result from postgres.
If it is data, we encode it as lists of lists with
column data. Everything from postgres is C strings,
so we use
The driver is to be compiled and linked to a shared
library (DLL on Windows). With gcc, this is done with
link flags
Before a driver can be called from Erlang, it must be
loaded and opened. Loading is done using the
When the port has been opened, the driver can be called. In
the
The following code is the Erlang part of the synchronous
postgres driver,
case erl_ddll:load_driver(".", "pg_sync") of
ok -> ok;
{error, already_loaded} -> ok;
E -> exit({error, E})
end,
Port = open_port({spawn, ?MODULE}, []),
case binary_to_term(port_control(Port, ?DRV_CONNECT, ConnectStr)) of
ok -> {ok, Port};
Error -> Error
end.
disconnect(Port) ->
R = binary_to_term(port_control(Port, ?DRV_DISCONNECT, "")),
port_close(Port),
R.
select(Port, Query) ->
binary_to_term(port_control(Port, ?DRV_SELECT, Query)).
]]>
The API is simple:
The connection string is to be a connection string for postgres.
The driver is loaded with
We use the
Sometimes database queries can take a long time to
complete, in our
The asynchronous version of the driver is in the sample files
Some things have changed from
Our driver data is also extended, we keep track of the
socket used for communication with postgres, and also
the port, which is needed when we send data to the port with
port, x.buff, x.index);
ei_x_free(&x);
}
PQconnectPoll(conn);
int socket = PQsocket(conn);
data->socket = socket;
driver_select(data->port, (ErlDrvEvent)socket, DO_READ, 1);
driver_select(data->port, (ErlDrvEvent)socket, DO_WRITE, 1);
data->conn = conn;
data->connecting = 1;
return 0;
}
]]>
The
Notice that we only return data (with
connecting = 0;
PGconn* conn = data->conn;
/* if there's an error return it now */
if (PQsendQuery(conn, s) == 0) {
ei_x_buff x;
ei_x_new_with_version(&x);
encode_error(&x, conn);
driver_output(data->port, x.buff, x.index);
ei_x_free(&x);
}
/* else wait for ready_output to get results */
return 0;
}
]]>
The
conn;
ei_x_buff x;
ei_x_new_with_version(&x);
if (data->connecting) {
ConnStatusType status;
PQconnectPoll(conn);
status = PQstatus(conn);
if (status == CONNECTION_OK)
encode_ok(&x);
else if (status == CONNECTION_BAD)
encode_error(&x, conn);
} else {
PQconsumeInput(conn);
if (PQisBusy(conn))
return;
res = PQgetResult(conn);
encode_result(&x, res, conn);
PQclear(res);
for (;;) {
res = PQgetResult(conn);
if (res == NULL)
break;
PQclear(res);
}
}
if (x.index > 1) {
driver_output(data->port, x.buff, x.index);
if (data->connecting)
driver_select(data->port, (ErlDrvEvent)data->socket, DO_WRITE, 0);
}
ei_x_free(&x);
}
]]>
The
If we have a result from a connect, indicated by having data in
the
If we are not connecting, we wait for results from a
Error handling is to be added here, for example, checking that the socket is still open, but this is only a simple example.
The Erlang part of the asynchronous driver consists of the
sample file
case erl_ddll:load_driver(".", "pg_async") of
ok -> ok;
{error, already_loaded} -> ok;
_ -> exit({error, could_not_load_driver})
end,
Port = open_port({spawn, ?MODULE}, [binary]),
port_control(Port, ?DRV_CONNECT, ConnectStr),
case return_port_data(Port) of
ok ->
{ok, Port};
Error ->
Error
end.
disconnect(Port) ->
port_control(Port, ?DRV_DISCONNECT, ""),
R = return_port_data(Port),
port_close(Port),
R.
select(Port, Query) ->
port_control(Port, ?DRV_SELECT, Query),
return_port_data(Port).
return_port_data(Port) ->
receive
{Port, {data, Data}} ->
binary_to_term(Data)
end.
]]>
The Erlang code is slightly different, as we do not
return the result synchronously from
As a final example we demonstrate the use of
The asynchronous API for drivers is complicated. First,
the work must be prepared. In the example, this is done in
The following code is from the sample file
The
The
data;
our_async_data(ErlDrvPort p, int command, const char* buf, int len);
};
our_async_data::our_async_data(ErlDrvPort p, int command,
const char* buf, int len)
: prev(command == 2),
data((int*)buf, (int*)buf + len / sizeof(int))
{
}
static void do_perm(void* async_data);
static void output(ErlDrvData drv_data, char *buf, int len)
{
if (*buf < 1 || *buf > 2) return;
ErlDrvPort port = reinterpret_cast(drv_data);
void* async_data = new our_async_data(port, *buf, buf+1, len);
driver_async(port, NULL, do_perm, async_data, do_free);
}
]]>
In the
(async_data);
if (d->prev)
prev_permutation(d->data.begin(), d->data.end());
else
next_permutation(d->data.begin(), d->data.end());
}
]]>
In the
When the data is returned, we deallocate our data.
(drv_data);
our_async_data* d = reinterpret_cast(async_data);
int n = d->data.size(), result_n = n*2 + 3;
ErlDrvTermData *result = new ErlDrvTermData[result_n], *rp = result;
for (vector::iterator i = d->data.begin();
i != d->data.end(); ++i) {
*rp++ = ERL_DRV_INT;
*rp++ = *i;
}
*rp++ = ERL_DRV_NIL;
*rp++ = ERL_DRV_LIST;
*rp++ = n+1;
driver_output_term(port, result, result_n);
delete[] result;
delete d;
}
]]>
This driver is called like the others from Erlang. However, as
we use
The input is changed into a list of integers and sent to the driver.
case whereis(next_perm) of
undefined ->
case erl_ddll:load_driver(".", "next_perm") of
ok -> ok;
{error, already_loaded} -> ok;
E -> exit(E)
end,
Port = open_port({spawn, "next_perm"}, []),
register(next_perm, Port);
_ ->
ok
end.
list_to_integer_binaries(L) ->
[<> || I <- L].
next_perm(L) ->
next_perm(L, 1).
prev_perm(L) ->
next_perm(L, 2).
next_perm(L, Nxt) ->
load(),
B = list_to_integer_binaries(L),
port_control(next_perm, Nxt, B),
receive
Result ->
Result
end.
all_perm(L) ->
New = prev_perm(L),
all_perm(New, L, [New]).
all_perm(L, L, Acc) ->
Acc;
all_perm(L, Orig, Acc) ->
New = prev_perm(L),
all_perm(New, Orig, [New | Acc]).
]]>