Skip to content

Commit 561ddc5

Browse files
wrap returned module objects as the class of the library that returned them (#1532)
* wrap returned module objects as the class of the library that returned them * Microedit --------- Co-authored-by: Dirk Eddelbuettel <edd@debian.org>
1 parent a2467cc commit 561ddc5

14 files changed

Lines changed: 187 additions & 1 deletion

File tree

‎ChangeLog‎

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,23 @@
6666
* inst/tinytest/cpp/Module.cpp (Module_scope_is_cleared): New
6767
* inst/tinytest/test_module.R: Test loading the failing module
6868

69+
* inst/include/Rcpp/Module.h (internal::module_class_pointer): New
70+
per-library record of the class_<Class> registered for a C++ type
71+
* inst/include/Rcpp/Module.h (internal::make_new_object): Pass it to
72+
the new cpp_object_maker_by_class(), so that objects returned from C++
73+
are wrapped as the module class of the library that returned them
74+
rather than as another library's class of the same name (#1531)
75+
* inst/include/Rcpp/module/class.h (class_::get_instance): Record the
76+
class
77+
* R/Module.R (cpp_object_maker_by_class): New, looking the class up by
78+
its pointer and creating the object through the class's generator
79+
* R/Module.R (Module): Register classes by pointer as well
80+
* R/zzz.R: Add .classes_by_pointer
81+
* inst/tinytest/testRcppCollisionA: New test package
82+
* inst/tinytest/testRcppCollisionB: Idem
83+
* inst/tinytest/test_module_class_collision.R: Test objects returned
84+
by both
85+
6986
2026-10-23 Dirk Eddelbuettel <edd@debian.org>
7087

7188
* DESCRIPTION (Version, Date): Roll micro version and date

‎R/Module.R‎

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -161,6 +161,17 @@ cpp_object_maker <- function(typeid, pointer){
161161
new( Class, .object_pointer = pointer )
162162
}
163163

164+
# called by make_new_object() with the module class that the calling library
165+
# registered for this C++ type, since other libraries' unrelated classes of
166+
# the same name share its typeid; the object is created through that class's
167+
# generator, as new() would look the reference class up by name
168+
cpp_object_maker_by_class <- function(typeid, pointer, class_pointer){
169+
Class <- .classes_by_pointer[[ externalptr_address(class_pointer) ]]
170+
if( is.null( Class ) )
171+
return( cpp_object_maker( typeid, pointer ) )
172+
Class@generator$new( .object_pointer = pointer )
173+
}
174+
164175
Module <- function( module, PACKAGE = methods::getPackageName(where), where = topenv(parent.frame()), mustStart = FALSE ) {
165176
if (inherits(module, "DLLInfo") && missing(mustStart)) mustStart <- TRUE # #nocov
166177
if (inherits(module, "Module")) {
@@ -287,6 +298,7 @@ Module <- function( module, PACKAGE = methods::getPackageName(where), where = to
287298
# rather than rebuilding it (and all of its method/field objects)
288299
CLASS@generator <- generators[[ clname ]]
289300
.classes_map[[ CLASS@typeid ]] <- storage[[ demangled_name ]] <- CLASS
301+
.classes_by_pointer[[ externalptr_address( CLASS@pointer ) ]] <- CLASS
290302

291303
# exposing enums values as CLASS.VALUE
292304
# (should really be CLASS$value but I don't know how to do it)

‎R/zzz.R‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
.dummyInstancePointer <- new.env() # just something permanent
1919

2020
.classes_map <- new.env()
21+
.classes_by_pointer <- new.env()
2122

2223
.onLoad <- function(libname, pkgname){
2324
new_dummyObject(.dummyInstancePointer) # nocov start

‎inst/include/Rcpp/Module.h‎

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -73,10 +73,33 @@ namespace Rcpp{
7373
} ;
7474

7575
namespace internal {
76+
77+
// the class_<Class> this shared library registered in a module, if
78+
// any. Unrelated classes of the same name in other libraries share
79+
// the typeid name, so make_new_object() passes this on for R to pick
80+
// the right module class; hidden visibility keeps one per library,
81+
// where the dynamic linker would otherwise share it between them
82+
template <typename Class>
83+
inline attribute_hidden void*& module_class_pointer(){
84+
static void* pointer = 0 ;
85+
return pointer ;
86+
}
87+
7688
template <typename Class>
7789
SEXP make_new_object( Class* ptr ){
7890
Rcpp::XPtr<Class> xp( ptr, true ) ;
79-
Function maker = Environment::Rcpp_namespace()[ "cpp_object_maker"] ;
91+
Environment rcpp = Environment::Rcpp_namespace() ;
92+
93+
// older versions of Rcpp only have cpp_object_maker()
94+
void* class_pointer = module_class_pointer<Class>() ;
95+
SEXP maker_by_class = rcpp.get( "cpp_object_maker_by_class" ) ;
96+
if( class_pointer != 0 && maker_by_class != R_NilValue ){
97+
Shield<SEXP> class_xp( R_MakeExternalPtr( class_pointer, R_NilValue, R_NilValue ) ) ;
98+
Function maker( maker_by_class ) ;
99+
return maker( typeid(Class).name(), xp, class_xp ) ;
100+
}
101+
102+
Function maker = rcpp[ "cpp_object_maker" ] ;
80103
return maker( typeid(Class).name() , xp ) ;
81104
}
82105
}

‎inst/include/Rcpp/module/class.h‎

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,10 @@
8989
RCPP_DEBUG_MODULE_2( "class_<%s>::get_instance(). [freshly created] class_pointer = class_pointer\n", DEMANGLE(Class), class_pointer ) ;
9090

9191
}
92+
93+
// lets objects returned from C++ find this module class
94+
internal::module_class_pointer<Class>() = static_cast<class_Base*>( class_pointer ) ;
95+
9296
return class_pointer ;
9397
}
9498

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
Package: testRcppCollisionA
2+
Type: Package
3+
Title: Module Class Name Collision Test A
4+
Version: 0.1
5+
Author: Kevin Ushey
6+
Maintainer: Kevin Ushey <kevinushey@gmail.com>
7+
Description: Exposes a module class whose name is also used by an unrelated
8+
class in package testRcppCollisionB, for the Rcpp tests.
9+
License: GPL (>= 2)
10+
Imports: methods, Rcpp
11+
LinkingTo: Rcpp
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
useDynLib(testRcppCollisionA)
2+
import(methods, Rcpp)
3+
export(Collision, make_collision)
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
loadModule("collision_module", TRUE)
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
#include <Rcpp.h>
2+
3+
using namespace Rcpp;
4+
5+
// unrelated to testRcppCollisionB's Collision, but of the same name, and so
6+
// with the same typeid name
7+
class Collision {
8+
public:
9+
Collision(double x_) : x(x_) {}
10+
double get() { return x; }
11+
double x;
12+
};
13+
14+
RCPP_EXPOSED_CLASS_NODECL(Collision)
15+
16+
Collision make_collision(double x) {
17+
return Collision(x);
18+
}
19+
20+
RCPP_MODULE(collision_module) {
21+
class_<Collision>("Collision")
22+
.constructor<double>()
23+
.method("get", &Collision::get)
24+
;
25+
26+
function("make_collision", &make_collision);
27+
}
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
Package: testRcppCollisionB
2+
Type: Package
3+
Title: Module Class Name Collision Test B
4+
Version: 0.1
5+
Author: Kevin Ushey
6+
Maintainer: Kevin Ushey <kevinushey@gmail.com>
7+
Description: Exposes a module class whose name is also used by an unrelated
8+
class in package testRcppCollisionA, for the Rcpp tests.
9+
License: GPL (>= 2)
10+
Imports: methods, Rcpp
11+
LinkingTo: Rcpp

0 commit comments

Comments
 (0)