Sukhdeep Singh
Sukhdeep Singh

Reputation: 23

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)


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’

Upvotes: 2

Views: 1423

Answers (1)

sehe
sehe

Reputation: 393084

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

Upvotes: 2

Related Questions