[education/rkward] rkward/rbackend: Full proof of concept allowing to load libR.so via dlopen()

Thomas Friedrichsmeier null at kde.org
Fri May 10 17:04:24 BST 2024


Git commit f99be88120cffd7f68403c5e9d96c2ec2119b4bc by Thomas Friedrichsmeier.
Committed on 05/05/2024 at 11:57.
Pushed by tfry into branch 'master'.

Full proof of concept allowing to load libR.so via dlopen()

M  +11   -2    rkward/rbackend/CMakeLists.txt
M  +12   -8    rkward/rbackend/rkrapi.cpp
M  +2    -3    rkward/rbackend/rkrapi.h
M  +23   -5    rkward/rbackend/rkrbackendprotocol_backend.cpp
M  +3    -0    rkward/rbackend/rkwarddevice/CMakeLists.txt
M  +1    -1    rkward/rbackend/rkwarddevice/rkgraphicsdevice_stubs.cpp

https://invent.kde.org/education/rkward/-/commit/f99be88120cffd7f68403c5e9d96c2ec2119b4bc

diff --git a/rkward/rbackend/CMakeLists.txt b/rkward/rbackend/CMakeLists.txt
index dbca2a3b7..98342bee4 100644
--- a/rkward/rbackend/CMakeLists.txt
+++ b/rkward/rbackend/CMakeLists.txt
@@ -8,6 +8,7 @@ IF(NOT WIN32)
 	FIND_PACKAGE(Threads)
 ENDIF(NOT WIN32)
 
+SET(DLOPEN_RLIB 1) # TODO: Allow configuration
 ADD_SUBDIRECTORY( rpackages )
 ADD_SUBDIRECTORY( rkwarddevice )
 
@@ -44,12 +45,20 @@ SET (
 
 ADD_LIBRARY(rbackend STATIC ${rbackend_frontend_SRCS} ${rbackend_shared_SRCS})
 TARGET_LINK_LIBRARIES(rbackend rkgraphicsdevice.frontend Qt6::Widgets KF6::TextEditor KF6::WindowSystem)
+IF(${DLOPEN_RLIB})
+TARGET_COMPILE_DEFINITIONS(rbackend PUBLIC RK_DLOPEN_LIBRSO)
+ENDIF()
 
 ADD_DEFINITIONS (-DRKWARD_BACKEND_PATH="${KDE_INSTALL_FULL_LIBEXECDIR}")
 LINK_DIRECTORIES(${R_SHAREDLIBDIR})
+
 ADD_EXECUTABLE(rkward.rbackend ${rbackend_backend_SRCS} ${rbackend_shared_SRCS})
 ECM_MARK_NONGUI_EXECUTABLE(rkward.rbackend)
-TARGET_LINK_LIBRARIES(rkward.rbackend rkgraphicsdevice.backend ${R_USED_LIBS} ${CMAKE_THREAD_LIBS_INIT} Qt6::Network Qt6::Core KF6::I18n)
+TARGET_LINK_LIBRARIES(rkward.rbackend rkgraphicsdevice.backend ${CMAKE_THREAD_LIBS_INIT} Qt6::Network Qt6::Core KF6::I18n)
+IF(NOT ${DLOPEN_RLIB})
+TARGET_LINK_LIBRARIES(rkward.rbackend rkgraphicsdevice.backend ${R_USED_LIBS})
+ENDIF()
+
 IF(WIN32)
 # 64MB stack size is what R itself is build with on Windows, while by default the stack size would be 1MB
 # Not sure, if 64MB is actually needed, but 1MB does cause trouble e.g. with some shiny apps.
@@ -76,7 +85,7 @@ ADD_DEPENDENCIES(rkward.rbackend R_lib)
 ENDIF(MSVC)
 
 IF(WIN32)
-	# on Widows, we install to the rbackend subdirectory, because 1) LIBEXEC_INSTALL_DIR == BIN_INSTALL_DIR and 2) we don't want the backend to pick up
+	# on Windows, we install to the rbackend subdirectory, because 1) LIBEXEC_INSTALL_DIR == BIN_INSTALL_DIR and 2) we don't want the backend to pick up
 	# all the KDE library versions, first, when loading DLLs
 	INSTALL(TARGETS rkward.rbackend DESTINATION ${KDE_INSTALL_BINDIR}/rbackend)
 ELSE(WIN32)
diff --git a/rkward/rbackend/rkrapi.cpp b/rkward/rbackend/rkrapi.cpp
index 48aa14587..50a6ec515 100644
--- a/rkward/rbackend/rkrapi.cpp
+++ b/rkward/rbackend/rkrapi.cpp
@@ -7,16 +7,20 @@ SPDX-License-Identifier: GPL-2.0-or-later
 
 #include "rkrapi.h"
 
+#include <dlfcn.h>
+
+#include "../debug.h"
+
 void RFn::init(void *dllinfo) {
 	// TODO: This is just proof/test of concept code!
-	auto dummy_rfn = new RFn();
-	auto meta = dummy_rfn->metaObject();
-	for (int i = 0; i < meta->propertyCount(); ++i) {
-		auto prop = meta->property(i+meta->propertyOffset());
+	auto rfn = new RFn(); // we need a dummy object, even if we are only interested in the static members
+	auto meta = rfn->metaObject();
+	RK_DEBUG(RBACKEND, DL_DEBUG, "Loading %d symbols from R lib %p", meta->propertyCount() - meta->propertyOffset(), dllinfo);
+	for (int i = meta->propertyOffset(); i < meta->propertyCount(); ++i) {
+		auto prop = meta->property(i);
 		auto name = prop.name();
-		//RFn::Rf_warning("%d %s %d %d", i, name, prop.isReadable(), prop.isWritable());
-		//RFn::Rf_warning("%d", prop.write(dummy_rfn, prop.read(dummy_rfn))); //dummy_rfn->setProperty(name, QVariant::fromValue(nullptr))); // NOTE: Qt refuses to write nullptr as value!
+		auto symb = dlsym(dllinfo, name);
+		RK_DEBUG(RBACKEND, DL_DEBUG, "Lookup of symbol %s in %p: %p", name, dllinfo, symb);
+		prop.write(rfn, QVariant::fromValue(symb)); // NOTE: Qt refuses to write nullptr as value, but that's already the initial value of each member
 	}
-	dummy_rfn->setProperty("Rf_warning", QVariant::fromValue((void*) RFn::Rf_error));
-	RFn::Rf_warning("warning as error");
 }
diff --git a/rkward/rbackend/rkrapi.h b/rkward/rbackend/rkrapi.h
index 9a4260b96..7693e2cb7 100644
--- a/rkward/rbackend/rkrapi.h
+++ b/rkward/rbackend/rkrapi.h
@@ -76,14 +76,12 @@ extern "C" {
 // some functions we need that are not declared
 extern "C" void run_Rmainloop(void);
 
-#define RK_DLOPEN_LIBRSO
 #ifdef RK_DLOPEN_LIBRSO
-
 // Using Qt Meta-Property system for introspection, in order to automate the dlsym-calls.
 // Only the _set ## X function is actually used (for initialization). The _get ## X functions are just to keep the MOC happy.
 // The actual access happens directly via the member (X)
 #define IMPORT_R_API(X) Q_PROPERTY(void* X READ _get ## X WRITE _set ## X) \
-                        public: static inline decltype(::X) *X = &::X; \
+                        public: static inline decltype(::X) *X = nullptr; \
                         void _set ## X (void* v) { X = (decltype(X)) v; } \
                         void* _get ## X () { return (void*) X; }
 #define ROb(X) *(RFn::X)
@@ -289,6 +287,7 @@ IMPORT_R_API(UserBreak);
 IMPORT_R_API(R_GE_clipPathFillRule);
 IMPORT_R_API(R_GE_maskType);
 #endif
+
 public:
 	static void init(void* dllinfo);
 };
diff --git a/rkward/rbackend/rkrbackendprotocol_backend.cpp b/rkward/rbackend/rkrbackendprotocol_backend.cpp
index ee5abde4d..2314c3032 100644
--- a/rkward/rbackend/rkrbackendprotocol_backend.cpp
+++ b/rkward/rbackend/rkrbackendprotocol_backend.cpp
@@ -1,6 +1,6 @@
 /*
 rkrbackendprotocol - This file is part of RKWard (https://rkward.kde.org). Created: Thu Nov 04 2010
-SPDX-FileCopyrightText: 2010-2013 by Thomas Friedrichsmeier <thomas.friedrichsmeier at kdemail.net>
+SPDX-FileCopyrightText: 2010-2024 by Thomas Friedrichsmeier <thomas.friedrichsmeier at kdemail.net>
 SPDX-FileContributor: The RKWard Team <rkward-devel at kde.org>
 SPDX-License-Identifier: GPL-2.0-or-later
 */
@@ -15,14 +15,16 @@ SPDX-License-Identifier: GPL-2.0-or-later
 #include <QThread>
 #include <QLocalSocket>
 #include <QMutex>
+#include <QUuid>     // mis-used as a random-string generator
+#include <QDir>
+#include <QUrl>
 
-#include "rktransmitter.h"
 #include <iostream>
+#include <dlfcn.h>
 
 #include "rkbackendtransmitter.h"
-#include <QUuid>		// mis-used as a random-string generator
-#include <QDir>
-#include <QUrl>
+#include "rktransmitter.h"
+#include "rkrapi.h"
 
 #ifdef Q_OS_MACOS
 #include <CoreFoundation/CoreFoundation.h>
@@ -109,6 +111,22 @@ SPDX-License-Identifier: GPL-2.0-or-later
 		// this token is sent both via stdout and the local socket connection. The frontend simply compares both values.
 		QString token = QUuid::createUuid ().toString ();
 
+		// TODO: Should rather take the libname from CMake
+		// maybe we also want to accept an absolute path specified on command line from the frontend
+#ifdef RK_DLOPEN_LIBRSO
+#ifdef Q_OS_WIN
+#	define RLIBNAME "R.dll"
+#else
+#	define RLIBNAME "libR.so"
+#endif
+//#if defined(RTLD_DEEPBIND)
+//		RK_DEBUG(RBACKEND, DL_DEBUG, "Now loading R lib, dynamically (deepbind)");
+//		RFn::init(dlopen(RLIBNAME, RTLD_NOW | RTLD_DEEPBIND));
+//#else
+		RK_DEBUG(RBACKEND, DL_DEBUG, "Now loading R lib, dynamically (local)");
+		RFn::init(dlopen(RLIBNAME, RTLD_NOW | RTLD_LOCAL));
+//#endif
+#endif
 		RKRBackendTransmitter transmitter (servername, token);
 		RKRBackendProtocolBackend::p_transmitter = &transmitter;
 		RKRBackendProtocolBackend backend (data_dir, rkd_server_name);
diff --git a/rkward/rbackend/rkwarddevice/CMakeLists.txt b/rkward/rbackend/rkwarddevice/CMakeLists.txt
index a5f7000fa..90687e278 100644
--- a/rkward/rbackend/rkwarddevice/CMakeLists.txt
+++ b/rkward/rbackend/rkwarddevice/CMakeLists.txt
@@ -22,3 +22,6 @@ TARGET_LINK_LIBRARIES(rkgraphicsdevice.frontend Qt6::Widgets Qt6::Core Qt6::Netw
 
 ADD_LIBRARY(rkgraphicsdevice.backend STATIC ${rkgraphicsdevice_backend_SRCS})
 TARGET_LINK_LIBRARIES(rkgraphicsdevice.backend Qt6::Core Qt6::Network)
+IF(${DLOPEN_RLIB})
+TARGET_COMPILE_DEFINITIONS(rkgraphicsdevice.backend PUBLIC RK_DLOPEN_LIBRSO)
+ENDIF()
diff --git a/rkward/rbackend/rkwarddevice/rkgraphicsdevice_stubs.cpp b/rkward/rbackend/rkwarddevice/rkgraphicsdevice_stubs.cpp
index 93569fc7b..06467ad46 100644
--- a/rkward/rbackend/rkwarddevice/rkgraphicsdevice_stubs.cpp
+++ b/rkward/rbackend/rkwarddevice/rkgraphicsdevice_stubs.cpp
@@ -872,7 +872,7 @@ void RKD_UseGroup(SEXP ref, SEXP trans, pDevDesc dev) {
 	bool have_trans = (trans != ROb(R_NilValue));
 	double matrix[6];
 	if (have_trans) {
-		for (int i = 0; i < 6; ++i) matrix[i] = REAL(trans)[i];  // order in cairo terms: xx, xy, x0, yx, yy, y0
+		for (int i = 0; i < 6; ++i) matrix[i] = RFn::REAL(trans)[i];  // order in cairo terms: xx, xy, x0, yx, yy, y0
 	}
 
 	{



More information about the rkward-tracker mailing list