Ответ 1
Дирк, вероятно, прав, что RInside облегчает жизнь. Но для умирающих... Сущность исходит из Написание R Расширений разделов 8.1 и 8.2, а также из примеров, распространенных с R. Материал ниже охватывает построение и оценку вызова; обращение к возвращаемому значению - это другая (и в некотором смысле более легкая) тема.
Настройка
Предположим, что платформа Linux/Mac. Прежде всего, необходимо, чтобы R был скомпилирован, чтобы разрешить связывание либо с общей, либо с статической библиотекой R. Я работаю с SVN-копией источника R в каталоге ~/src/R-devel
. Я переключусь на другой каталог, назовите его ~/bin/R-devel
, а затем
~/src/R-devel/configure --enable-R-shlib
make -j
это генерирует ~/bin/R-devel/lib/libR.so
; возможно, какое распространение у вас уже есть? Флаг -j
запускается параллельно, что значительно ускоряет сборку.
Примеры для вложения находятся в ~/src/R-devel/tests/Embedding
, и их можно сделать с помощью cd ~/bin/R-devel/tests/Embedding && make
. Очевидно, исходный код для этих примеров чрезвычайно поучителен.
код
Чтобы проиллюстрировать, создайте файл embed.cpp
. Начните с включения заголовка, который определяет структуры данных R, и интерфейс внедрения R; они расположены в bin/R-devel/include
и служат в качестве первичной документации. У нас также есть прототип функции, которая будет выполнять всю работу
#include <Rembedded.h>
#include <Rdefines.h>
static void doSplinesExample();
Рабочий поток должен запустить R, выполнить работу и закончить R:
int
main(int argc, char *argv[])
{
Rf_initEmbeddedR(argc, argv);
doSplinesExample();
Rf_endEmbeddedR(0);
return 0;
}
Примеры под Embedding
включают в себя тот, который вызывает library(splines)
, устанавливает именованный параметр, затем запускает функцию example("ns")
. Здесь процедура, которая делает это
static void
doSplinesExample()
{
SEXP e, result;
int errorOccurred;
// create and evaluate 'library(splines)'
PROTECT(e = lang2(install("library"), mkString("splines")));
R_tryEval(e, R_GlobalEnv, &errorOccurred);
if (errorOccurred) {
// handle error
}
UNPROTECT(1);
// 'options(FALSE)' ...
PROTECT(e = lang2(install("options"), ScalarLogical(0)));
// ... modified to 'options(example.ask=FALSE)' (this is obscure)
SET_TAG(CDR(e), install("example.ask"));
R_tryEval(e, R_GlobalEnv, NULL);
UNPROTECT(1);
// 'example("ns")'
PROTECT(e = lang2(install("example"), mkString("ns")));
R_tryEval(e, R_GlobalEnv, &errorOccurred);
UNPROTECT(1);
}
Скомпилировать и запустить
Теперь мы готовы собрать все вместе. Компилятор должен знать, где находятся заголовки и библиотеки
g++ -I/home/user/bin/R-devel/include -L/home/user/bin/R-devel/lib -lR embed.cpp
Скомпилированное приложение должно запускаться в правильной среде, например, с правильной настройкой R_HOME; это может быть легко организовано (очевидно, развернутое приложение захочет использовать более широкий подход) с помощью
R CMD ./a.out
В зависимости от ваших амбиций некоторые части раздела 8 расширений Writing R не актуальны, например, обратные вызовы необходимы для реализации GUI поверх R, но не для оценки простых фрагментов кода.
Некоторые детали
Выполнение этого в деталях... SEXP (S-expression) - это структура данных, фундаментальная для представления R базовых типов (целые, логические, языковые вызовы и т.д.). Строка
PROTECT(e = lang2(install("library"), mkString("splines")));
делает символ library
и строку "splines"
, и помещает их в конструкцию языка, состоящую из двух элементов. Это создает неоценимый языковой объект, приблизительно эквивалентный quote(library("splines"))
в R. lang2
возвращает SEXP, который был выделен из пула памяти R, и он должен быть PROTECT
из коллекции мусора. PROTECT
добавляет адрес, на который указывает e
, в стек защиты, когда память больше не нуждается в защите, адрес выставляется из стека (с UNPROTECT(1)
, несколько строк вниз). Строка
R_tryEval(e, R_GlobalEnv, &errorOccurred);
пытается оценить e
в глобальной среде R. errorOccurred
устанавливается на не-0, если возникает ошибка. R_tryEval
возвращает SEXP, представляющий результат функции, но мы игнорируем его здесь. Поскольку нам больше не нужна память, выделенная для хранения library("splines")
, мы говорим R, что она больше не PROTECT'ed.
Следующий фрагмент кода аналогичен, оценивая options(example.ask=FALSE)
, но построение вызова сложнее. S-выражение, созданное lang2
, представляет собой список пар, концептуально с node, левым указателем (CAR) и правым указателем (CDR). Левый указатель e
указывает на символ options
. Правильный указатель e
указывает на другой node в списке пар, левым указателем которого является FALSE
(правый указатель R_NilValue
, указывающий конец выражения языка). Каждый node списка пар может иметь TAG
, смысл которого зависит от роли, которую играет node. Здесь мы добавляем имя аргумента.
SET_TAG(CDR(e), install("example.ask"));
Следующая строка вычисляет выражение, которое мы построили (options(example.ask=FALSE)
), используя NULL
, чтобы указать, что мы проигнорируем успех или неудачу оценки функции. Другой способ построения и оценки этого вызова проиллюстрирован в R-devel/tests/Embedding/RParseEval.c
, адаптированном здесь как
PROTECT(tmp = mkString("options(example.ask=FALSE)"));
PROTECT(e = R_ParseVector(tmp, 1, &status, R_NilValue));
R_tryEval(VECTOR_ELT(e, 0), R_GlobalEnv, NULL);
UNPROTECT(2);
но это не похоже на хорошую стратегию в целом, поскольку она смешивает код R и C и не позволяет использовать вычисляемые аргументы в R-функциях. Вместо этого пишите и управляйте R-кодом в R (например, создавая пакет с функциями, которые выполняют сложную серию манипуляций с R), которые использует ваш код C.
Последний блок кода выше строит и оценивает example("ns")
. Rf_tryEval
возвращает результат вызова функции, поэтому
SEXP result;
PROTECT(result = Rf_tryEval(e, R_GlobalEnv, &errorOccurred));
// ...
UNPROTECT(1);
захватит это для последующей обработки.