Skip to content

Add YARP interop plugin and support for opening YARP devices via dinrail::Device factory - #36

Merged
traversaro merged 5 commits into
mainfrom
yarpinterop
Aug 20, 2026
Merged

Add YARP interop plugin and support for opening YARP devices via dinrail::Device factory#36
traversaro merged 5 commits into
mainfrom
yarpinterop

Conversation

@traversaro

@traversaro traversaro commented Aug 11, 2026

Copy link
Copy Markdown
Contributor

This PR adds the required code to open any YARP device as a dinrail device (i.e. via dinrail::Device::open), and view any interface of the opened YARP device via dinrail::Device::view, without adding any YARP dependency in the core dinrail library, but only a runtime dependency on the dinrail-yarp library.

In a nutshell, it permits to write code like:

#include <dinrail/Device.h>
#include <dinrail/Parameters.h>
#include <yarp/dev/IAxisInfo.h>

dinrail::Parameters options;
options.put("device", "fakeMotionControl");
options.addGroup("GENERAL").put("Joints", 3);

dinrail::Device device;
device.open(options);

// Native yarp::dev::* interfaces of the wrapped device are resolved by view().
yarp::dev::IAxisInfo* axisInfo = nullptr;
device.view(axisInfo);

How this is achieved? The PR is unfortunately large, but the key points are the following.

Dinrail Interop Plugins

A new kind of dinrail plugin (beside the dinrail device) is introduced, the dinrail interop plugin, that is a class that inherits from the dinrail::IInteropPlugin, i.e. that defines this two methods:

    std::unique_ptr<dinrail::IDevice> createDevice(const dinrail::Parameters& config) override

    std::vector<dinrail::DeviceInfo> listDevices() const override

i.e., it provides an arbitrary way to open devices, given a dinrail::Parameters object.

This is used to implement without adding any dep on YARP on the dinrail core, the possibility of opening in dinrail non-dinrail devices, such as YARP devices. The listDevices part is a minor features, useful to make sure that dinrail dev --list can list YARP devices in case the YARP interop plugin is installed.

YARP Interop Plugin

The main class that implements the dinrail::IInteropPlugin is the dinrail::YarpInteropPlugin class, that implements the aforementioned methods, and in particular std::unique_ptr<dinrail::IDevice> createDevice. This is implemented by creating a dinrail::YarpDeviceWrapper that is a proper dinrail device (i.e. it inherits from dinrail::IDevice), but under the hood, it opens a YARP device via the yarp::dev::PolyDriver.

However, the big problem here is the following: you can easily open YARP devices and pass them as dinrail devics using dinrail::YarpDeviceWrapper, but then the Device::view will fail to view any interface implemented by the underlying YARP device, as the dynamic_cast will be called dinrail::YarpDeviceWrapper, not on the underlying YARP device. To solve this, the next point is introduced.

Runtime Dynamic Cast

The Device::view method has been extended as in the following:

    template <class T> bool view(T*& x)
    {
        x = nullptr;

        IDevice* impl = getImplementation();
        if (!impl)
        {
            return false;
        }

        T* v = dynamic_cast<T*>(impl);
        if (v != nullptr)
        {
            x = v;
            return true;
        }

+        // Devices wrapping a foreign implementation (e.g. interop plugins) can
+        // resolve interfaces that are not reachable through a direct cast.
+        auto* interfaceView = dynamic_cast<IInterfaceView*>(impl);
+        if (interfaceView != nullptr)
+        {
+            void* resolved = interfaceView->viewInterface(typeid(T));
+            if (resolved != nullptr)
+            {
+                x = static_cast<T*>(resolved);
+                return true;
+            }
+        }

        return false;
    }

The dinrail::IInterfaceView is an interface implemented by the dinrail::YarpDeviceWrapper class, and it permits basically to do a dynamic cast, without knowing the base polymorphic class of the pointer, that in the case of YARP devices is yarp::dev::DeviceDriver. This is not possible to do in stock C++, so a bit of platform-specific code (targeting Itanium ABI for Clang and GCC, and MSVC ABI for Visual Studio, that both provide some platform-specific way of doing that) has been added in the RuntimeDynamicCast helper functions. In this way, the view functionality can be extended in a plugin-base way, without the need to recompile the code, as instead it happens in the regular templated view for each pair of base polymorphic interface (like dinrail::IDevice or yarp::dev::DeviceDriver ) and interface T being tested.

Follow up work

The work necessary to complete the YARP device drop-in replacement support is not completed, as it is missing #39 . However, this PR is already quite big, so it was decidec to keep #39 in a future PR.

@traversaro
traversaro marked this pull request as draft August 11, 2026 11:02
@traversaro
traversaro force-pushed the yarpinterop branch 3 times, most recently from a994296 to ea2f37a Compare August 12, 2026 13:46
@traversaro traversaro changed the title [draft] Add YARP interop plugin and support for opening YARP devices via dinrail::Device factory Add YARP interop plugin and support for opening YARP devices via dinrail::Device factory Aug 12, 2026
@traversaro
traversaro requested a balanced review from Copilot August 12, 2026 13:50

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds an interop framework allowing dinrail::Device to open YARP devices and expose native YARP interfaces.

Changes:

  • Adds interop plugin discovery and fallback device creation.
  • Implements the YARP interop plugin and runtime interface casting.
  • Extends CLI discovery, tests, and documentation.

Reviewed changes

Copilot reviewed 30 out of 30 changed files in this pull request and generated 10 comments.

Show a summary per file
File Description
src/yarp/test/YarpDropInCompatibilityTest.cpp Tests opening and viewing YARP devices.
src/yarp/test/CMakeLists.txt Registers the YARP compatibility test.
src/yarp/dinrail/YarpInteropPlugin.h Declares the YARP interop plugin.
src/yarp/dinrail/YarpInteropPlugin.cpp Implements YARP device creation and discovery.
src/yarp/dinrail/YarpDeviceWrapper.h Declares the YARP device wrapper.
src/yarp/dinrail/YarpDeviceWrapper.cpp Implements native YARP interface resolution.
src/yarp/CMakeLists.txt Builds and installs the YARP interop module.
src/tools/dinrail.cpp Adds interop and expanded device listing commands.
src/core/test/RuntimeDynamicCastTest.cpp Tests runtime casting behavior.
src/core/test/InteropPluginTest.cpp Tests interop discovery and fallback.
src/core/test/interop/TestInteropCommon.h Provides shared interop test fixtures.
src/core/test/interop/TestInteropBeta.cpp Adds the beta test plugin.
src/core/test/interop/TestInteropAlpha.cpp Adds the alpha test plugin.
src/core/test/interop/IFooTest.h Defines a test-only foreign interface.
src/core/test/CMakeLists.txt Builds interop plugins and tests.
src/core/dinrail/RuntimeDynamicCast.h Declares ABI-level runtime casting.
src/core/dinrail/RuntimeDynamicCast.cpp Implements MSVC and Itanium casting paths.
src/core/dinrail/RuntimeContext.h Exposes interop discovery APIs.
src/core/dinrail/RuntimeContext.cpp Adds interop loading, caching, and fallback.
src/core/dinrail/PluginUtils.h Defines discovery metadata and APIs.
src/core/dinrail/PluginUtils.cpp Implements native and interop discovery.
src/core/dinrail/Interfaces.cpp Defines new interface destructors.
src/core/dinrail/IInteropPlugin.h Defines the interop plugin contract.
src/core/dinrail/IInterfaceView.h Defines dynamic interface lookup.
src/core/dinrail/Device.h Extends view() for wrapped interfaces.
src/core/dinrail/Device.cpp Updates implementation dependencies.
src/core/CMakeLists.txt Adds new core sources and tests.
docs/yarp-migration.md Documents migration from PolyDriver.
docs/plugin-discovery.md Documents interop discovery conventions.
docs/interop-plugins.md Documents authoring interop plugins.
Suppressed comments (1)

src/core/dinrail/RuntimeDynamicCast.h:95

  • Source is already unqualified by the preceding static assertions, so source and static_cast<UnqualifiedSource*>(source) are the same pointer and this calculation is guaranteed to produce zero. Therefore the MSVC path cannot supply the nonzero vfptr displacement it was designed to support; derive the displacement using the actual MSVC object-layout mechanism and add a hierarchy test that requires it.
    const auto sourceAddress = reinterpret_cast<std::intptr_t>(static_cast<void*>(source));
    const auto rttiAddress = reinterpret_cast<std::intptr_t>(
        static_cast<void*>(static_cast<UnqualifiedSource*>(source)));
    const auto vfDelta = static_cast<std::ptrdiff_t>(rttiAddress - sourceAddress);
    return PolymorphicView{static_cast<void*>(source), typeid(UnqualifiedSource), vfDelta};

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment thread src/core/dinrail/RuntimeDynamicCast.h
Comment thread src/core/dinrail/PluginUtils.cpp Outdated
Comment thread src/core/dinrail/RuntimeContext.cpp Outdated
Comment thread docs/interop-plugins.md
Comment thread docs/interop-plugins.md Outdated
Comment thread src/core/dinrail/RuntimeDynamicCast.h
Comment thread src/core/dinrail/RuntimeDynamicCast.cpp
Comment thread src/core/test/RuntimeDynamicCastTest.cpp
Comment thread docs/yarp-migration.md Outdated
Comment thread src/core/dinrail/RuntimeDynamicCast.cpp Outdated

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 30 out of 30 changed files in this pull request and generated 3 comments.

Suppressed comments (1)

src/core/dinrail/RuntimeDynamicCast.h:94

  • Correct the typo in the RTTI pointer name.
    // RTTI vfptrx

Comment thread src/yarp/CMakeLists.txt Outdated
Comment thread src/core/dinrail/PluginUtils.cpp
Comment thread src/core/dinrail/RuntimeDynamicCast.h
@S-Dafarra

Copy link
Copy Markdown
Contributor

Brilliant! Maybe I am missing something, but can't the dinrail::IInteropPlugin have also a view-like method, to avoid the compiler dependent dynamic cast mechanism?

@traversaro

Copy link
Copy Markdown
Contributor Author

Brilliant! Maybe I am missing something, but can't the dinrail::IInteropPlugin have also a view-like method, to avoid the compiler dependent dynamic cast mechanism?

How you would envision the signature of such a method?

@S-Dafarra

Copy link
Copy Markdown
Contributor

Brilliant! Maybe I am missing something, but can't the dinrail::IInteropPlugin have also a view-like method, to avoid the compiler dependent dynamic cast mechanism?

How you would envision the signature of such a method?

I thought the interface should be like

template <class T> bool view(T*& x)
, but I see now that the template would not allow for a dynamic load.

Just for brainstorming, to avoid the use the templates, we could have instead a getViewWorker method that returns a std::function? I am thinking of a way to exploit the view method of the polydriver

@traversaro

Copy link
Copy Markdown
Contributor Author

Just for brainstorming, to avoid the use the templates, we could have instead a getViewWorker method that returns a std::function? I am thinking of a way to exploit the view method of the polydriver

If I got this correctly, I think I experimented a bit with this idea. The problem is that the only way of doing dynamic_cast in standard C++ (without the compiler specific bits introduced here) is to compile somewhere something similar to the following code:

yarp::dev::DeviceDriver* ptr = ..;
Interface* interface{nullptr};
interface = dynamic_cast<Interface*>(ptr);

This requires to know (at compile time) both the types yarp::dev::DeviceDriver and Interface in the same compilation unit. Assuming that we do not want to depend on YARP in the dinrail core and we do not want to enumarate all the possible interfaces we may want to view in the dinrail-yarp-interop plugin, the problem has no solution (at least for what I was able to find). Even if the interop plugin exposed a way to return a std::function, this std::function would need someway to get the Interface type to pass to the view, and as it can't get a templated type, we are back to have to do a dynamic_cast out of a std::type_info. If you want to read more on this, this is my brainstorming with ChatGPT: https://chatgpt.com/share/6a7d7c59-930c-83eb-a831-555134b8469c .

@S-Dafarra

Copy link
Copy Markdown
Contributor

Ok, I see. Thanks for the explanation!

@traversaro

Copy link
Copy Markdown
Contributor Author

fyi @isorrentino if there is anyone interested in this on @gbionics/team-hermes side, feel free to check it out!

@traversaro

traversaro commented Aug 19, 2026

Copy link
Copy Markdown
Contributor Author

I did not add @RiccardoGrieco to the long list of reviewers, my bad!

auto interop = interopPlugin->allocate();
if (!interop)
{
std::cerr << "dinrail::Device: impossible to create instance for interop plugin '"

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is it possible to move these messages to spdlog?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yep, but I would do that in a separate PR where we migrate the whole src/core directory to spdlog, if that is ok for you: #45 .

Comment thread src/core/dinrail/RuntimeDynamicCast.cpp Outdated

@S-Dafarra S-Dafarra left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Great work

@traversaro
traversaro merged commit c961787 into main Aug 20, 2026
2 checks passed
@traversaro
traversaro deleted the yarpinterop branch August 20, 2026 12:41
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants