8  Linking GUIs and other front-ends to R

Next: , Previous: , Up: Writing R Extensions   [Contents][Index]

There are a number of ways to build front-ends to R: we take this to mean a GUI or other application that has the ability to submit commands to R and perhaps to receive results back (not necessarily in a text format). There are other routes besides those described here, for example the package Rserve (from CRAN, see also https://www.rforge.net/Rserve/) and connections to Java in JRI’ (part of the rJava package on CRAN).

Note that the APIs described in this chapter are only intended to be used in an alternative front-end: they are not part of the API made available for R packages and can be dangerous to use in a conventional package (although packages may contain alternative front-ends). Conversely some of the functions from the API (such as R_alloc) should not be used in front-ends.

8.1 Embedding R under Unix-alikes

R can be built as a shared library174 if configured with --enable-R-shlib. This shared library can be used to run R from alternative front-end programs. We will assume this has been done for the rest of this section. Also, it can be built as a static library if configured with --enable-R-static-lib, and that can be used in a very similar way (at least on Linux: on other platforms one needs to ensure that all the symbols exported by libR.a are linked into the front-end).

The command-line R front-end, R_HOME/bin/exec/R, is one such example, and the former GNOME (see package gnomeGUI on CRAN’s Archive’ area) and macOS consoles are others. The source for R_HOME/bin/exec/R is in file src/main/Rmain.c and is very simple

int Rf_initialize_R(int ac, char **av); /* in ../unix/system.c */
void Rf_mainloop();                     /* in main.c */

extern int R_running_as_main_program;   /* in ../unix/system.c */

int main(int ac, char **av)
{
    R_running_as_main_program = 1;
    Rf_initialize_R(ac, av);
    Rf_mainloop(); /* does not return */
    return 0;
}

indeed, misleadingly simple. Remember that R_HOME/bin/exec/R is run from a shell script R_HOME/bin/R which sets up the environment for the executable, and this is used for

  • Setting R_HOME and checking it is valid, as well as the path R_SHARE_DIR and R_DOC_DIR to the installed share and doc directory trees. Also setting R_ARCH if needed.
  • Setting LD_LIBRARY_PATH to include the directories used in linking R. This is recorded as the default setting of R_LD_LIBRARY_PATH in the shell script R_HOME/etcR_ARCH/ldpaths.
  • Processing some of the arguments, for example to run R under a debugger and to launch alternative front-ends to provide GUIs.

The first two of these can be achieved for your front-end by running it via R CMD. So, for example

R CMD /usr/local/lib/R/bin/exec/R
R CMD exec/R

will both work in a standard R installation. (R CMD looks first for executables in R_HOME/bin. These command-lines need modification if a sub-architecture is in use.) If you do not want to run your front-end in this way, you need to ensure that R_HOME is set and LD_LIBRARY_PATH is suitable. (The latter might well be, but modern Unix/Linux systems do not normally include /usr/local/lib (/usr/local/lib64 on some architectures), and R does look there for system components.)

The other senses in which this example is too simple are that all the internal defaults are used and that control is handed over to the R main loop. There are a number of small examples175 in the tests/Embedding directory. These make use of Rf_initEmbeddedR in src/main/Rembedded.c, and essentially use

#include <Rembedded.h>

int main(int ac, char **av)
{
    /* do some setup */
    Rf_initEmbeddedR(argc, argv);
    /* do some more setup */

    /* submit some code to R, which is done interactively via
        run_Rmainloop();

        A possible substitute for a pseudo-console is

        R_ReplDLLinit();
        while(R_ReplDLLdo1() > 0) {
        /* add user actions here if desired */
       }

     */
    Rf_endEmbeddedR(0);
    /* final tidying up after R is shutdown */
    return 0;
}

If you do not want to pass R arguments, you can fake an argv array, for example by

    char *argv[]= {"REmbeddedPostgres", "--silent"};
    Rf_initEmbeddedR(sizeof(argv)/sizeof(argv[0]), argv);

However, to make a GUI we usually do want to run run_Rmainloop after setting up various parts of R to talk to our GUI, and arranging for our GUI callbacks to be called during the R mainloop.

One issue to watch is that on some platforms Rf_initEmbeddedR and Rf_endEmbeddedR change the settings of the FPU (e.g. to allow errors to be trapped and to make use of extended precision registers).

The standard code sets up a session temporary directory in the usual way, unless R_TempDir is set to a non-NULL value before Rf_initEmbeddedR is called. In that case the value is assumed to contain an existing writable directory, and it is not cleaned up when R is shut down.

Rf_initEmbeddedR sets R to be in interactive mode: you can set R_Interactive (defined in Rinterface.h) subsequently to change this.

Note that R expects to be run with the locale category LC_NUMERIC’ set to its default value of C, and so should not be embedded into an application which changes that.

It is the user’s responsibility to attempt to initialize only once. To protect the R interpreter, Rf_initialize_R will exit the process if re-initialization is attempted.

8.1.1 Compiling against the R library

Suitable flags to compile and link against the R (shared or static) library can be found by

R CMD config --cppflags
R CMD config --ldflags

(These apply only to an uninstalled copy or a standard install.)

If R is installed, pkg-config is available and neither sub-architectures nor a macOS framework have been used, alternatives for a shared R library are

pkg-config --cflags libR
pkg-config --libs libR

and for a static R library

pkg-config --cflags libR
pkg-config --static --libs libR

(This may work for an installed OS framework if pkg-config is taught where to look for libR.pc: it is installed inside the framework.)

However, a more comprehensive way is to set up a Makefile to compile the front-end. Suppose file myfe.c is to be compiled to myfe. A suitable Makefile might be

## WARNING: does not work when ${R_HOME} contains spaces
include ${R_HOME}/etc${R_ARCH}/Makeconf
all: myfe

## The following is not needed, but avoids PIC flags.
myfe.o: myfe.c
        $(CC) $(ALL_CPPFLAGS) $(CFLAGS) -c myfe.c -o $@

## replace $(LIBR) $(LIBS) by $(STATIC_LIBR) if R was built with a static libR
myfe: myfe.o
        $(MAIN_LINK) -o $@ myfe.o $(LIBR) $(LIBS)

invoked as

R CMD make
R CMD myfe

Even though not recommended, ${R_HOME} may contain spaces. In that case, it cannot be passed as an argument to include in the makefile. Instead, one can instruct make using the -f option to include Makeconf, for example via recursive invocation of make, see Writing portable packages.

all:
      $(MAKE) -f "${R_HOME}/etc${R_ARCH}/Makeconf" -f Makefile.inner

Additional flags which $(MAIN_LINK) includes are, amongst others, those to select OpenMP and --export-dynamic for the GNU linker on some platforms. In principle $(LIBS) is not needed when using a shared R library as libR is linked against those libraries, but some platforms need the executable also linked against them.

8.1.2 Setting R callbacks

For Unix-alikes there is a public header file Rinterface.h that makes it possible to change the standard callbacks used by R in a documented way. This defines pointers (if R_INTERFACE_PTRS is defined)

extern void (*ptr_R_Suicide)(const char *);
extern void (*ptr_R_ShowMessage)(const char *);
extern int  (*ptr_R_ReadConsole)(const char *, unsigned char *, int, int);
extern void (*ptr_R_WriteConsole)(const char *, int);
extern void (*ptr_R_WriteConsoleEx)(const char *, int, int);
extern void (*ptr_R_ResetConsole)();
extern void (*ptr_R_FlushConsole)();
extern void (*ptr_R_ClearerrConsole)();
extern void (*ptr_R_Busy)(int);
extern void (*ptr_R_CleanUp)(SA_TYPE, int, int);
extern int  (*ptr_R_ShowFiles)(int, const char **, const char **,
                               const char *, Rboolean, const char *);
extern int  (*ptr_R_ChooseFile)(int, char *, int);
extern int  (*ptr_R_EditFile)(const char *);
extern void (*ptr_R_loadhistory)(SEXP, SEXP, SEXP, SEXP);
extern void (*ptr_R_savehistory)(SEXP, SEXP, SEXP, SEXP);
extern void (*ptr_R_addhistory)(SEXP, SEXP, SEXP, SEXP);
extern int  (*ptr_R_EditFiles)(int, const char **, const char **, const char *);
extern SEXP (*ptr_do_selectlist)(SEXP, SEXP, SEXP, SEXP);
extern SEXP (*ptr_do_dataentry)(SEXP, SEXP, SEXP, SEXP);
extern SEXP (*ptr_do_dataviewer)(SEXP, SEXP, SEXP, SEXP);
extern void (*ptr_R_ProcessEvents)();

which allow standard R callbacks to be redirected to your GUI. What these do is generally documented in the file src/unix/system.txt.

Function:void R_ShowMessage (char *message)

This should display the message, which may have multiple lines: it should be brought to the user’s attention immediately.

Function:void R_Busy (intwhich)

This function invokes actions (such as change of cursor) when R embarks on an extended computation (which=1) and when such a state terminates (which=0).

Function:int R_ReadConsole (const char *prompt, unsigned char *buf, intbuflen, inthist)
Function:void R_WriteConsole (const char *buf, intbuflen)
Function:void R_WriteConsoleEx (const char *buf, intbuflen, intotype)
Function:void R_ResetConsole ()
Function:void R_FlushConsole ()
Function:void R_ClearerrConsole ()

: These functions interact with a console.

`R_ReadConsole`{.code} prints the given prompt at the console and
then does a `fgets(3)`{.code}--like operation, writing up to
`buflen`{.variable .var} bytes into the buffer `buf`{.variable
.var}. The last of the bytes written should be `"\0"`{.sample
.samp}'. When there is enough space in the buffer to hold the full
input line including the line terminator, the line terminator should
be included. Otherwise, the rest of the line should be returned in
subsequent calls to `R_ReadConsole`{.code}. The last call should
return data terminated by the line terminator. If `hist`{.variable
.var} is non-zero, then the line should be added to any command
history which is being maintained. The return value is 0 if no input
is available and \>0 otherwise.

`R_WriteConsoleEx`{.code} writes the given buffer to the console,
`otype`{.variable .var} specifies the output type (regular output or
warning/error). Call to `R_WriteConsole(buf, buflen)`{.code} is
equivalent to `R_WriteConsoleEx(buf, buflen, 0)`{.code}. To ensure
backward compatibility of the callbacks,
`ptr_R_WriteConsoleEx`{.code} is used only if
`ptr_R_WriteConsole`{.code} is set to `NULL`{.code}. To ensure that
`stdout()`{.code} and `stderr()`{.code} connections point to the
console, set the corresponding files to `NULL`{.code} *via*


``` R
      R_Outputfile = NULL;
      R_Consolefile = NULL;
```

`R_ResetConsole`{.code} is called when the system is reset after an
error. `R_FlushConsole`{.code} is called to flush any pending output
to the system console. `R_ClearerrConsole`{.code} clears any errors
associated with reading from the console.

Function:int R_ShowFiles (intnfile, const char **file, const char **headers, const char *wtitle, Rbooleandel, const char *pager)

This function is used to display the contents of files.

Function:int R_ChooseFile (intnew, char *buf, intlen)

Choose a file and return its name in buf of length len. Return value is 0 for success, > 0 otherwise.

Function:int R_EditFile (const char *buf)

Send a file to an editor window.

Function:int R_EditFiles (intnfile, const char **file, const char **title, const char *editor)

Send nfile files to an editor, with titles possibly to be used for the editor window(s).

Function:SEXP R_loadhistory (SEXP, SEXP, SEXP, SEXP);
Function:SEXP R_savehistory (SEXP, SEXP, SEXP, SEXP);
Function:SEXP R_addhistory (SEXP, SEXP, SEXP, SEXP);

: .Internal functions for loadhistory, savehistory and timestamp.

If the console has no history mechanism these can be as simple as


``` c
SEXP R_loadhistory (SEXP call, SEXP op, SEXP args, SEXP env)
{
    errorcall(call, "loadhistory is not implemented");
    return R_NilValue;
}
SEXP R_savehistory (SEXP call, SEXP op , SEXP args, SEXP env)
{
    errorcall(call, "savehistory is not implemented");
    return R_NilValue;
}
SEXP R_addhistory (SEXP call, SEXP op , SEXP args, SEXP env)
{
    return R_NilValue;
}
```

The `R_addhistory`{.code} function should return silently if no
history mechanism is present, as a user may be calling
`timestamp`{.code} purely to write the time stamp to the console.
Function:void R_Suicide (const char *message)

This should abort R as rapidly as possible, displaying the message. A possible implementation is

void R_Suicide (const char *message)
{
    char  pp[1024];
    snprintf(pp, 1024, "Fatal error: %s\n", message);
    R_ShowMessage(pp);
    R_CleanUp(SA_SUICIDE, 2, 0);
}
Function:void R_CleanUp (SA_TYPEsaveact, intstatus, intRunLast)

This function invokes any actions which occur at system termination. It needs to be quite complex:

#include <Rinterface.h>
#include <Rembedded.h>    /* for Rf_KillAllDevices */

void R_CleanUp (SA_TYPE saveact, int status, int RunLast)
{
    if(saveact == SA_DEFAULT) saveact = SaveAction;
    if(saveact == SA_SAVEASK) {
       /* ask what to do and set saveact */
    }
    switch (saveact) {
    case SA_SAVE:
        if(runLast) R_dot_Last();
        if(R_DirtyImage) R_SaveGlobalEnv();
        /* save the console history in R_HistoryFile */
        break;
    case SA_NOSAVE:
        if(runLast) R_dot_Last();
        break;
    case SA_SUICIDE:
    default:
        break;
    }

    R_RunExitFinalizers();
    /* clean up after the editor e.g. CleanEd() */

    R_CleanTempDir();

    /* close all the graphics devices */
    if(saveact != SA_SUICIDE) Rf_KillAllDevices();
    fpu_setup(FALSE);

    exit(status);
}

These callbacks should never be changed in a running R session (and hence cannot be called from an extension package).

Function:SEXP R_dataentry (SEXP, SEXP, SEXP, SEXP);
Function:SEXP R_dataviewer (SEXP, SEXP, SEXP, SEXP);
Function:SEXP R_selectlist (SEXP, SEXP, SEXP, SEXP);

: .External functions for dataentry (and edit on matrices and data frames), View and select.list. These can be changed if they are not currently in use.

8.1.3 Registering symbols

An application embedding R needs a different way of registering symbols because it is not a dynamic library loaded by R as would be the case with a package. Therefore R reserves a special DllInfo entry for the embedding application such that it can register symbols to be used with .C, .Call etc. This entry can be obtained by calling getEmbeddingDllInfo, so a typical use is

DllInfo *info = R_getEmbeddingDllInfo();
R_registerRoutines(info, cMethods, callMethods, NULL, NULL);

The native routines defined by cMethods and callMethods should be present in the embedding application. See Registering native routines for details on registering symbols in general.

8.1.4 Meshing event loops

One of the most difficult issues in interfacing R to a front-end is the handling of event loops, at least if a single thread is used. R uses events and timers for

  • Running X11 windows such as the graphics device and data editor, and interacting with them (e.g., using locator()).
  • Supporting Tcl/Tk events for the tcltk package (for at least the X11 version of Tk).
  • Preparing input.
  • Timing operations, for example for profiling R code and Sys.sleep().
  • Interrupts, where permitted.

Specifically, the Unix-alike command-line version of R runs separate event loops for

  • Preparing input at the console command-line, in file src/unix/sys-unix.c.
  • Waiting for a response from a socket in the internal functions for direct socket access in file src/modules/internet/Rsock.c and for the interface to libcurl.
  • Mouse and window events when displaying the X11-based dataentry window, in file src/modules/X11/dataentry.c. This is regarded as modal, and no other events are serviced whilst it is active.

There is a protocol for adding event handlers to the first two types of event loops, using types and functions declared in the header R_ext/eventloop.h and described in comments in file src/unix/sys-std.c. It is possible to add (or remove) an input handler for events on a particular file descriptor, or to set a polling interval (via R_wait_usec) and a function to be called periodically via R_PolledEvents: the polling mechanism is used by the tcltk package. Input handlers are managed with addInputHandler,getInputHandler, and removeInputHandler. The handlers are held in a linked list R_InputHandlers.

It is not intended that these facilities are used by packages, but if they are needed exceptionally, the package should ensure that it cleans up and removes its handlers when its namespace is unloaded. Note that the header sys/select.h is needed176: users should check this is available and define HAVE_SYS_SELECT_H before including R_ext/eventloop.h. (It is often the case that another header will include sys/select.h before eventloop.h is processed, but this should not be relied on.)

An alternative front-end needs both to make provision for other R events whilst waiting for input, and to ensure that it is not frozen out during events of the second type. The ability to add a polled handler as R_timeout_handler is used by the tcltk package.

8.1.5 Threading issues

Embedded R is designed to be run in the main thread, and all the testing is done in that context. There is a potential issue with the stack-checking mechanism where threads are involved. This uses two variables declared in Rinterface.h (if CSTACK_DEFNS is defined) as

extern uintptr_t R_CStackLimit; /* C stack limit */
extern uintptr_t R_CStackStart; /* Initial stack address */

Note that uintptr_t is an optional C99 type for which a substitute is defined in R, so your code needs to define HAVE_UINTPTR_T appropriately. To do so, test if the type is defined in C header stdint.h or C++ header cstdint and if so include the header and define HAVE_UINTPTR_T before including Rinterface.h. (For C code one can simply include Rconfig.h, possibly via R.h, and for C++11 code Rinterface.h will include the header cstdint.)

These will be set177 when Rf_initialize_R is called, to values appropriate to the main thread. Stack-checking can be disabled by setting R_CStackLimit = (uintptr_t)-1 immediately after Rf_initialize_R is called, but it is better to if possible set appropriate values. (What these are and how to determine them are OS-specific, and the stack size limit may differ for secondary threads. If you have a choice of stack size, at least 10Mb is recommended.)

You may also want to consider how signals are handled: R sets signal handlers for several signals, including SIGINT, SIGSEGV, SIGPIPE, SIGUSR1 and SIGUSR2, but these can all be suppressed by setting the variable R_SignalHandlers (declared in Rinterface.h) to 0.

Note that these variables must not be changed by an R package: a package should not call R internals which makes use of the stack-checking mechanism on a secondary thread.

8.2 Embedding R under Windows

This section is only about x86_64’ Windows.

All Windows interfaces to R call entry points in the DLL R.dll, directly or indirectly. Simpler applications may find it easier to use the indirect route via (D)COM.

8.2.1 Using (D)COM

(D)COM is a standard Windows mechanism used for communication between Windows applications. One application (here R) is run as COM server which offers services to clients, here the front-end calling application. The services are described in a ‘Type Library’ and are (more or less) language-independent, so the calling application can be written in C or C++ or Visual Basic or Perl or Python and so on. The ‘D’ in (D)COM refers to ‘distributed’, as the client and server can be running on different machines.

The basic R distribution is not a (D)COM server, but two addons are currently available that interface directly with R and provide a (D)COM server:

  • There is a (D)COM server called StatConnector written by Thomas Baier available via https://www.autstat.com/, which works with R packages to support transfer of data to and from R and remote execution of R commands, as well as embedding of an R graphics window.

    Recent versions have usage restrictions.

8.2.2 Calling R.dll directly

The R DLL is mainly written in C and has _cdecl entry points. Calling it directly will be tricky except from C code (or C++ with a little care).

There is a version of the Unix-alike interface calling

int Rf_initEmbeddedR(int ac, char **av);
void Rf_endEmbeddedR(int fatal);

which is an entry point in R.dll. Examples of its use (and a suitable Makefile.win) can be found in the tests/Embedding directory of the sources. You may need to ensure that R_HOME/bin is in your PATH so the R DLLs are found.

Examples of calling R.dll directly are provided in the directory src/gnuwin32/front-ends, including a simple command-line front end rtest.c whose code is

#define Win32
#include <windows.h>
#include <stdio.h>
#include <Rversion.h>
#define LibExtern __declspec(dllimport) extern
#include <Rembedded.h>
#include <R_ext/RStartup.h>
/* for askok and askyesnocancel */
#include <graphapp.h>

/* for signal-handling code */
#include <psignal.h>

/* simple input, simple output */

/* This version blocks all events: a real one needs to call ProcessEvents
   frequently. See rterm.c and ../system.c for one approach using
   a separate thread for input.
*/
int myReadConsole(const char *prompt, unsigned char *buf, int len, int addtohistory)
{
    fputs(prompt, stdout);
    fflush(stdout);
    if(fgets((char *)buf, len, stdin)) return 1; else return 0;
}

void myWriteConsole(const char *buf, int len)
{
    printf("%s", buf);
}

void myCallBack(void)
{
    /* called during i/o, eval, graphics in ProcessEvents */
}

void myBusy(int which)
{
    /* set a busy cursor ... if which = 1, unset if which = 0 */
}

static void my_onintr(int sig) { UserBreak = 1; }

int main (int argc, char **argv)
{
    structRstart rp;
    Rstart Rp = &rp;
    char Rversion[25], *RHome, *RUser;

    sprintf(Rversion, "%s.%s", R_MAJOR, R_MINOR);
    if(strcmp(getDLLVersion(), Rversion) != 0) {
        fprintf(stderr, "Error: R.DLL version does not match\n");
        exit(1);
    }

    R_setStartTime();
    R_DefParamsEx(Rp, RSTART_VERSION);
    if((RHome = get_R_HOME()) == NULL) {
        fprintf(stderr, "R_HOME must be set in the environment or Registry\n");
        exit(1);
    }
    Rp->rhome = RHome;
    RUser = getRUser();
    Rp->home = RUser;
    Rp->CharacterMode = LinkDLL;
    Rp->EmitEmbeddedUTF8 = FALSE;
    Rp->ReadConsole = myReadConsole;
    Rp->WriteConsole = myWriteConsole;
    Rp->CallBack = myCallBack;
    Rp->ShowMessage = askok;
    Rp->YesNoCancel = askyesnocancel;
    Rp->Busy = myBusy;

    Rp->R_Quiet = TRUE;        /* Default is FALSE */
    Rp->R_Interactive = FALSE; /* Default is TRUE */
    Rp->RestoreAction = SA_RESTORE;
    Rp->SaveAction = SA_NOSAVE;
    R_SetParams(Rp);
    freeRUser(RUser);
    free_R_HOME(RHome);
    R_set_command_line_arguments(argc, argv);

    FlushConsoleInputBuffer(GetStdHandle(STD_INPUT_HANDLE));

    signal(SIGBREAK, my_onintr);
    GA_initapp(0, 0);
    readconsolecfg();
    setup_Rmainloop();
#ifdef SIMPLE_CASE
    run_Rmainloop();
#else
    R_ReplDLLinit();
    while(R_ReplDLLdo1() > 0) {
/* add user actions here if desired */
    }
/* only get here on EOF (not q()) */
#endif
    Rf_endEmbeddedR(0);
    return 0;
}

The ideas are

  • Check that the front-end and the linked R.dll match – other front-ends may allow a looser match.
  • Find and set the R home directory and the user’s home directory. The former may be available from the Windows Registry: it will be in HKEY_LOCAL_MACHINE\Software\R-core\R\InstallPath from an administrative install and HKEY_CURRENT_USER\Software\R-core\R\InstallPath otherwise, if selected during installation (as it is by default).
  • Define startup conditions and callbacks via the Rstart structure. R_DefParams sets the defaults, and R_SetParams sets updated values. R_DefParamsEx takes an extra argument, the version number of the Rstart structure provided (RSTART_VERSION refers to the current version) and returns a non-zero status when that version is not supported by R.
  • Record the command-line arguments used by R_set_command_line_arguments for use by the R function commandArgs().
  • Set up the signal handler and the basic user interface.
  • Run the main R loop, possibly with our actions intermeshed.
  • Arrange to clean up.

An underlying theme is the need to keep the GUI ‘alive’, and this has not been done in this example. The R callback R_ProcessEvents needs to be called frequently to ensure that Windows events in R windows are handled expeditiously. Conversely, R needs to allow the GUI code (which is running in the same process) to update itself as needed – two ways are provided to allow this:

  • R_ProcessEvents calls the callback registered by Rp->callback. A version of this is used to run package Tcl/Tk for tcltk under Windows, for the code is

    void R_ProcessEvents(void)
    {
        while (peekevent()) doevent(); /* Windows events for GraphApp */
        if (UserBreak) { UserBreak = FALSE; Rf_onintr(); }
        R_CallBackHook();
        if(R_tcldo) R_tcldo();
    }
  • The mainloop can be split up to allow the calling application to take some action after each line of input has been dealt with: see the alternative code below #ifdef SIMPLE_CASE.

It may be that no R GraphApp windows need to be considered, although these include pagers, the windows() graphics device, the R data and script editors and various popups such as choose.file() and select.list(). It would be possible to replace all of these, but it seems easier to allow GraphApp to handle most of them.

It is possible to run R in a GUI in a single thread (as RGui.exe shows) but it will normally be easier178 to use multiple threads.

Note that R’s own front ends use a stack size of 10Mb, whereas MinGW executables default to 2Mb, and Visual C++ ones to 1Mb. The latter stack sizes are too small for a number of R applications, so general-purpose front-ends should use a larger stack size.

Applications embedding R 4.2.0 and newer should use UCRT as the C runtime and opt in for UTF-8 as the active code page in their manifest, as all frontends shipped with R do. This will allow the embedded R to use UTF-8 as its native encoding on recent Windows systems.

8.2.3 Finding R_HOME

Both applications which embed R and those which use a system call to invoke R (as Rscript.exe, Rterm.exe or R.exe) need to be able to find the R bin directory. The simplest way to do so is the ask the user to set an environment variable R_HOME and use that, but naive users may be flummoxed as to how to do so or what value to use.

The R for Windows installers have for a long time allowed the value of R_HOME to be recorded in the Windows Registry: this is optional but selected by default. Where it is recorded has changed over the years to allow for multiple versions of R to be installed at once, and to allow 32- and 64-bit versions of R to be installed on the same machine. Only 64-bit versions are supported since R 4.2.

The basic Registry location is Software\R-core\R. For an administrative install this is under HKEY_LOCAL_MACHINE and on a 64-bit OS HKEY_LOCAL_MACHINE\Software\R-core\R is by default redirected for a 32-bit application, so a 32-bit application will see the information for the last 32-bit install, and a 64-bit application that for the last 64-bit install. For a personal install, the information is under HKEY_CURRENT_USER\Software\R-core\R which is seen by both 32-bit and 64-bit applications and so records the last install of either architecture. To circumvent this, with Intel builds there are locations Software\R-core\R32 and Software\R-core\R64 which always refer to one architecture.

When R is installed and recording is not disabled then two string values are written at that location for keys InstallPath and Current Version, and these keys are removed when R is uninstalled. To allow information about other installed versions to be retained, there is also a key named something like 3.0.0 or 3.0.0 patched or 3.1.0 Pre-release with a value for InstallPath.

So a comprehensive algorithm to search for R_HOME is something like

  • Decide which of personal or administrative installs should have precedence. There are arguments both ways: we find that with roaming profiles that HKEY_CURRENT_USER\Software often gets reverted to an earlier version. Do the following for one or both of HKEY_CURRENT_USER and HKEY_LOCAL_MACHINE.
  • If the desired architecture is known, look in Software\R-core\R32 or Software\R-core\R64, and if that does not exist or the architecture is immaterial, in Software\R-core\R.
  • If key InstallPath exists then this is R_HOME (recorded using backslashes). If it does not, look for version-specific keys like 2.11.0 alpha, pick the latest (which is of itself a complicated algorithm as 2.11.0 patched > 2.11.0 > 2.11.0 alpha > 2.8.1) and use its value for InstallPath.