This shows you the differences between two versions of the page.
— |
r-connection-api [2017/09/20 19:05] (current) |
||
---|---|---|---|
Line 1: | Line 1: | ||
+ | ======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==== | ||
+ | |||
+ | <code c Xconnections.h> | ||
+ | |||
+ | /** 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; | ||
+ | }; | ||
+ | </code> | ||
+ | | ||
+ | <code c Xconnections.c> | ||
+ | #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; | ||
+ | } | ||
+ | |||
+ | </code> | ||
+ | |||
+ | |||
+ | ====Example==== | ||
+ | <code c> | ||
+ | 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); | ||
+ | } | ||
+ | </code> | ||
+ | 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 |