[Rcpp-devel] What is the best practice to expose a C structure from 3rd party library into R?

romain at r-enthusiasts.com romain at r-enthusiasts.com
Mon Jul 22 13:19:54 CEST 2013


Le 2013-07-22 10:12, Wush Wu a écrit :
> Hi all,
>
> I wrote a wrapper of hiredis, which is a minimalistic C client for 
> the
> Redis database. Its name is `Rhiredis` and is much faster than 
> rredis,
> an existed redis client of R. Please
> see http://rpubs.com/wush978/rhiredis [1] for details. 

Cool.

> Thanks for the Rcpp, it is much easier to wrap a C library into R and
> extend the features of R. 

Great. That's why we do it.

> However, I still want to know if there is a better approach. I tried
> three approaches. Here is my opinions.
>
> # Preliminary
>
> Given a 3rd party C library, it usually provides a C structure and
> functions of memory management. For example:
>
> ```c
>
> struct A {
>     int flag;
>     // ...
> };
>
> A* initA();
> void freeA(A* a);
> ```
>
> The structure `A` need to be stored into a R object and pass to
> another function.
> Also, the user might need to access `A.flag`. For simplicity, there 
> is
> only one field in this example. However, there might be a number of
> fields in practice.
>
> # XPtr
>
>  Thanks for the user guide of Rcpp modules , this is the first
> approach I tried.
>
> We could expose `A` into R as follow:
>
> ```cpp
> Rcpp::XPtr<A, freeA> a(initA());
> ```
>
> However, we should write helper function to accessing `A.flag` for
> every fields.

Yes. And at the R level you deal with an external pointer. One usually 
then have to maintain what they exactly mean in R.

> # RCPP_MODULE
>
> The guids of Rcpp modules also provides another approach:
>
> ```cpp
>
> RCPP_EXPOSED_CLASS(A)
>
> RCPP_MODULE(A) {
>   class_<A>("A")
>   .field("flag", &A::flag)
>   ;
>  }
>
> //@export
> //[[Rcpp::export]]
> SEXP init() {
>   BEGIN_RCPP
>   return wrap(*initA());
>   END_RCPP
> }
> ```
>
> This will produce a S4 class named `A` in R which stores C structure
> `A`.
>
> However, it also produces memory leak because no `freeA` is called.
>
> Adding `.finalizer(freeA)` in `RCPP_MODULE` will cause an error of
> freeing memory twice.

Gotta look into why this happens.

> # Embed `A` into C++ class and expose the class with RCPP_MODULE
>
> This approach is implemented in `Rhiredis`.
>
> Finally, I still need to write helper function to expose the field of
> `A`. But the user could access these flag in R with operator `$`.
>
> Note that I still need a function to extract the pointer of `A` from
> exposed S4 object:
>
> ```cpp
>
> template<class T>
> T* extract_ptr(SEXP s) {
>  Rcpp::S4 s4(s);
>  Rcpp::Environment env(s4);
>  Rcpp::XPtr<T> xptr(env.get(".pointer"));
>  return static_cast<T*>(R_ExternalPtrAddr(xptr));
> }
> ```
>
> Please give me some suggestion if you know a better or a different
> approach.

I would essentially do what you do: use RCPP_MODULE to expose a C++ 
class so that the C++ class manages scoping : constructor, destructor, 
etc ...

class A_cpp {
public:
     A_cpp( ) : obj( initA() ){}
     ~A_cpp(){ freeA(obj); obj = NULL ; }

     int get_flag(){ return obj->flag ; }
     void set_flag( int x ){ obj->flag = x ; }

private:
     A* obj ;
} ;

RCPP_MODULE(Whatever){
     class_<A_cpp>( "A" )
         .constructor()
         .property( "flag", &A_cpp::get_flag, &A_cpp::set_flag )
     ;
}

Then in R:

a <- new( A )
a$flag
a$flag <- 12L
a$flag

I would probably do some tricks with macros to avoid writing boiler 
plate get_ and set_, something like this :

#define GET_SET(_type_,_name_)                  \
_type_ get_##_name_(){ return obj->_name_ ; }   \
void set_##_name_(_type_ x){ obj->_name_ = x ; }

so that you'd use:

GET_SET(int,flag)
...




With the devel version of Rcpp, you can use as to get hold of a 
reference (or a const reference) of your object from the SEXP:

int get_flag( SEXP s4obj ){
     A_cpp& obj = as<A_cpp&>( s4obj ) ;
     return obj.get_flag() ;
}

But you could also expose this simply as :

int get_flag( A_cpp& obj ){
     return obj.get_flag() ;
}

To have access to this "as", you have to declare some class traits, but 
the RCPP_EXPOSED_CLASS can help you with this.
Here is a working .cpp file that demonstrate all this.

#include <Rcpp.h>
using namespace Rcpp ;

struct A{
     int flag ;
};

A* initA(){return new A() ; }
void freeA(A* a){ delete a; }

#define GET_SET(_type_,_name_)                  \
_type_ get_##_name_(){ return obj->_name_ ; }   \
void set_##_name_(_type_ x){ obj->_name_ = x ; }

RCPP_EXPOSED_CLASS(A_cpp);
class A_cpp {
public:
     A_cpp( ) : obj( initA() ){}
     ~A_cpp(){ freeA(obj); obj = NULL ; }

     GET_SET(int,flag)

private:
     A* obj ;
} ;

int get_flag( SEXP s4obj ){
     A_cpp& obj = as<A_cpp&>( s4obj ) ;
     return obj.get_flag() ;
}

RCPP_MODULE(Whatever){
     class_<A_cpp>( "A" )
         .constructor()
         .property( "flag", &A_cpp::get_flag, &A_cpp::set_flag )
     ;
     function( "get_flag", &get_flag ) ;

}


/*** R

a <- new( A )
a$flag
a$flag <- 12L
a$flag
get_flag( a )

***/

I hope this clarifies a few things, or at least puts you on a good 
track.

Let us know.

Romain



> Thanks.
>
> Wush
>
> Links:
> ------
> [1] http://rpubs.com/wush978/rhiredis


More information about the Rcpp-devel mailing list