aboutsummaryrefslogtreecommitdiffstats
path: root/erts/doc/src/driver.xml
diff options
context:
space:
mode:
Diffstat (limited to 'erts/doc/src/driver.xml')
-rw-r--r--erts/doc/src/driver.xml812
1 files changed, 812 insertions, 0 deletions
diff --git a/erts/doc/src/driver.xml b/erts/doc/src/driver.xml
new file mode 100644
index 0000000000..c396ee0b90
--- /dev/null
+++ b/erts/doc/src/driver.xml
@@ -0,0 +1,812 @@
+<?xml version="1.0" encoding="latin1" ?>
+<!DOCTYPE chapter SYSTEM "chapter.dtd">
+
+<chapter>
+ <header>
+ <copyright>
+ <year>2001</year><year>2009</year>
+ <holder>Ericsson AB. All Rights Reserved.</holder>
+ </copyright>
+ <legalnotice>
+ The contents of this file are subject to the Erlang Public License,
+ Version 1.1, (the "License"); you may not use this file except in
+ compliance with the License. You should have received a copy of the
+ Erlang Public License along with this software. If not, it can be
+ retrieved online at http://www.erlang.org/.
+
+ Software distributed under the License is distributed on an "AS IS"
+ basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
+ the License for the specific language governing rights and limitations
+ under the License.
+
+ </legalnotice>
+
+ <title>How to implement a driver</title>
+ <prepared>Jakob C</prepared>
+ <docno></docno>
+ <date>2000-11-28</date>
+ <rev>PA1</rev>
+ <file>driver.xml</file>
+ </header>
+
+ <note><p>This document was written a long time ago. A lot of it is still
+ valid, but some things have changed since it was first written.
+ Updates of this document are planned for the future. The reader
+ is encouraged to also read the
+ <seealso marker="erl_driver">erl_driver</seealso>, and the
+ <seealso marker="erl_driver">driver_entry</seealso> documentation.
+ </p></note>
+
+ <section>
+ <title>Introduction</title>
+ <p>This chapter tells you how to build your own driver for erlang.</p>
+ <p>A driver in Erlang is a library written in C, that is linked to
+ the Erlang emulator and called from erlang. Drivers can be used
+ when C is more suitable than Erlang, to speed things up, or to
+ provide access to OS resources not directly accessible from
+ Erlang.</p>
+ <p>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 chapter.</p>
+ <p>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 will bring the whole emulator down. In
+ short: you have to be extremely careful!</p>
+ <p></p>
+ </section>
+
+ <section>
+ <title>Sample driver</title>
+ <p>This is a simple driver for accessing a postgres
+ database using the libpq C client library. Postgres
+ is used because it's free and open source. For information
+ on postgres, refer to the website www.postgres.org.</p>
+ <p>The driver is synchronous, it uses the synchronous calls of
+ the client library. This is only for simplicity, and is
+ generally not good, since it will
+ halt the emulator while waiting for the database.
+ This will be improved on below with an asynchronous
+ sample driver.</p>
+ <p>The code is quite straight-forward: all
+ communication between Erlang and the driver
+ is done with <c><![CDATA[port_control/3]]></c>, and the
+ driver returns data back using the <c><![CDATA[rbuf]]></c>.</p>
+ <p>An Erlang driver only exports one function: the driver
+ entry function. This is defined with a macro,
+ <c><![CDATA[DRIVER_INIT]]></c>, and returns a pointer to a
+ C <c><![CDATA[struct]]></c> containing the entry points that are
+ called from the emulator. The <c><![CDATA[struct]]></c> defines the
+ entries that the emulator calls to call the driver, with
+ a <c><![CDATA[NULL]]></c> pointer for entries that are not defined
+ and used by the driver.</p>
+ <p>The <c><![CDATA[start]]></c> entry is called when the driver
+ is opened as a port with <c><![CDATA[open_port/2]]></c>. Here
+ we allocate memory for a user data structure.
+ This user data will be passed every time the emulator
+ calls us. First we store the driver handle, because it
+ is needed in subsequent calls. We allocate memory for
+ the connection handle that is used by LibPQ. We also
+ set the port to return allocated driver binaries, by
+ setting the flag <c><![CDATA[PORT_CONTROL_FLAG_BINARY]]></c>, calling
+ <c><![CDATA[set_port_control_flags]]></c>. (This is because
+ we don't know whether our data will fit in the
+ result buffer of <c><![CDATA[control]]></c>, which has a default size
+ set up by the emulator, currently 64 bytes.)</p>
+ <p>There is an entry <c><![CDATA[init]]></c> which is called when
+ the driver is loaded, but we don't use this, since
+ it is executed only once, and we want to have the
+ possibility of several instances of the driver.</p>
+ <p>The <c><![CDATA[stop]]></c> entry is called when the port
+ is closed.</p>
+ <p>The <c><![CDATA[control]]></c> entry is called from the emulator
+ when the Erlang code calls <c><![CDATA[port_control/3]]></c>,
+ to do the actual work. We have defined a simple set of
+ commands: <c><![CDATA[connect]]></c> to login to the database, <c><![CDATA[disconnect]]></c>
+ to log out and <c><![CDATA[select]]></c> to send a SQL-query and get the result.
+ All results are returned through <c><![CDATA[rbuf]]></c>.
+ The library <c><![CDATA[ei]]></c> in <c><![CDATA[erl_interface]]></c> is used
+ to encode data in binary term format. The result is returned
+ to the emulator as binary terms, so <c><![CDATA[binary_to_term]]></c>
+ is called in Erlang to convert the result to term form.</p>
+ <p>The code is available in <c><![CDATA[pg_sync.c]]></c> in the <c><![CDATA[sample]]></c>
+ directory of <c><![CDATA[erts]]></c>.</p>
+ <p>The driver entry contains the functions that
+ will be called by the emulator. In our simple
+ example, we only provide <c><![CDATA[start]]></c>, <c><![CDATA[stop]]></c>
+ and <c><![CDATA[control]]></c>.</p>
+ <code type="none"><![CDATA[
+/* Driver interface declarations */
+static ErlDrvData start(ErlDrvPort port, char *command);
+static void stop(ErlDrvData drv_data);
+static int control(ErlDrvData drv_data, unsigned int command, char *buf,
+ int len, char **rbuf, int rlen);
+
+static ErlDrvEntry pq_driver_entry = {
+ NULL, /* init */
+ start,
+ stop,
+ NULL, /* output */
+ NULL, /* ready_input */
+ NULL, /* ready_output */
+ "pg_sync", /* the name of the driver */
+ NULL, /* finish */
+ NULL, /* handle */
+ control,
+ NULL, /* timeout */
+ NULL, /* outputv */
+ NULL, /* ready_async */
+ NULL, /* flush */
+ NULL, /* call */
+ NULL /* event */
+};
+ ]]></code>
+ <p>We have a structure to store state needed by the driver,
+ in this case we only need to keep the database connection.</p>
+ <code type="none"><![CDATA[
+typedef struct our_data_s {
+ PGconn* conn;
+} our_data_t;
+ ]]></code>
+ <p>These are control codes we have defined.</p>
+ <code type="none"><![CDATA[
+/* Keep the following definitions in alignment with the
+ * defines in erl_pq_sync.erl
+ */
+
+#define DRV_CONNECT 'C'
+#define DRV_DISCONNECT 'D'
+#define DRV_SELECT 'S'
+ ]]></code>
+ <p>This just returns the driver structure. The macro
+ <c><![CDATA[DRIVER_INIT]]></c> defines the only exported function.
+ All the other functions are static, and will not be exported
+ from the library.</p>
+ <code type="none"><![CDATA[
+/* INITIALIZATION AFTER LOADING */
+
+/*
+ * This is the init function called after this driver has been loaded.
+ * It must *not* be declared static. Must return the address to
+ * the driver entry.
+ */
+
+DRIVER_INIT(pq_drv)
+{
+ return &pq_driver_entry;
+}
+ ]]></code>
+ <p>Here we do some initialization, <c><![CDATA[start]]></c> is called from
+ <c><![CDATA[open_port]]></c>. The data will be passed to <c><![CDATA[control]]></c>
+ and <c><![CDATA[stop]]></c>.</p>
+ <code type="none"><![CDATA[
+/* DRIVER INTERFACE */
+static ErlDrvData start(ErlDrvPort port, char *command)
+{
+ our_data_t* data;
+
+ data = (our_data_t*)driver_alloc(sizeof(our_data_t));
+ data->conn = NULL;
+ set_port_control_flags(port, PORT_CONTROL_FLAG_BINARY);
+ return (ErlDrvData)data;
+}
+ ]]></code>
+ <p>We call disconnect to log out from the database.
+ (This should have been done from Erlang, but just in case.)</p>
+ <code type="none"><![CDATA[
+ static int do_disconnect(our_data_t* data, ei_x_buff* x);
+
+static void stop(ErlDrvData drv_data)
+{
+ do_disconnect((our_data_t*)drv_data, NULL);
+}
+ ]]></code>
+ <p>We use the binary format only to return data to the emulator;
+ input data is a string paramater for <c><![CDATA[connect]]></c> and
+ <c><![CDATA[select]]></c>. The returned data consists of Erlang terms.</p>
+ <p>The functions <c><![CDATA[get_s]]></c> and <c><![CDATA[ei_x_to_new_binary]]></c> are
+ utilities that is used to make the code shorter. <c><![CDATA[get_s]]></c>
+ duplicates the string and zero-terminates it, since the
+ postgres client library wants that. <c><![CDATA[ei_x_to_new_binary]]></c>
+ takes an <c><![CDATA[ei_x_buff]]></c> buffer and allocates a binary and
+ copies the data there. This binary is returned in <c><![CDATA[*rbuf]]></c>.
+ (Note that this binary is freed by the emulator, not by us.)</p>
+ <code type="none"><![CDATA[
+static char* get_s(const char* buf, int len);
+static int do_connect(const char *s, our_data_t* data, ei_x_buff* x);
+static int do_select(const char* s, our_data_t* data, ei_x_buff* x);
+
+/* Since we are operating in binary mode, the return value from control
+ * is irrelevant, as long as it is not negative.
+ */
+static int control(ErlDrvData drv_data, unsigned int command, char *buf,
+ int len, char **rbuf, int rlen)
+{
+ int r;
+ ei_x_buff x;
+ our_data_t* data = (our_data_t*)drv_data;
+ char* s = get_s(buf, len);
+ ei_x_new_with_version(&x);
+ switch (command) {
+ case DRV_CONNECT: r = do_connect(s, data, &x); break;
+ case DRV_DISCONNECT: r = do_disconnect(data, &x); break;
+ case DRV_SELECT: r = do_select(s, data, &x); break;
+ default: r = -1; break;
+ }
+ *rbuf = (char*)ei_x_to_new_binary(&x);
+ ei_x_free(&x);
+ driver_free(s);
+ return r;
+}
+ ]]></code>
+ <p>In <c><![CDATA[do_connect]]></c> is where we log in to the database. If the connection
+ was successful we store the connection handle in our driver
+ data, and return ok. Otherwise, we return the error message
+ from postgres, and store <c><![CDATA[NULL]]></c> in the driver data.</p>
+ <code type="none"><![CDATA[
+static int do_connect(const char *s, our_data_t* data, ei_x_buff* x)
+{
+ PGconn* conn = PQconnectdb(s);
+ if (PQstatus(conn) != CONNECTION_OK) {
+ encode_error(x, conn);
+ PQfinish(conn);
+ conn = NULL;
+ } else {
+ encode_ok(x);
+ }
+ data->conn = conn;
+ return 0;
+}
+ ]]></code>
+ <p>If we are connected (if the connection handle is not <c><![CDATA[NULL]]></c>),
+ we log out from the database. We need to check if a we should
+ encode an ok, since we might get here from the <c><![CDATA[stop]]></c>
+ function, which doesn't return data to the emulator.</p>
+ <code type="none"><![CDATA[
+static int do_disconnect(our_data_t* data, ei_x_buff* x)
+{
+ if (data->conn == NULL)
+ return 0;
+ PQfinish(data->conn);
+ data->conn = NULL;
+ if (x != NULL)
+ encode_ok(x);
+ return 0;
+}
+ ]]></code>
+ <p>We execute a query and encodes the result. Encoding is done
+ in another C module, <c><![CDATA[pg_encode.c]]></c> which is also provided
+ as sample code.</p>
+ <code type="none"><![CDATA[
+static int do_select(const char* s, our_data_t* data, ei_x_buff* x)
+{
+ PGresult* res = PQexec(data->conn, s);
+ encode_result(x, res, data->conn);
+ PQclear(res);
+ return 0;
+}
+ ]]></code>
+ <p>Here we simply checks the result from postgres, and
+ if it's data we encode it as lists of lists with
+ column data. Everything from postgres is C strings,
+ so we just use <c><![CDATA[ei_x_encode_string]]></c> to send
+ the result as strings to Erlang. (The head of the list
+ contains the column names.)</p>
+ <code type="none"><![CDATA[
+void encode_result(ei_x_buff* x, PGresult* res, PGconn* conn)
+{
+ int row, n_rows, col, n_cols;
+ switch (PQresultStatus(res)) {
+ case PGRES_TUPLES_OK:
+ n_rows = PQntuples(res);
+ n_cols = PQnfields(res);
+ ei_x_encode_tuple_header(x, 2);
+ encode_ok(x);
+ ei_x_encode_list_header(x, n_rows+1);
+ ei_x_encode_list_header(x, n_cols);
+ for (col = 0; col < n_cols; ++col) {
+ ei_x_encode_string(x, PQfname(res, col));
+ }
+ ei_x_encode_empty_list(x);
+ for (row = 0; row < n_rows; ++row) {
+ ei_x_encode_list_header(x, n_cols);
+ for (col = 0; col < n_cols; ++col) {
+ ei_x_encode_string(x, PQgetvalue(res, row, col));
+ }
+ ei_x_encode_empty_list(x);
+ }
+ ei_x_encode_empty_list(x);
+ break;
+ case PGRES_COMMAND_OK:
+ ei_x_encode_tuple_header(x, 2);
+ encode_ok(x);
+ ei_x_encode_string(x, PQcmdTuples(res));
+ break;
+ default:
+ encode_error(x, conn);
+ break;
+ }
+}
+ ]]></code>
+ </section>
+
+ <section>
+ <title>Compiling and linking the sample driver</title>
+ <p>The driver should be compiled and linked to a shared
+ library (DLL on windows). With gcc this is done
+ with the link flags <c><![CDATA[-shared]]></c> and <c><![CDATA[-fpic]]></c>.
+ Since we use the <c><![CDATA[ei]]></c> library we should include
+ it too. There are several versions of <c><![CDATA[ei]]></c>, compiled
+ for debug or non-debug and multi-threaded or single-threaded.
+ In the makefile for the samples the <c><![CDATA[obj]]></c> directory
+ is used for the <c><![CDATA[ei]]></c> library, meaning that we use
+ the non-debug, single-threaded version.</p>
+ </section>
+
+ <section>
+ <title>Calling a driver as a port in Erlang</title>
+ <p>Before a driver can be called from Erlang, it must be
+ loaded and opened. Loading is done using the <c><![CDATA[erl_ddll]]></c>
+ module (the <c><![CDATA[erl_ddll]]></c> driver that loads dynamic
+ driver, is actually a driver itself). If loading is ok
+ the port can be opened with <c><![CDATA[open_port/2]]></c>. The port
+ name must match the name of the shared library and
+ the name in the driver entry structure.</p>
+ <p>When the port has been opened, the driver can be called. In
+ the <c><![CDATA[pg_sync]]></c> example, we don't have any data from
+ the port, only the return value from the
+ <c><![CDATA[port_control]]></c>.</p>
+ <p>The following code is the Erlang part of the synchronous
+ postgres driver, <c><![CDATA[pg_sync.erl]]></c>.</p>
+ <code type="none"><![CDATA[
+-module(pg_sync).
+
+-define(DRV_CONNECT, 1).
+-define(DRV_DISCONNECT, 2).
+-define(DRV_SELECT, 3).
+
+-export([connect/1, disconnect/1, select/2]).
+
+connect(ConnectStr) ->
+ 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)).
+ ]]></code>
+ <p>The api is simple: <c><![CDATA[connect/1]]></c> loads the driver, opens it
+ and logs on to the database, returning the Erlang port
+ if successful, <c><![CDATA[select/2]]></c> sends a query to the driver,
+ and returns the result, <c><![CDATA[disconnect/1]]></c> closes the
+ database connection and the driver. (It does not unload it,
+ however.) The connection string should be a connection
+ string for postgres.</p>
+ <p>The driver is loaded with <c><![CDATA[erl_ddll:load_driver/2]]></c>,
+ and if this is successful, or if it's already loaded,
+ it is opened. This will call the <c><![CDATA[start]]></c> function
+ in the driver.</p>
+ <p>We use the <c><![CDATA[port_control/3]]></c> function for all
+ calls into the driver, the result from the driver is
+ returned immediately, and converted to terms by calling
+ <c><![CDATA[binary_to_term/1]]></c>. (We trust that the terms returned
+ from the driver are well-formed, otherwise the
+ <c><![CDATA[binary_to_term]]></c> calls could be contained in a
+ <c><![CDATA[catch]]></c>.)</p>
+ </section>
+
+ <section>
+ <title>Sample asynchronous driver</title>
+ <p>Sometimes database queries can take long time to
+ complete, in our <c><![CDATA[pg_sync]]></c> driver, the emulator
+ halts while the driver is doing it's job. This is
+ often not acceptable, since no other Erlang processes
+ gets a chance to do anything. To improve on our
+ postgres driver, we reimplement it using the asynchronous
+ calls in LibPQ.</p>
+ <p>The asynchronous version of the driver is in the
+ sample files <c><![CDATA[pg_async.c]]></c> and <c><![CDATA[pg_asyng.erl]]></c>.</p>
+ <code type="none"><![CDATA[
+/* Driver interface declarations */
+static ErlDrvData start(ErlDrvPort port, char *command);
+static void stop(ErlDrvData drv_data);
+static int control(ErlDrvData drv_data, unsigned int command, char *buf,
+ int len, char **rbuf, int rlen);
+static void ready_io(ErlDrvData drv_data, ErlDrvEvent event);
+
+static ErlDrvEntry pq_driver_entry = {
+ NULL, /* init */
+ start,
+ stop,
+ NULL, /* output */
+ ready_io, /* ready_input */
+ ready_io, /* ready_output */
+ "pg_async", /* the name of the driver */
+ NULL, /* finish */
+ NULL, /* handle */
+ control,
+ NULL, /* timeout */
+ NULL, /* outputv */
+ NULL, /* ready_async */
+ NULL, /* flush */
+ NULL, /* call */
+ NULL /* event */
+};
+
+typedef struct our_data_t {
+ PGconn* conn;
+ ErlDrvPort port;
+ int socket;
+ int connecting;
+} our_data_t;
+ ]]></code>
+ <p>Here some things have changed from <c><![CDATA[pg_sync.c]]></c>: we use the
+ entry <c><![CDATA[ready_io]]></c> for <c><![CDATA[ready_input]]></c> and
+ <c><![CDATA[ready_output]]></c> which will be called from the emulator only
+ when there is input to be read from the socket. (Actually, the
+ socket is used in a <c><![CDATA[select]]></c> function inside
+ the emulator, and when the socket is signalled,
+ indicating there is data to read, the <c><![CDATA[ready_input]]></c> entry
+ is called. More on this below.)</p>
+ <p>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
+ <c><![CDATA[driver_output]]></c>. We have a flag <c><![CDATA[connecting]]></c> to tell
+ whether the driver is waiting for a connection or waiting
+ for the result of a query. (This is needed since the entry
+ <c><![CDATA[ready_io]]></c> will be called both when connecting and
+ when there is query result.)</p>
+ <code type="none"><![CDATA[
+static int do_connect(const char *s, our_data_t* data)
+{
+ PGconn* conn = PQconnectStart(s);
+ if (PQstatus(conn) == CONNECTION_BAD) {
+ ei_x_buff x;
+ ei_x_new_with_version(&x);
+ encode_error(&x, conn);
+ PQfinish(conn);
+ conn = NULL;
+ driver_output(data->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;
+}
+ ]]></code>
+ <p>The <c><![CDATA[connect]]></c> function looks a bit different too. We connect
+ using the asynchronous <c><![CDATA[PQconnectStart]]></c> function. After the
+ connection is started, we retrieve the socket for the connection
+ with <c><![CDATA[PQsocket]]></c>. This socket is used with the
+ <c><![CDATA[driver_select]]></c> function to wait for connection. When
+ the socket is ready for input or for output, the <c><![CDATA[ready_io]]></c>
+ function will be called.</p>
+ <p>Note that we only return data (with <c><![CDATA[driver_output]]></c>) if there
+ is an error here, otherwise we wait for the connection to be completed,
+ in which case our <c><![CDATA[ready_io]]></c> function will be called.</p>
+ <code type="none"><![CDATA[
+static int do_select(const char* s, our_data_t* data)
+{
+ data->connecting = 0;
+ PGconn* conn = data->conn;
+ /* if there's an error return it now */
+ if (PQsendQuery(conn, s) == 0) {
+\011ei_x_buff x;
+\011ei_x_new_with_version(&x);
+\011encode_error(&x, conn);
+\011driver_output(data->port, x.buff, x.index);
+\011ei_x_free(&x);
+ }
+ /* else wait for ready_output to get results */
+ return 0;
+}
+ ]]></code>
+ <p>The <c><![CDATA[do_select]]></c> function initiates a select, and returns
+ if there is no immediate error. The actual result will be returned
+ when <c><![CDATA[ready_io]]></c> is called.</p>
+ <code type="none"><![CDATA[
+static void ready_io(ErlDrvData drv_data, ErlDrvEvent event)
+{
+ PGresult* res = NULL;
+ our_data_t* data = (our_data_t*)drv_data;
+ PGconn* conn = data->conn;
+ ei_x_buff x;
+ ei_x_new_with_version(&x);
+ if (data->connecting) {
+\011ConnStatusType status;
+\011PQconnectPoll(conn);
+\011status = PQstatus(conn);
+\011if (status == CONNECTION_OK)
+\011 encode_ok(&x);
+\011else if (status == CONNECTION_BAD)
+\011 encode_error(&x, conn);
+ } else {
+\011PQconsumeInput(conn);
+\011if (PQisBusy(conn))
+\011 return;
+\011res = PQgetResult(conn);
+\011encode_result(&x, res, conn);
+\011PQclear(res);
+\011for (;;) {
+\011 res = PQgetResult(conn);
+\011 if (res == NULL)
+\011\011break;
+\011 PQclear(res);
+\011}
+ }
+ if (x.index > 1) {
+\011driver_output(data->port, x.buff, x.index);
+\011if (data->connecting)
+\011 driver_select(data->port, (ErlDrvEvent)data->socket, DO_WRITE, 0);
+ }
+ ei_x_free(&x);
+}
+ ]]></code>
+ <p>The <c><![CDATA[ready_io]]></c> function will be called when the socket
+ we got from postgres is ready for input or output. Here
+ we first check if we are connecting to the database. In that
+ case we check connection status and return ok if the
+ connection is successful, or error if it's not. If the
+ connection is not yet established, we simply return; <c><![CDATA[ready_io]]></c>
+ will be called again.</p>
+ <p>If we have result from a connect, indicated that we have data in
+ the <c><![CDATA[x]]></c> buffer, we no longer need to select on
+ output (<c><![CDATA[ready_output]]></c>), so we remove this by calling
+ <c><![CDATA[driver_select]]></c>.</p>
+ <p>If we're not connecting, we're waiting for results from a
+ <c><![CDATA[PQsendQuery]]></c>, so we get the result and return it. The
+ encoding is done with the same functions as in the earlier
+ example.</p>
+ <p>We should add error handling here, for instance checking
+ that the socket is still open, but this is just a simple
+ example.</p>
+ <p>The Erlang part of the asynchronous driver consists of the
+ sample file <c><![CDATA[pg_async.erl]]></c>.</p>
+ <code type="none"><![CDATA[
+-module(pg_async).
+
+-define(DRV_CONNECT, $C).
+-define(DRV_DISCONNECT, $D).
+-define(DRV_SELECT, $S).
+
+-export([connect/1, disconnect/1, select/2]).
+
+connect(ConnectStr) ->
+ case erl_ddll:load_driver(".", "pg_async") of
+\011ok -> ok;
+\011{error, already_loaded} -> ok;
+\011_ -> 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
+\011ok ->
+\011 {ok, Port};
+\011Error ->
+\011 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
+\011{Port, {data, Data}} ->
+\011 binary_to_term(Data)
+ end.
+ ]]></code>
+ <p>The Erlang code is slightly different, this is because we
+ don't return the result synchronously from <c><![CDATA[port_control]]></c>,
+ instead we get it from <c><![CDATA[driver_output]]></c> as data in the
+ message queue. The function <c><![CDATA[return_port_data]]></c> above
+ receives data from the port. Since the data is in
+ binary format, we use <c><![CDATA[binary_to_term/1]]></c> to convert
+ it to Erlang term. Note that the driver is opened in
+ binary mode, <c><![CDATA[open_port/2]]></c> is called with the option
+ <c><![CDATA[[binary]]]></c>. This means that data sent from the driver
+ to the emulator is sent as binaries. Without the <c><![CDATA[binary]]></c>
+ option, they would have been lists of integers.</p>
+ </section>
+
+ <section>
+ <title>An asynchronous driver using driver_async</title>
+ <p>As a final example we demonstrate the use of <c><![CDATA[driver_async]]></c>.
+ We also use the driver term interface. The driver is written
+ in C++. This enables us to use an algorithm from STL. We will
+ use the <c><![CDATA[next_permutation]]></c> algorithm to get the next permutation
+ of a list of integers. For large lists (more than 100000
+ elements), this will take some time, so we will perform this
+ as an asynchronous task.</p>
+ <p>The asynchronous api for drivers are quite complicated. First
+ of all, the work must be prepared. In our example we do this
+ in <c><![CDATA[output]]></c>. We could have used <c><![CDATA[control]]></c> just as well,
+ but we want some variation in our examples. In our driver, we allocate
+ a structure that contains all needed for the asynchronous task
+ to do the work. This is done in the main emulator thread.
+ Then the asynchronous function is called from a driver thread,
+ separate from the main emulator thread. Note that the driver-
+ functions are not reentrant, so they shouldn't be used.
+ Finally, after the function is completed, the driver callback
+ <c><![CDATA[ready_async]]></c> is called from the main emulator thread,
+ this is where we return the result to Erlang. (We can't
+ return the result from within the asynchronous function, since
+ we can't call the driver-functions.)</p>
+ <p>The code below is from the sample file <c><![CDATA[next_perm.cc]]></c>.</p>
+ <p>The driver entry looks like before, but also contains the
+ call-back <c><![CDATA[ready_async]]></c>.</p>
+ <code type="none"><![CDATA[
+static ErlDrvEntry next_perm_driver_entry = {
+ NULL,\011\011\011/* init */
+ start,
+ NULL, \011\011\011/* stop */
+ output,\011\011\011
+ NULL,\011\011\011/* ready_input */
+ NULL,\011\011\011/* ready_output */
+ "next_perm", /* the name of the driver */
+ NULL,\011\011\011/* finish */
+ NULL,\011\011\011/* handle */
+ NULL,\011\011\011/* control */
+ NULL,\011\011\011/* timeout */
+ NULL,\011\011\011/* outputv */
+ ready_async,
+ NULL,\011\011\011/* flush */
+ NULL,\011\011\011/* call */
+ NULL\011\011\011/* event */
+};
+ ]]></code>
+ <p>The <c><![CDATA[output]]></c> function allocates the work-area of the
+ asynchronous function. Since we use C++, we use a struct,
+ and stuff the data in it. We have to copy the original data,
+ it is not valid after we have returned from the <c><![CDATA[output]]></c>
+ function, and the <c><![CDATA[do_perm]]></c> function will be called later,
+ and from another thread. We return no data here, instead it will
+ be sent later from the <c><![CDATA[ready_async]]></c> call-back.</p>
+ <p>The <c><![CDATA[async_data]]></c> will be passed to the <c><![CDATA[do_perm]]></c> function.
+ We do not use a <c><![CDATA[async_free]]></c> function (the last argument to
+ <c><![CDATA[driver_async]]></c>, it's only used if the task is cancelled
+ programmatically.</p>
+ <code type="none"><![CDATA[
+struct our_async_data {
+ bool prev;
+ vector<int> data;
+ our_async_data(ErlDrvPort p, int command, const char* buf, int len);
+};
+
+our_async_data::our_async_data(ErlDrvPort p, int command,
+\011\011\011 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<ErlDrvPort>(drv_data);
+ void* async_data = new our_async_data(port, *buf, buf+1, len);
+ driver_async(port, NULL, do_perm, async_data, do_free);
+}
+ ]]></code>
+ <p>In the <c><![CDATA[do_perm]]></c> we simply do the work, operating
+ on the structure that was allocated in <c><![CDATA[output]]></c>.</p>
+ <code type="none"><![CDATA[
+static void do_perm(void* async_data)
+{
+ our_async_data* d = reinterpret_cast<our_async_data*>(async_data);
+ if (d->prev)
+\011prev_permutation(d->data.begin(), d->data.end());
+ else
+\011next_permutation(d->data.begin(), d->data.end());
+}
+ ]]></code>
+ <p>In the <c><![CDATA[ready_async]]></c> function, the output is sent back to the
+ emulator. We use the driver term format instead of <c><![CDATA[ei]]></c>.
+ This is the only way to send Erlang terms directly to a driver,
+ without having the Erlang code to call <c><![CDATA[binary_to_term/1]]></c>. In
+ our simple example this works well, and we don't need to use
+ <c><![CDATA[ei]]></c> to handle the binary term format.</p>
+ <p>When the data is returned we deallocate our data.</p>
+ <code type="none"><![CDATA[
+static void ready_async(ErlDrvData drv_data, ErlDrvThreadData async_data)
+{
+ ErlDrvPort port = reinterpret_cast<ErlDrvPort>(drv_data);
+ our_async_data* d = reinterpret_cast<our_async_data*>(async_data);
+ int n = d->data.size(), result_n = n*2 + 3;
+ ErlDrvTermData* result = new ErlDrvTermData[result_n], * rp = result;
+ for (vector<int>::iterator i = d->data.begin();
+\011 i != d->data.end(); ++i) {
+\011*rp++ = ERL_DRV_INT;
+\011*rp++ = *i;
+ }
+ *rp++ = ERL_DRV_NIL;
+ *rp++ = ERL_DRV_LIST;
+ *rp++ = n+1;
+ driver_output_term(port, result, result_n);
+ delete[] result;
+ delete d;
+}
+ ]]></code>
+ <p>This driver is called like the others from Erlang, however, since
+ we use <c><![CDATA[driver_output_term]]></c>, there is no need to call
+ binary_to_term. The Erlang code is in the sample file
+ <c><![CDATA[next_perm.erl]]></c>.</p>
+ <p>The input is changed into a list of integers and sent to
+ the driver.</p>
+ <code type="none"><![CDATA[
+-module(next_perm).
+
+-export([next_perm/1, prev_perm/1, load/0, all_perm/1]).
+
+load() ->
+ case whereis(next_perm) of
+\011undefined ->
+\011 case erl_ddll:load_driver(".", "next_perm") of
+\011\011ok -> ok;
+\011\011{error, already_loaded} -> ok;
+\011\011E -> exit(E)
+\011 end,
+\011 Port = open_port({spawn, "next_perm"}, []),
+\011 register(next_perm, Port);
+\011_ ->
+\011 ok
+ end.
+
+list_to_integer_binaries(L) ->
+ [<<I:32/integer-native>> || 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
+\011Result ->
+\011 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]).
+ ]]></code>
+ </section>
+</chapter>
+