Сохранение объектов C в R

В руководстве R Extensions я нашел информацию о доступе к объектам R из C. В моей ситуации, однако, я работаю с другим C-кодом, который имеет специализированную структуру данных (назовите это Foo). Моя цель состоит в том, чтобы иметь R-функции, которые:

  • инициализировать объект Foo. Я не хочу хранить одну из них в терминах R-списков или матриц.
  • обновить объект Foo. Я не хочу воссоздавать объект Foo, чтобы сделать это, а изменить его на месте. Кроме того, эта функция может возвращать информацию о том, было ли обновление успешным.
  • удалить объект Foo из памяти.

Другими словами, я хотел бы сохранить объект C в среде R, используя функции R (поддерживаемые функциями C), чтобы создать его, изменить и удалить.

Заранее благодарим за любые советы.

Ответы

Ответ 1

Как уже упоминалось, идея заключается в использовании внешнего указателя. Это описано в руководстве "Написание R-расширений". Вот краткий пример.

Включить соответствующий заголовок R

#include <Rdefines.h>

Мы создадим объект, который может содержать C char * длиной не более 15

const int N_MAX=15;

Внешний указатель хотел бы получить финализатор, который будет вызываться, когда внешний указатель больше не будет представлен каким-либо объектом R. Мы играем в этом безопасно, проверяя, что адрес указателя действителен до освобождения (с помощью Free, поскольку мы будем выделять Calloc - это функции выделения памяти уровня C, которые сохраняются во всех вызовах на C, в отличие от R_alloc) и очистки указателя чтобы подтвердить, что он уже завершен.

static void
_finalizer(SEXP ext)
{
    if (NULL == R_ExternalPtrAddr(ext))
        return;
    Rprintf("finalizing\n");
    char *ptr = (char *) R_ExternalPtrAddr(ext);
    Free(ptr);
    R_ClearExternalPtr(ext);
}

Здесь наш конструктор. Требуется некоторая R 'информация', которую он будет носить с собой (не используется позже в этом примере), затем выделяет некоторую память для короткой строки. Мы строим внешний указатель с x и info (R_NilValue является "тегом", который по соглашению мы могли бы использовать для обозначения нашего объекта - mkString ( "MyCObject" ) или аналогичного). Мы связываем наш финализатор с внешним указателем. PROTECT/UNPROTECT должны защищать от сборщика мусора, вызванного вызовом R_RegisterCFinalizerEx. Сборщик мусора можно вызвать, когда R выделяет память; это трудно понять, когда это происходит (мы можем проследить поток кода), поэтому мы играем его безопасно и добавляем внешний указатель к PROTECT, когда мы его создаем.

SEXP
create(SEXP info)
{
    char *x = Calloc(N_MAX, char);
    snprintf(x, N_MAX, "my name is joe");
    SEXP ext = PROTECT(R_MakeExternalPtr(x, R_NilValue, info));
    R_RegisterCFinalizerEx(ext, _finalizer, TRUE);
    UNPROTECT(1);

    return ext;
}

Здесь получатель, просто ссылающийся на внешний адрес указателя и возвращающий его как символ R (1)

SEXP
get(SEXP ext)
{
    return mkString((char *) R_ExternalPtrAddr(ext));
}

и сеттер, который берет внешний указатель и символ (1), копируя представление C первого элемента str в наш объект. Мы возвращаем логический (1), но можем что-то вернуть.

SEXP
set(SEXP ext, SEXP str)
{
    char *x = (char *) R_ExternalPtrAddr(ext);
    snprintf(x, N_MAX, CHAR(STRING_ELT(str, 0)));
    return ScalarLogical(TRUE);
}

Если это файл tmp.c, мы скомпилируем с помощью

R CMD SHLIB tmp.c

или интегрируйте это в пакет как файл src/tmp.c и создайте пакет как обычно. Использовать:

> dyn.load("tmp.so")
> x <- .Call("create", list("info could be any R object", 1:5))
> .Call("get", x)
[1] "my name is joe"
> ## reference semantics!
> .Call("set", x, "i am sam i am")
[1] TRUE
> .Call("get", x)
[1] "i am sam i am"
> x <- NULL
> gc()
finalizing
         used (Mb) gc trigger (Mb) max used (Mb)
Ncells 339306 18.2     467875   25   407500 21.8
Vcells 202064  1.6     786432    6   380515  3.0

Вот второй пример с struct, содержащий int, который увеличивается.

#include <Rdefines.h>

struct Foo {
    int x;
};

static void
_finalizer(SEXP ext)
{
    struct Foo *ptr = (struct Foo*) R_ExternalPtrAddr(ext);
    Free(ptr);
}

SEXP
create()
{
    struct Foo *foo = Calloc(1, struct Foo);
    foo->x = 0;
    SEXP ext = PROTECT(R_MakeExternalPtr(foo, R_NilValue, R_NilValue));
    R_RegisterCFinalizerEx(ext, _finalizer, TRUE);
    UNPROTECT(1);

    return ext;
}

SEXP
incr(SEXP ext)
{
    struct Foo *foo = (struct Foo*) R_ExternalPtrAddr(ext);
    foo->x += 1;
    return ScalarInteger(foo->x);
}