Ответ 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);
}