======R Connection API======
This document contains information about a proposed [[http://www.r-project.org|R]] connection API.
=====Rationale=====
* Replacement for existing connections internals
* Provide a mechanism to interface R with arbitrary data streams:
* hardware interfaces (serial, USB, etc.)
* novel/non-standard software interfaces (binary file formats)
* Further modularize R core by pushing some (all?) connections into packages
* Allow the R community to write new connections, taking advantage of the existing functionality:
* R level read/write routines
* Automagic character re-encoding
=====Connection API=====
The API designs presented below are partially motivated by R-devel posts (2006) by Jeff Horner.
====Creation====
The connection creation API consists of two functions, ''R_InitXConnection'' and ''R_RegisterXConnection'', to be called in sequence. The first function allocates (R_alloc) and initializes the public (struct Xconn) connection structure. User code then sets function pointers to I/O methods. Finally, the second function is used to register the connection. Registration involves checking the validity of the connection structure, setting up garbage collection, and making the connection accessible in the R session. This setup has several good qualities:
* extremely flexible for connection developers
* no access to other connections
* no access to private variables (encoding, etc.)
* API has 'final say' before connections are registered
====Code====
/** maximum number of Xconnections **/
#define MAXXCONNS 128
typedef enum {
XMODE_NULL,
XMODE_R,
XMODE_W,
XMODE_RW,
XMODE_A,
XMODE_RA
} Xmode;
typedef struct Xconn *Xconnection;
struct Xconn {
int (*open)(Xconnection, Xmode, const char *);
void (*close)(Xconnection);
void (*destroy)(Xconnection);
int (*seek)(Xconnection, long, int, int);
int (*flush)(Xconnection);
void (*truncate)(Xconnection);
size_t (*read)(void *, size_t, size_t, Xconnection);
size_t (*write)(const void *, size_t, size_t, Xconnection);
SEXP (*control)(Xconnection, SEXP);
void *data;
};
#include "Xconnections.h"
typedef struct Xconn_private Xprivate;
static Xprivate XConnections[MAXXCONNS];
/** private connection structure **/
struct Xconn_private {
/** unique id **/
int id;
/** class name **/
char* class;
/** filename, url, etc. converted to CE_NATIVE (translateChar)**/
char* description;
/** character encoding name (iconv) **/
char* encname;
/** file operation mode **/
Xmode mode;
/** is connection open? **/
Rboolean open;
/** stuff for character encoding **/
void *iiconv, *oiconv; /* iconv_t */
char *iiconvbuff, *oiconvbuff, *inext, *onext;
void *exptr;
/** pointer to user connection structure **/
Xconnection conn;
};
/** private functions **/
/** return index of next available member of XConnections,
return non-negative on success, negative on failure **/
static int NextXConnection(void);
/** cleanup a connection **/
static void DestroyXConnection(int);
/** initialize a struct Xconn_private **/
static void init_Xprivate(Xprivate);
/** initialize a struct Xconn **/
static void init_Xconnection(Xconnection);
/** cleanup memory associated with struct Xconn_private **/
static void free_Xprivate(Xprivate);
/** parse an R mode string **/
static Xmode parse_mode(const char *);
/** initialize character re-encoding return 0 on success, non-zero on fail**/
static int init_iconv(Xprivate);
/** C finalizer for struct Xconn_private **/
static void finalizer(SEXP exptr);
/** public functions **/
Xconnection R_InitXConnection(void);
SEXP R_RegisterXConnection(char *, char *, char *, char *, Xconnection);
static int NextXConnection(void)
{
int i;
for(i = 0; i < MAXXCONNS; i++)
if(!XConnections[i]) return i;
return -1;
}
static void DestroyXConnection(int nconn)
{
Xprivate priv = XConnections[nconn];
if(priv) {
XConnections[nconn] = NULL;
if(priv->open && priv->conn->close)
priv->conn->close(priv->conn);
if(priv->conn->destroy)
priv->conn->destroy(priv->conn);
free_Xprivate(priv);
}
}
static void init_Xprivate(Xprivate priv)
{
priv->class = NULL;
priv->description = NULL;
priv->encname = NULL;
priv->mode = XMODE_NULL;
priv->open = FALSE;
priv->iiconv = NULL;
priv->oiconv = NULL;
priv->iiconvbuff = NULL;
priv->inext = NULL;
priv->onext = NULL;
priv->exptr = NULL;
priv->xconn = NULL;
}
static void init_Xconnection(Xconnection conn)
{
conn->open = NULL;
conn->close = NULL;
conn->destroy = NULL;
conn->seek = NULL;
conn->flush = NULL;
conn->truncate = NULL;
conn->read = NULL;
conn->write = NULL;
conn->control = NULL;
conn->data = NULL;
}
static void free_Xprivate(Xprivate priv)
{
if(priv) {
if(priv->encname) free(priv->encname);
if(priv->description) free(priv->description);
if(priv->class) free(priv->class);
if(priv->conn) free(priv->conn);
free(priv);
}
}
static int init_iconv(Xprivate priv)
{
/* not yet implemented, disable */
if(priv->encname)
free(priv->encname)
priv->encname = NULL;
return 0;
}
static void finalizer(SEXP exptr)
{
int i, nconn = -1;
int *idptr = (int *) R_ExternalPtrAddr(exptr);
for(i = 0; i < MAXXCONNS; i++) {
if(XConnections[i] && &XConnections[i]->id == idptr) {
nconn = i;
break;
}
}
if(nconn < 0) return;
warning("closing unused Xconnection %d (%s)", priv->id, priv->conn->description);
DestroyXConnection(nconn);
R_ClearExternalPtr(exptr); /* not really needed */
}
Xconnection R_InitXConnection(void)
{
Xconnection conn = NULL
conn = (Xconnection) R_alloc(1, sizeof(struct Xconn));
init_Xconnection(conn);
return conn;
}
static void register_fail(Xprivate priv, const char *msg)
{
free_Xprivate(priv);
error("failed to register Xconnection: %s", msg);
}
/* only encname may be NULL */
SEXP R_RegisterXConnection(char *class, char *description, char *mode, char *encname, Xconnection conn)
{
Xprivate priv = NULL;
SEXP ans, sclass;
int nconn;
/* FIXME check Xconnection */
/* check for space in XConnections */
nconn = NextXConnection();
if(nconn < 0) register_fail(priv, "too many Xconnections");
/* check conn, class, description, encname, mode, copy to priv */
priv = (Xprivate) malloc(sizeof(struct Xconn_private));
if(!priv) register_fail(priv, "memory allocation failed");
init_Xprivate(priv);
priv->conn = (Xconnection) malloc(sizeof(struct Xconn));
if(!priv->conn) register_fail(priv, "memory allocation failed");
memcpy(priv->conn, conn, sizeof(struct Xconn));
priv->class = (char *) malloc(strlen(class) + 1);
if(!priv->class) register_fail(priv, "memory allocation failed");
strcpy(priv->class, class);
priv->description = (char *) malloc(strlen(description) + 1);
if(!priv->description) register_fail(priv, "memory allocation failed");
strcpy(priv->description, description);
if(encname) {
priv->encname = (char *) malloc(strlen(encname) + 1);
if(!priv->encname) register_fail(priv, "memory allocation failed");
strcpy(priv->encname, encname);
/* initialize character re-encoding */
if(init_iconv(priv))
register_fail(priv, "iconv initialization failed");
}
priv->mode = parse_mode(mode);
if(priv->mode == XMODE_NULL)
register_fail(priv, "invalid mode argument");
/* initialize finalizer code */
priv->exptr = R_MakeExternalPtr(con->id, install("Xconnection"), R_NilValue);
setAttrib(ans, install("exptr"), priv->exptr);
R_RegisterCFinalizerEx(priv->exptr, finalizer, FALSE);
/* store Xprivate pointer */
XConnections[nconn] = priv;
/* prepare return value */
PROTECT(ans = allocVector(INTSXP, 1));
INTEGER(ans)[0] = nconn;
PROTECT(sclass = allocVector(STRSXP, 2));
SET_STRING_ELT(sclass, 0, mkChar(priv->class));
SET_STRING_ELT(sclass, 1, mkChar("Xconnection"));
classgets(ans, sclass);
UNPROTECT(2);
return ans;
}
====Example====
SEXP nullConnection(SEXP description, SEXP mode, SEXP blocking, SEXP data)
{
SEXP ans;
Xconnection conn;
if(!isString(description) || length(description)!=1)
error("invalid %s argument", "description");
if(!isString(mode) || length(mode)!=1)
error("invalid %s argument", "mode");
if(!isLogical(blocking) || length(blocking)!=1)
error("invalid %s argument", "blocking")
conn = R_InitXConnection();
/*
- set blocking
- set I/O operation pointers
- set encname
- set private
*/
return R_RegisterXConnection("null",\
CHAR(STRING_ELT(description,0),\
CHAR(STRING_ELT(mode,0)), CE_NATIVE, conn);
}
As a test case, this example code should compile as is, and not cause problems (other than R-level errors). This is the 'null' connection