Skip to content

Commit 0f6dad8

Browse files
kevinusheyeddelbuettel
authored andcommitted
run the module body only once per dll
1 parent b501ab8 commit 0f6dad8

3 files changed

Lines changed: 35 additions & 11 deletions

File tree

‎ChangeLog‎

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,14 @@
4141
* inst/tinytest/cpp/Module.cpp: Add a class deriving from another
4242
* inst/tinytest/test_module.R: Test the new checks
4343

44+
2026-10-29 Kevin Ushey <kevinushey@gmail.com>
45+
46+
* inst/include/Rcpp/Module.h (RCPP_MODULE): Run the module body only the
47+
first time the boot function is called, since it adds to the static
48+
module and every Module() call boots the module again, which duplicated
49+
constructors and methods (#1527)
50+
* inst/tinytest/test_module.R: Test loading a module repeatedly
51+
4452
2026-10-29 Kevin Ushey <kevinushey@gmail.com>
4553

4654
* inst/include/Rcpp/Module.h (RCPP_MODULE): Wrap the module boot

‎inst/include/Rcpp/Module.h‎

Lines changed: 17 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -637,17 +637,23 @@ namespace Rcpp {
637637
#define RCPP_MODULE_BOOT(name) _rcpp_module_boot_##name
638638

639639
// the boot function is called from R via .Call(), so exceptions thrown by
640-
// the module body must be turned into R errors
641-
#define RCPP_MODULE(name) \
642-
void _rcpp_module_##name##_init() ; \
643-
static Rcpp::Module _rcpp_module_##name( # name ) ; \
644-
extern "C" SEXP _rcpp_module_boot_##name(){ \
645-
BEGIN_RCPP \
646-
Rcpp::internal::ModuleScope _rcpp_module_scope( & _rcpp_module_##name ) ; \
647-
_rcpp_module_##name##_init( ) ; \
648-
return Rcpp::XPtr<Rcpp::Module>( & _rcpp_module_##name , false ) ; \
649-
END_RCPP \
650-
} \
640+
// the module body must be turned into R errors; it runs every time the
641+
// module is loaded, but the body adds to the static module, so it may only
642+
// run once
643+
#define RCPP_MODULE(name) \
644+
void _rcpp_module_##name##_init() ; \
645+
static Rcpp::Module _rcpp_module_##name( # name ) ; \
646+
static bool _rcpp_module_##name##_initialized = false ; \
647+
extern "C" SEXP _rcpp_module_boot_##name(){ \
648+
BEGIN_RCPP \
649+
if( ! _rcpp_module_##name##_initialized ){ \
650+
Rcpp::internal::ModuleScope _rcpp_module_scope( & _rcpp_module_##name ) ; \
651+
_rcpp_module_##name##_init( ) ; \
652+
_rcpp_module_##name##_initialized = true ; \
653+
} \
654+
return Rcpp::XPtr<Rcpp::Module>( & _rcpp_module_##name , false ) ; \
655+
END_RCPP \
656+
} \
651657
void _rcpp_module_##name##_init()
652658

653659
// silly little dance to suppress a 'defined but not used variable' warning

‎inst/tinytest/test_module.R‎

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -189,3 +189,13 @@ expect_identical( res, c(1, 2, 3) )
189189
## rather than an error (which used to surface at garbage collection)
190190
r <- new( ModuleRandomizer )
191191
expect_identical( r$finalize(), NULL )
192+
193+
## loading a module again must not re-run its body, which used to add
194+
## every constructor and method to the static module once more (#1527);
195+
## this redefines the module's classes, so it comes last
196+
is_demo_module <- function(dll) is.loaded("_rcpp_module_boot_demoModule", PACKAGE = dll[["name"]])
197+
dll <- Filter(is_demo_module, getLoadedDLLs())[[1L]]
198+
mod <- Module( "demoModule", PACKAGE = dll, mustStart = TRUE )
199+
mod <- Module( "demoModule", PACKAGE = dll, mustStart = TRUE )
200+
expect_equal( length( mod$ModuleWorld@constructors ), 1L )
201+
expect_equal( length( mod$ModuleWorld@methods$greet@nargs ), 1L )

0 commit comments

Comments
 (0)