Skip to Content
CTech Digital
  • Home
  • Odoo services
  • AI services
  • Contact us
  • 0
  • Nederlands (BE) English (UK) Français
CTech Digital
  • 0
    • Home
    • Odoo services
    • AI services
    • Contact us
  • Nederlands (BE) English (UK) Français

Plug in design for 5G Alert

  • All Blogs
  • Daily blog
  • Plug in design for 5G Alert
  • 30 June 2025 by
    CTech Metrology, Luc Wens

    Of course. Exposing a full C++ class from a shared library (.so file on the Nano) is a standard and powerful technique for creating a plug-in architecture. However, you don't expose the class directly in the way you might think. Directly exporting all member functions of a C++ class can lead to issues with name mangling and ABI (Application Binary Interface) stability.

    The standard and most robust method is to expose a factory function using extern "C" linkage. This C-style function acts as a public entry point to your library, responsible for creating an instance of your class and returning a pointer to it, preferably as an abstract base class (interface).

    This approach provides a clean, stable C interface to the outside world while allowing you to use all the power of C++ (inheritance, polymorphism, etc.) inside your plug-in.

    Here is a complete, step-by-step guide on how to design and implement this on your Nvidia Nano.

    The 3 Key Components

    1. The Interface (Header File): A common header file that defines the abstract base class. Both your main application and your plug-in will include this.
    2. The Plug-in (.so file): The implementation of your OutputSink class and the C-style factory function to create it.
    3. The Main Application (Executable): The program that loads the .so file, calls the factory function, and uses the OutputSink through the interface pointer.

    Step 1: Define the Common Interface

    This is the contract that your plug-ins must adhere to. Create a header file, for example, IOutputSink.h.

    IOutputSink.h

    C++

    #pragma once
    
    // This is the interface for our output sink plug-in.
    // It uses pure virtual functions to define the contract.
    class IOutputSink {
    public:
        // Virtual destructor is crucial for proper cleanup when deleting through a base pointer.
        virtual ~IOutputSink() {}
    
        // Function to configure the sink
        virtual bool configure(const char* configString) = 0;
    
        // Function to write data to the sink
        virtual void write(const char* data) = 0;
    
        // Function to close or release resources
        virtual void close() = 0;
    };
    
    // Define the function signature for our factory functions.
    // This makes the code in the main application cleaner.
    typedef IOutputSink* (*create_sink_t)();
    typedef void (*destroy_sink_t)(IOutputSink*);
    

    Step 2: Implement the Plug-in (.so file)

    Now, create a specific implementation of this interface. For example, a "ConsoleSink" that just prints to the console.

    ConsoleSink.cpp

    C++

    #include "IOutputSink.h"
    #include <iostream>
    
    // This is our concrete implementation of the IOutputSink interface.
    // It remains hidden inside the .so file.
    class ConsoleSink : public IOutputSink {
    public:
        ConsoleSink() {
            std::cout << "ConsoleSink: Created!" << std::endl;
        }
    
        ~ConsoleSink() override {
            std::cout << "ConsoleSink: Destroyed!" << std::endl;
        }
    
        bool configure(const char* configString) override {
            std::cout << "ConsoleSink: Configuring with '" << configString << "'" << std::endl;
            return true; // Simple success
        }
    
        void write(const char* data) override {
            std::cout << "ConsoleSink: " << data << std::endl;
        }
    
        void close() override {
            std::cout << "ConsoleSink: Closing." << std::endl;
        }
    };
    
    // These are the C-style factory functions that the main application will look for.
    // The 'extern "C"' is essential to prevent C++ name mangling.
    extern "C" {
        // Factory function to create an instance of the class
        IOutputSink* create() {
            return new ConsoleSink();
        }
    
        // Destroyer function to properly delete the instance
        void destroy(IOutputSink* p) {
            delete p;
        }
    }
    

    Step 3: Create the Main Application

    This application will dynamically load your plug-in.

    main.cpp

    C++

    #include "IOutputSink.h"
    #include <iostream>
    #include <dlfcn.h> // For dynamic loading on Linux
    
    int main() {
        std::cout << "Main App: Loading the plug-in..." << std::endl;
    
        // Use dlopen() to load the shared library.
        // RTLD_LAZY: Resolve symbols as code from the library is executed.
        // Use "./libconsolesink.so" to look in the current directory.
        void* handle = dlopen("./libconsolesink.so", RTLD_LAZY);
        if (!handle) {
            std::cerr << "Main App: Cannot load library: " << dlerror() << std::endl;
            return 1;
        }
    
        // Reset errors
        dlerror();
    
        // Use dlsym() to find the address of the 'create' function.
        create_sink_t create_func = (create_sink_t) dlsym(handle, "create");
        const char* dlsym_error = dlerror();
        if (dlsym_error) {
            std::cerr << "Main App: Cannot find symbol 'create': " << dlsym_error << std::endl;
            dlclose(handle);
            return 1;
        }
        
        // Find the 'destroy' function
        destroy_sink_t destroy_func = (destroy_sink_t) dlsym(handle, "destroy");
        dlsym_error = dlerror();
        if (dlsym_error) {
            std::cerr << "Main App: Cannot find symbol 'destroy': " << dlsym_error << std::endl;
            dlclose(handle);
            return 1;
        }
    
        // Now, use the factory function to create an instance of our sink.
        std::cout << "Main App: Creating sink instance..." << std::endl;
        IOutputSink* mySink = create_func();
    
        // Use the sink through the interface pointer.
        // The main application knows nothing about the 'ConsoleSink' class itself.
        mySink->configure("mode=verbose");
        mySink->write("Hello from the main application!");
        mySink->write("This is a test.");
        mySink->close();
    
        // Clean up
        std::cout << "Main App: Destroying sink instance..." << std::endl;
        destroy_func(mySink);
    
        // Unload the library
        std::cout << "Main App: Unloading the plug-in." << std::endl;
        dlclose(handle);
    
        return 0;
    }
    

    Step 4: Compile Everything

    On your Nvidia Nano's command line, you would use g++ to compile these files.

    1. Compile the Plug-in (ConsoleSink.cpp) into a shared object (.so):
      The -fPIC flag is crucial for creating position-independent code, which is required for shared libraries.Bash
      g++ -fPIC -shared -o libconsolesink.so ConsoleSink.cpp
      
    2. Compile the Main Application (main.cpp):
      You need to link against the dynamic linking library (-ldl).Bash
      g++ -o main_app main.cpp -ldl
      
    3. Run the Application:
      Make sure the .so file is in the same directory or in a location where the dynamic linker can find it.Bash
      ./main_app
      

    Expected Output

    Main App: Loading the plug-in...
    Main App: Creating sink instance...
    ConsoleSink: Created!
    ConsoleSink: Configuring with 'mode=verbose'
    ConsoleSink: Hello from the main application!
    ConsoleSink: This is a test.
    ConsoleSink: Closing.
    Main App: Destroying sink instance...
    ConsoleSink: Destroyed!
    Main App: Unloading the plug-in.
    

    This factory-based approach is the standard, safe, and portable way to expose C++ class functionality from a dynamic library on Linux-based systems like the one on your Nvidia Nano.

    in Daily blog
    # Xenomatix
    Updating CMake version on Linux
    LINUX
    Copyright © CTech
    Nederlands (BE) | English (UK) | Français
    Powered by Odoo - The #1 Open Source eCommerce