Search code examples
c++boostdependency-injectiondynamic-library

C++ Dependency injection with Dynamic library loading


I am unable to use C++ dependency injection library "boost::di" with another boost library for dynamic loading of libraries named "Boost.dll".

I broke down the problem into two parts - firstly, Testing dynamic loading, and secondly, binding the abstract class to implementation (which is loaded dynamically).

I am able to successfully load the libraries dynamically. But when I am trying to use Dependency injection binding then It is reporting the mismatch that class template is expected and not received.

I have a very basic sample code in this repo: https://bitbucket.org/kobe_la/boost-plugins-prework/src/master/

I would really use some help in figuring out the binding process for dynamically loaded library. (see exact error at bottom)

  • File ioperation.hpp

     #include <string>
    
     class ioperation {
    
     public:
         virtual std::string name() const = 0;
         virtual float calculate(float x, float y) = 0;
    
         virtual ~ioperation() {}
     };
    
  • File sum.cpp

     #include <boost/config.hpp>
     #include <boost/dll/alias.hpp>
     #include <boost/dll/import.hpp>
     #include "ioperation.hpp"
     #include <iostream>
    
     namespace sum_namespace {
    
     class sum: public ioperation {
     public:
         sum() {
             std::cout << "[sum_constructor]" << std::endl;
         }
    
         std::string name() const {
             return "sum";
         }
    
         float calculate(float x, float y) {
             return x + y;
         }
    
         ~sum() {
             std::cout << "[~sum_destructor]" << std::endl;
         }
    
    
            // Factory method
             static boost::shared_ptr<sum> create_sum() {
                     return boost::shared_ptr<sum>(
                             new sum()
                     );
             }
         };
     }
    
     BOOST_DLL_ALIAS(
         sum_namespace::sum::create_sum, // <-- this function is exported with...
         create_sum                                       // <-- ...this alias name
     )
    
  • File dot_product.cpp

     #include <boost/config.hpp>
     #include <boost/dll/alias.hpp>
     #include <boost/dll/import.hpp>
     #include <iostream>
     #include "ioperation.hpp"
    
     namespace dot_product_namespace {
    
     class dot_product: public ioperation {
    
             boost::shared_ptr<ioperation> sum_ptr;
     public:
             dot_product(boost::shared_ptr<ioperation> &arg) {
                     sum_ptr = arg;
                     std::cout << "[dot_product_constructor]" << std::endl;
             }
    
             std::string name() const {
                     return "dot_product";
             }
    
             float calculate(float a1, float a2) {
                     //dot product given vector with itself
                     //formula: a.b = a1*b1 + a2*b2
                     return sum_ptr->calculate(a1*a1, a2*a2);
             }
    
             // Factory method
             static boost::shared_ptr<ioperation> create_dot_product(boost::shared_ptr<ioperation> sum_ptr) {
                     return boost::shared_ptr<ioperation>(
                             new dot_product(sum_ptr)
                     );
             }
    
             ~dot_product() {
                     std::cout << "[~dot_product_destructor]" << std::endl;
             }
         };
     };
    
     BOOST_DLL_ALIAS(
         dot_product_namespace::dot_product::create_dot_product, // <-- this function is exported with...
         create_dot_product                               // <-- ...this alias name
     )
    
  • File application_di.cpp

     #include "boost/shared_ptr.hpp"
     #include <boost/dll/import.hpp>
     #include "boost/function.hpp"
     #include <boost/di.hpp>
     #include "ioperation.hpp"
     #include <iostream>
    
     namespace di = boost::di;
     namespace dll = boost::dll;
    
    
     class app {
      private:
         boost::shared_ptr<ioperation> ptr_;
      public:
         app(boost::shared_ptr<ioperation> ptr)
          : ptr_(ptr)
         {
                std::cout<<"name: " << ptr_->name()<<std::endl;
         }
    
     };
    
    
     int main(int argc, char* argv[]) {
    
         //setting up paths to library(.so) files
             boost::filesystem::path shared_library_path(".");    // argv[1] contains path to directory with our plugin library
             boost::filesystem::path child_library_path = shared_library_path/"dot_product";
             boost::filesystem::path parent_library_path = shared_library_path/"sum";
    
             //defining function types for lib constructors
             typedef boost::shared_ptr<ioperation> (sum_create_t)();
             typedef boost::shared_ptr<ioperation> (dot_product_create_t)(boost::shared_ptr<ioperation>);
             boost::function<sum_create_t> sum_creator;
             boost::function<dot_product_create_t> dot_product_creator;
    
             //importing SUM lib constructor(takes no arg)
             sum_creator = boost::dll::import_alias<sum_create_t>(   // type of imported symbol must be explicitly specified
                     parent_library_path,                            // path to library
                     "create_sum",                                   // symbol to import
                     dll::load_mode::append_decorations              // do append extensions and prefixes
             );
    
             //importing DOT_PRODUCT lib constructor(takes 1 arg of ptr to IOPERATION)
             dot_product_creator = boost::dll::import_alias<dot_product_create_t>(   // type of imported symbol must be explicitly specified
                     child_library_path,                                             // path to library
                     "create_dot_product",                                           // symbol to import
                     dll::load_mode::append_decorations                              // do append extensions and prefixes
             );
    
             //creating a ptr to SUM object
             boost::shared_ptr<ioperation> sum_ptr = sum_creator();
    
             //creating a ptr to DOT_PRODUCT object(passing above created SUM object ptr)
             boost::shared_ptr<ioperation> dot_product_ptr = dot_product_creator(sum_ptr);
             auto use_sum = true;
    
         const auto injector = di::make_injector(
               di::bind<ioperation>().to([&](const auto& injector) -> boost::shared_ptr<ioperation> {
                 if (use_sum)
                   return injector.template create<boost::shared_ptr<sum_ptr>>();
                 else
                   return injector.template create<boost::shared_ptr<dot_product_ptr>>();
             })
         );
    
         injector.create<app>();
     }
    
  • File application_main.cpp

     #include "boost/shared_ptr.hpp"
     #include <boost/dll/import.hpp>
     #include "boost/function.hpp"
     #include <iostream>
     #include "ioperation.hpp"
    
     namespace dll = boost::dll;
    
     int main(int argc, char* argv[]) {
    
         //setting up paths to library(.so) files
         boost::filesystem::path shared_library_path(argv[1]);    // argv[1] contains path to directory with our plugin library
             boost::filesystem::path child_library_path = shared_library_path/"dot_product";
         boost::filesystem::path parent_library_path = shared_library_path/"sum";
    
         //defining function types for lib constructors
         typedef boost::shared_ptr<ioperation> (sum_create_t)();
         typedef boost::shared_ptr<ioperation> (dot_product_create_t)(boost::shared_ptr<ioperation>);
         boost::function<sum_create_t> sum_creator;
         boost::function<dot_product_create_t> dot_product_creator;
    
         //importing SUM lib constructor(takes no arg)
         sum_creator = boost::dll::import_alias<sum_create_t>(   // type of imported symbol must be explicitly specified
                 parent_library_path,                            // path to library
                 "create_sum",                                   // symbol to import
                 dll::load_mode::append_decorations              // do append extensions and prefixes
             );
    
         //importing DOT_PRODUCT lib constructor(takes 1 arg of ptr to IOPERATION)
         dot_product_creator = boost::dll::import_alias<dot_product_create_t>(   // type of imported symbol must be explicitly specified
                 child_library_path,                                             // path to library
                 "create_dot_product",                                           // symbol to import
                 dll::load_mode::append_decorations                              // do append extensions and prefixes
             );
    
         //creating a ptr to PARENT_PLUGIN_SUM objecti
         boost::shared_ptr<ioperation> sum_ptr = sum_creator();
    
         //creating a ptr to CHILD_PLUGIN_MULT object(passing above created PARENT_PLUGIN_SUM object ptr)
         boost::shared_ptr<ioperation> dot_product_ptr = dot_product_creator(sum_ptr);
    
         //executing calculate methods for object ptrs
         std::cout << "Plugin Name: " << sum_ptr->name() << std::endl;
         std::cout << "sum_ptr->calculate(1, 2)[expected=3]: " << sum_ptr->calculate(1, 2) << std::endl;
         std::cout << "Plugin Name: " << dot_product_ptr->name() << std::endl;
             std::cout << "dot_product_ptr->calculate(1, 2)[expected=5]: " << dot_product_ptr->calculate(1, 2) << std::endl;
     }   
    

Below is the exact error observed:

    + echo '=> Compiling libraries and application_main.cpp ...'
    => Compiling libraries and application_main.cpp ...

    + g++ -std=c++14 -fPIC -c -o libsum.o sum.cpp
    + g++ -shared -o libsum.so libsum.o
    + g++ -std=c++14 -fPIC -c -o libdot_product.o dot_product.cpp
    + g++ -shared -o libdot_product.so libdot_product.o
    + g++ -std=c++14 -lboost_filesystem -lboost_system -ldl application_main.cpp -o application_main
    + echo '=> Compilation completed. '
    => Compilation completed. 

    + echo '=> Executing ./application_main .'
    => Executing ./application_main .

    + ./application_main .
    [sum_constructor]
    [dot_product_constructor]
    Plugin Name: sum
    sum_ptr->calculate(1, 2)[expected=3]: 3
    Plugin Name: dot_product
    dot_product_ptr->calculate(1, 2)[expected=5]: 5
    [~dot_product_destructor]
    [~sum_destructor]
    + echo ==================================
    ==================================
    + echo '=> Compiling application_di.cpp ...'

    => Compiling application_di.cpp …
    + g++ application_di.cpp -lboost_filesystem -lboost_system -ldl -o application_di
    application_di.cpp: In lambda function:
    application_di.cpp:62:65: error: type/value mismatch at argument 1 in template parameter list for ‘template<class T> class boost::shared_ptr’
        62 |               return injector.template create<boost::shared_ptr<sum_ptr>>();
            |                                                                 ^~~~~~~
    application_di.cpp:62:65: note:   expected a type, got ‘sum_ptr’
    application_di.cpp:64:65: error: type/value mismatch at argument 1 in template parameter list for ‘template<class T> class boost::shared_ptr’
        64 |               return injector.template create<boost::shared_ptr<dot_product_ptr>>();
            |                                                                 ^~~~~~~~~~~~~~~
    application_di.cpp:64:65: note:   expected a type, got ‘dot_product_ptr’


Solution

  • Shared pointer is the problem. The bind<> template argument cannot be a pointer or reference type

    There's some information about why pointers are disallowed in bind<>: https://github.com/boost-ext/di/issues/317

    I figured out a working way to bind the dependency:

    const auto injector =
        di::make_injector(di::bind<ioperation>().to(
            [=, &use_sum](const auto& injector) -> op_ptr
            {
                return use_sum
                    ? sum_creator()
                    : dot_product_creator(sum_creator());
            }) //
        );
    

    Note that capturing the factory functions by value keeps the DLL around as long as the injector exists. This is safer than capturing by reference. Capturing the use_sum by reference highlights that the injector is dynamic. If that's not required, then I'd replace the whole injector with:

    const auto injector = di::make_injector(di::bind<ioperation>().to(
        use_sum ? sum_creator() : dot_product_creator(sum_creator())) //
    );
    

    Full Demo

    See also Github: https://github.com/sehe/boost-plugins-prework

    • File ioperation.hpp

       #pragma once
       #include <boost/shared_ptr.hpp>
       #include <string>
      
       struct ioperation {
         virtual std::string name() const                = 0;
         virtual float       calculate(float x, float y) = 0;
      
         virtual ~ioperation() = default;
       };
      
       using op_ptr = boost::shared_ptr<ioperation>;
      
    • File sum.cpp

       #include <boost/config.hpp>
       #include <boost/dll/alias.hpp>
       #include <boost/dll/import.hpp>
       #include "ioperation.hpp"
       #include <iostream>
      
       namespace sum_namespace {
           class sum : public ioperation {
             public:
               sum() { std::cout << "[sum_constructor]" << std::endl; }
               std::string name() const { return "sum"; }
               float calculate(float x, float y) { return x + y; }
               ~sum() { std::cout << "[~sum_destructor]" << std::endl; }
      
               // Factory method
               static op_ptr create_sum() { return op_ptr(new sum()); }
           };
       } // namespace sum_namespace
      
       BOOST_DLL_ALIAS(
           sum_namespace::sum::create_sum, // <-- this function is exported with...
           create_sum                      // <-- ...this alias name
       )
      
    • File sum.hpp

       #pragma once
       #include "ioperation.hpp"
       #include "ioperation.hpp"
      
       namespace sum_namespace {
           struct sum : ioperation {
               sum();
               ~sum() override;
               std::string name() const override;
               float       calculate(float, float) override;
      
               static op_ptr create_sum();
           };
       } // namespace sum_namespace
      
    • File dot_product.cpp

       #include "dot_product.h"
       #include <boost/config.hpp>
       #include <boost/dll/alias.hpp>
       #include <boost/dll/import.hpp>
       #include <iostream>
      
       namespace dot_product_namespace {
      
           dot_product::dot_product(op_ptr& arg) {
               sum_ptr = arg;
               std::cout << "[dot_product_constructor]" << std::endl;
           }
      
           std::string dot_product::name() const { return "dot_product"; }
      
           float dot_product::calculate(float a1, float a2) {
               // dot product given vector with itself
               // formula: a.b = a1*b1 + a2*b2
               return sum_ptr->calculate(a1 * a1, a2 * a2);
           }
      
           // Factory method
           /*static*/ op_ptr dot_product::create_dot_product(op_ptr sum_ptr) {
               return op_ptr(new dot_product(sum_ptr));
           }
      
           dot_product::~dot_product() {
               std::cout << "[~dot_product_destructor]" << std::endl;
           }
       }; // namespace dot_product_namespace
      
       BOOST_DLL_ALIAS(dot_product_namespace::dot_product::
                           create_dot_product, // <-- this function is exported with...
                       create_dot_product      // <-- ...this alias name
       )
      
    • File dot_product.h

       #pragma once
       #include "ioperation.hpp"
      
       namespace dot_product_namespace {
      
           struct dot_product : ioperation {
               dot_product(op_ptr&);
               ~dot_product() override;
               std::string name() const override;
               float       calculate(float, float) override;
      
               static op_ptr create_dot_product(op_ptr sum_ptr);
      
             private:
               op_ptr sum_ptr;
           };
       }; // namespace dot_product_namespace
      
    • File application_di.cpp

       #include "boost/function.hpp"
       #include "ioperation.hpp"
       #include <boost/di.hpp>
       #include <boost/dll/import.hpp>
       #include <iostream>
      
       namespace di = boost::di;
       namespace dll = boost::dll;
      
       class app {
        private:
          op_ptr ptr_;
      
        public:
            app(boost::shared_ptr<ioperation> ptr)
                : ptr_(ptr)
            {
                std::cout << "name: " << ptr_->name() << std::endl;
            }
       };
      
       using boost::filesystem::path;
      
       int main(int argc, char** argv) {
           // setting up paths to library(.so) files
           path lib_path(argc > 1 ? argv[1] : "."),
               child_library_path = lib_path / "dot_product",
               parent_library_path = lib_path / "sum";
      
           // defining function types for lib constructors
           using sum_create_t = op_ptr();
           using dot_product_create_t = op_ptr(op_ptr);
      
           // importing SUM lib factory
           auto sum_creator = boost::dll::import_alias<sum_create_t>(
               parent_library_path,
               "create_sum",
               dll::load_mode::append_decorations);
      
           // importing DOT_PRODUCT lib factory
           auto dot_product_creator =
               boost::dll::import_alias<dot_product_create_t>(
                   child_library_path,
                   "create_dot_product",
                   dll::load_mode::append_decorations);
      
           auto use_sum = true;
      
           const auto injector =
               di::make_injector(di::bind<ioperation>().to(
                   [=, &use_sum](const auto& injector) -> op_ptr
                   {
                       return use_sum
                           ? sum_creator()
                           : dot_product_creator(sum_creator());
                   }) //
               );
      
           use_sum = argc > 2;
           injector.create<app>();
       }
      
    • File application_main.cpp

       #include "boost/shared_ptr.hpp"
       #include <boost/dll/import.hpp>
       #include "boost/function.hpp"
       #include <iostream>
       #include "ioperation.hpp"
      
       namespace dll = boost::dll;
       using boost::filesystem::path;
      
       int main(int argc, char** argv)
       {
           // setting up paths to library(.so) files
           path lib_path(argc > 1 ? argv[1] : "."),
               child_library_path = lib_path / "dot_product",
               parent_library_path = lib_path / "sum";
      
           // defining function types for lib constructors
           using sum_create_t = op_ptr();
           using dot_product_create_t = op_ptr(op_ptr);
      
           // importing SUM lib factory
           auto sum_creator = dll::import_alias<sum_create_t>(
               parent_library_path,
               "create_sum",
               dll::load_mode::append_decorations);
      
           // importing DOT_PRODUCT lib factory
           auto dot_product_creator =
               dll::import_alias<dot_product_create_t>(
                   child_library_path,
                   "create_dot_product",
                   dll::load_mode::append_decorations);
      
           auto sum_ptr = sum_creator();
           auto dot_product_ptr = dot_product_creator(sum_ptr);
      
           //executing calculate methods for object ptrs
           for (op_ptr op : {sum_creator(), dot_product_creator(sum_creator()) }) {
               std::cout << "\nPlugin Name: " << op->name() << "\n";
               std::cout << "calculate(1, 2): " << op->calculate(1, 2) << "\n";
           }
       }
      
    • File compile_n_run.sh

       #!/bin/bash
      
       set -e -x -u
       ./cleanup.sh
      
       echo "=> Compiling now..."
       CPPFLAGS="-std=c++14 -fPIC -I /home/sehe/custom/boost-di/include/"
       LDFLAGS=""
      
       # Required to compile on sehe's machine:
       #CPPFLAGS="$CPPFLAGS -I /home/sehe/custom/boost-di/include/"
       #CPPFLAGS="$CPPFLAGS -isystem /home/sehe/custom/superboost/ ";
       #LDFLAGS="$LDFLAGS -L /home/sehe/custom/superboost/stage/lib"
      
       g++ $CPPFLAGS sum.cpp -shared -o libsum.so $LDFLAGS
       g++ $CPPFLAGS dot_product.cpp -shared -o libdot_product.so $LDFLAGS
      
       # add application libraries
       LDFLAGS="$LDFLAGS -lboost_filesystem -lboost_system -ldl"
       g++ $CPPFLAGS application_main.cpp -o application_main $LDFLAGS
       g++ $CPPFLAGS application_di.cpp -o application_di $LDFLAGS
      
       ./application_main .
      
       ./application_di . use_sum
       ./application_di . # uses dot_product
      

    Output

    Using compile_n_run.sh:

    + ./cleanup.sh
    removed 'application_main'
    removed 'libdot_product.so'
    removed 'libsum.so'
    => cleaned up!
    + echo '=> Compiling now...'
    => Compiling now...
    + CPPFLAGS='-std=c++14 -fPIC -I /home/sehe/custom/boost-di/include/'
    + LDFLAGS=
    + g++ -std=c++14 -fPIC -I /home/sehe/custom/boost-di/include/ sum.cpp -shared -o libsum.so
    + g++ -std=c++14 -fPIC -I /home/sehe/custom/boost-di/include/ dot_product.cpp -shared -o libdot_product.so
    + LDFLAGS=' -lboost_filesystem -lboost_system -ldl'
    + g++ -std=c++14 -fPIC -I /home/sehe/custom/boost-di/include/ application_main.cpp -o application_main -lboost_filesystem -lboost_system -ldl
    + g++ -std=c++14 -fPIC -I /home/sehe/custom/boost-di/include/ application_di.cpp -o application_di -lboost_filesystem -lboost_system -ldl
    + ./application_main .
    [sum_constructor]
    [dot_product_constructor]
    [sum_constructor]
    [sum_constructor]
    [dot_product_constructor]
    
    Plugin Name: sum
    calculate(1, 2): 3
    
    Plugin Name: dot_product
    calculate(1, 2): 5
    [~dot_product_destructor]
    [~sum_destructor]
    [~sum_destructor]
    [~dot_product_destructor]
    [~sum_destructor]
    + ./application_di . use_sum
    [sum_constructor]
    name: sum
    [~sum_destructor]
    + ./application_di .
    [sum_constructor]
    [dot_product_constructor]
    name: dot_product
    [~dot_product_destructor]
    [~sum_destructor]
    

    Or more live:enter image description here