8 Linking GUIs and other front-ends to R
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 library1 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).
1 In the parlance of macOS this is a dynamic library, and is the normal way to build R on that platform.
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)
{
= 1;
R_running_as_main_program (ac, av);
Rf_initialize_R(); /* does not return */
Rf_mainloopreturn 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 pathR_SHARE_DIR
andR_DOC_DIR
to the installedshare
anddoc
directory trees. Also settingR_ARCH
if needed. - Setting
LD_LIBRARY_PATH
to include the directories used in linking R. This is recorded as the default setting ofR_LD_LIBRARY_PATH
in the shell scriptR_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 examples2 in the tests/Embedding
directory. These make use of Rf_initEmbeddedR
in src/main/Rembedded.c
, and essentially use
2 but these are not part of the automated test procedures and so little tested.
#include <Rembedded.h>
int main(int ac, char **av)
{
/* do some setup */
(argc, argv);
Rf_initEmbeddedR/* 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 */
}
*/
(0);
Rf_endEmbeddedR/* 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"};
(sizeof(argv)/sizeof(argv[0]), argv); Rf_initEmbeddedR
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
-config --cflags libR
pkg-config --libs libR pkg
and for a static R library
-config --cflags libR
pkg-config --static --libs libR pkg
(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
{R_HOME}/etc${R_ARCH}/Makeconf
include $: myfe
all
## The following is not needed, but avoids PIC flags.
.o: myfe.c
myfe(CC) $(ALL_CPPFLAGS) $(CFLAGS) -c myfe.c -o $@
$
## replace $(LIBR) $(LIBS) by $(STATIC_LIBR) if R was build with a static libR
: myfe.o
myfe(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 (int
which
) ¶ -
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
, int buflen
, int hist
) ¶
Function:void R_WriteConsole (const char *buf
, int buflen
) ¶
Function:void R_WriteConsoleEx (const char *buf
, int buflen
, int otype
) ¶
Function:void R_ResetConsole () ¶
Function:void R_FlushConsole () ¶
Function:void R_ClearerrConsole () ¶
: These functions interact with a console.
`R_ReadConsole` prints the given prompt at the console and then does
a `fgets(3)`--like operation, writing up to `buflen`
bytes into the buffer `buf`. The last of the bytes
written should be `"\0"`. 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`. The last call should return data terminated by the
line terminator. If `hist` 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` writes the given buffer to the console,
`otype` specifies the output type (regular output or
warning/error). Call to `R_WriteConsole(buf, buflen)` is equivalent
to `R_WriteConsoleEx(buf, buflen, 0)`. To ensure backward
compatibility of the callbacks, `ptr_R_WriteConsoleEx` is used only
if `ptr_R_WriteConsole` is set to `NULL`. To ensure that `stdout()`
and `stderr()` connections point to the console, set the
corresponding files to `NULL` *via*
``` R
R_Outputfile = NULL;
R_Consolefile = NULL;
```
`R_ResetConsole` is called when the system is reset after an error.
`R_FlushConsole` is called to flush any pending output to the system
console. `R_ClearerrConsole` clears any errors associated with
reading from the console.
- Function:int R_ShowFiles (int
nfile
, 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 (int
new
, char *buf
, intlen
) ¶ -
Choose a file and return its name in
buf
of lengthlen
. 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 (int
nfile
, 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` function should return silently if no history
mechanism is present, as a user may be calling `timestamp` 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]; (pp, 1024, "Fatal error: %s\n", message); snprintf(pp); R_ShowMessage(SA_SUICIDE, 2, 0); R_CleanUp}
- Function:void R_CleanUp (SA_TYPE
saveact
, 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(); (FALSE); fpu_setup (status); exit}
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
*info = R_getEmbeddingDllInfo();
DllInfo 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 tolibcurl
. - 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 needed3: 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.)
3 At least according to POSIX 2004 and later. Earlier standards prescribed sys/time.h
: R_ext/eventloop.h
will include it if HAVE_SYS_TIME_H
is defined.
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 set4 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.)
4 at least on platforms where the values are available, that is having getrlimit
and on Linux or having sysctl
supporting KERN_USRSTACK
, including FreeBSD and macOS.
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)
{
(prompt, stdout);
fputs(stdout);
fflushif(fgets((char *)buf, len, stdin)) return 1; else return 0;
}
void myWriteConsole(const char *buf, int len)
{
("%s", buf);
printf}
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= &rp;
Rstart Rp char Rversion[25], *RHome, *RUser;
(Rversion, "%s.%s", R_MAJOR, R_MINOR);
sprintfif(strcmp(getDLLVersion(), Rversion) != 0) {
(stderr, "Error: R.DLL version does not match\n");
fprintf(1);
exit}
();
R_setStartTime(Rp, RSTART_VERSION);
R_DefParamsExif((RHome = get_R_HOME()) == NULL) {
(stderr, "R_HOME must be set in the environment or Registry\n");
fprintf(1);
exit}
->rhome = RHome;
Rp= getRUser();
RUser ->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;
Rp(Rp);
R_SetParams(RUser);
freeRUser(RHome);
free_R_HOME(argc, argv);
R_set_command_line_arguments
(GetStdHandle(STD_INPUT_HANDLE));
FlushConsoleInputBuffer
(SIGBREAK, my_onintr);
signal(0, 0);
GA_initapp();
readconsolecfg();
setup_Rmainloop#ifdef SIMPLE_CASE
();
run_Rmainloop#else
();
R_ReplDLLinitwhile(R_ReplDLLdo1() > 0) {
/* add user actions here if desired */
}
/* only get here on EOF (not q()) */
#endif
(0);
Rf_endEmbeddedRreturn 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 andHKEY_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, andR_SetParams
sets updated values.R_DefParamsEx
takes an extra argument, the version number of theRstart
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 functioncommandArgs()
. - 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 byRp->callback
. A version of this is used to run package Tcl/Tk for tcltk under Windows, for the code isvoid R_ProcessEvents(void) { while (peekevent()) doevent(); /* Windows events for GraphApp */ if (UserBreak) { UserBreak = FALSE; onintr(); } (); R_CallBackHookif(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 easier5 to use multiple threads.
5 An attempt to use only threads in the late 1990s failed to work correctly under Windows 95, the predominant version of Windows at that time.
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 ofHKEY_CURRENT_USER
andHKEY_LOCAL_MACHINE
. - If the desired architecture is known, look in
Software\R-core\R32
orSoftware\R-core\R64
, and if that does not exist or the architecture is immaterial, inSoftware\R-core\R
. - If key
InstallPath
exists then this isR_HOME
(recorded using backslashes). If it does not, look for version-specific keys like2.11.0 alpha
, pick the latest (which is of itself a complicated algorithm as2.11.0 patched > 2.11.0 > 2.11.0 alpha > 2.8.1
) and use its value forInstallPath
.
Footnotes