CxxWrap.jl

Wrapping a C++ library using CxxWrap.jl

Get your system ready

Introduction

What is CxxWrap?

  • Package for using C++ libraries in Julia
  • Library-based approach

Workshop objectives

  1. What does it take to enable
    add MyGreatCppPackage
  2. How do you wrap:
    • Functions
    • Classes
    • Parametric (template) types
    • Smart pointers
    • STL containers
    • Enum types
    • ...

Libfoo A-Z

Project outline

  1. JLL package: depends on libcxxwrap-julia
  2. Julia package: uses the JLL package and CxxWrap

Building the JLL package

  1. Build a library that links to libcxxwrap-julia
    • Write CMakeLists.txt
    • Write wrapping function
  2. Generate the JLL using BinaryBuilder:
    
                  julia --color=yes libfoo/build_tarballs.jl --debug --verbose x86_64-linux-gnu
                  julia --color=yes build_tarballs.jl --verbose --meta-json x86_64-linux-gnu > foo.json
                  julia build_jll.jl foo.json
                
  3. Set up override for local development
  4. Test
  5. (publish the JLL)

Building the Julia package

  1. Generate a normal Julia package
  2. Add CxxWrap and JLL as dependencies
  3. Load the library
  4. Extend from the Julia side as needed

Workflow conclusions

  • Good integration with BinaryBuilder workflow
    • Could use better "local jll" support
    • Julia JLL is not complete yet
  • Docker devcontainer + vscode is fairly painless
  • Fully native setup is possible too
    • Use distro or Julia or build from source
    • Build libcxxwrap from source and set override file

Eigen, step by step

What is Eigen?

Setting up

  1. Directory with a .devcontainer with these files.
  2. Copy over CMakeLists.txt and foo.cpp as templates.
  3. Open in vscode and edit the names

Adding a type


            #include <jlcxx/jlcxx.hpp>
            #include <jlcxx/stl.hpp>

            #include <Eigen/Dense>


            JLCXX_MODULE define_julia_module(jlcxx::Module& mod)
            {
              mod.add_type<Eigen::MatrixXd>("MatrixXd");
            }
          

            module Eigen

            using CxxWrap

            @wrapmodule "/workspaces/eigen-wrapper/build/lib/libjleigen"

            function __init__()
              @initcxx
            end

            export MatrixXd

            end # module

          

Adding some methods


            .method("cols", &Eigen::MatrixXd::cols)
            .method("rows", &Eigen::MatrixXd::rows)
            .method("norm", &Eigen::MatrixXd::norm)
          

Adding a constructor


            .constructor<int64_t, int64_t>()
          

Let's add another one


            // fails
            .method("setConstant", &Eigen::MatrixXd::setConstant);
          

            // Cast
            .method("setConstant", static_cast<Eigen::MatrixXd& (Eigen::MatrixXd::*)(const double&)>(&Eigen::MatrixXd::setConstant));
          

            // Or use a lambda
            .method("setConstant", [](Eigen::MatrixXd& m, double x) { return m.setConstant(x); });
          

Adding methods to Base from C++


          mod.set_override_module(jl_base_module);
          mod.method("getindex", [](const Eigen::MatrixXd& m, int_t i, int_t j) { return m(i-1,j-1); });
          mod.method("setindex!", [](Eigen::MatrixXd& m, double value, int_t i, int_t j) { m(i-1,j-1) = value; });
          mod.unset_override_module();

          mod.method("toString", [] (const Eigen::MatrixXd& m)
          {
            std::stringstream stream;
            stream << m;
            return stream.str();
          });
          

Setting a base type


            mod.add_type<Eigen::MatrixXd>("MatrixXd", jlcxx::julia_type("AbstractMatrixXd"))
          

            const AbstractMatrixXd = AbstractMatrix{Float64}
          

            # Complete the interface
            Base.size(m::MatrixXd) = (rows(m),cols(m))
            Base.IndexStyle(::Type{<:MatrixXd})=IndexCartesian()
          

Templates


          namespace jleigen
          {
            struct WrapMatrix
            {
              template<typename TypeWrapperT>
              void operator()(TypeWrapperT&& wrapped)
              {
                using WrappedT = typename TypeWrapperT::type;
                using ScalarT = typename WrappedT::Scalar;
                wrapped.template constructor<Eigen::Index, Eigen::Index>();
                wrapped.method("cols", &WrappedT::cols);
                wrapped.method("rows", &WrappedT::rows);
                wrapped.method("norm", &WrappedT::norm);
                wrapped.method("setConstant", static_cast<WrappedT& (WrappedT::*)(const ScalarT&)>(&WrappedT::setConstant));

                wrapped.module().set_override_module(jl_base_module);
                wrapped.module().method("getindex", [](const WrappedT& m, int_t i, int_t j) { return m(i-1,j-1); });
                wrapped.module().method("setindex!", [](WrappedT& m, ScalarT value, int_t i, int_t j) { m(i-1,j-1) = value; });
                wrapped.module().unset_override_module();

                wrapped.module().method("toString", [] (const WrappedT& m)
                {
                  std::stringstream stream;
                  stream << m;
                  return stream.str();
                });
              }
            };
          }

          namespace jlcxx
          {

          template<typename T>
          struct BuildParameterList<Eigen::Matrix<T, Eigen::Dynamic, Eigen::Dynamic>>
          {
            typedef ParameterList<T> type;
          };

          }


          JLCXX_MODULE define_julia_module(jlcxx::Module& mod)
          {
            using jlcxx::Parametric;
            using jlcxx::TypeVar;
            mod.add_type<Parametric<TypeVar<1>>>("Matrix", jlcxx::julia_type("AbstractMatrix"))
              .apply<Eigen::Matrix<double, Eigen::Dynamic, Eigen::Dynamic>, Eigen::Matrix<float, Eigen::Dynamic, Eigen::Dynamic>>(jleigen::WrapMatrix());
          }

        

Enum types and constants


            mod.set_const("Dynamic", int_t(Eigen::Dynamic));
  
            mod.add_bits<Eigen::StorageOptions>("StorageOptions", jlcxx::julia_type("CppEnum"));
            mod.set_const("ColMajor", Eigen::ColMajor);
            mod.set_const("RowMajor", Eigen::RowMajor);
          

Templates, completed


            struct ApplyMatrix
            {
              template<typename T, typename Rows, typename Cols, typename Storage> using apply = Eigen::Matrix<T, Rows::value, Cols::value, Storage::value>;
            };

            template<typename T, int NRows, int NCols, int S>
              struct BuildParameterList<Eigen::Matrix<T, NRows, NCols, S>>
              {
                using type = ParameterList<T, std::integral_constant<int_t, NRows>, std::integral_constant<int_t, NCols>, std::integral_constant<Eigen::StorageOptions, Eigen::StorageOptions(S)>>;
              };
              
              }
              
              
              JLCXX_MODULE define_julia_module(jlcxx::Module& mod)
              {
                mod.set_const("Dynamic", int_t(Eigen::Dynamic));
                
                mod.add_bits<Eigen::StorageOptions>("StorageOptions", jlcxx::julia_type("CppEnum"));
                mod.set_const("ColMajor", Eigen::ColMajor);
                mod.set_const("RowMajor", Eigen::RowMajor);
              
                using jlcxx::Parametric;
                using jlcxx::TypeVar;
                using jlcxx::ParameterList;
              
                using scalar_types = ParameterList<float, double>;
                using sizes = ParameterList<std::integral_constant<int, 2>, std::integral_constant<int, 4>, std::integral_constant<int, Eigen::Dynamic>>;
                using storage = ParameterList<std::integral_constant<int, Eigen::ColMajor>, std::integral_constant<int, Eigen::RowMajor>>;
              
                mod.add_type<Parametric<TypeVar<1>, TypeVar<2>, TypeVar<3>, TypeVar<4>>>("Matrix", jlcxx::julia_type("AbstractEigenMatrix"))
                  .apply_combination<jleigen::ApplyMatrix, scalar_types, sizes, sizes, storage>(jleigen::WrapMatrix());
              }
          

Inheritance


          class ExtendedMatrix : public Eigen::Matrix<double,4,4>
            {
            public:
                using base_t = Eigen::Matrix<double,4,4>;
                ExtendedMatrix() : base_t() { setConstant(1.0); }
            
                // This constructor allows you to construct ExtendedMatrix from Eigen expressions
                template<typename OtherDerived>
                ExtendedMatrix(const Eigen::MatrixBase<OtherDerived>& other)
                    : base_t(other)
                { }
            
                // This method allows you to assign Eigen expressions to ExtendedMatrix
                template<typename OtherDerived>
                ExtendedMatrix& operator=(const Eigen::MatrixBase <OtherDerived>& other)
                {
                    this->base_t::operator=(other);
                    return *this;
                }
            };

          namespace jlcxx
          {
          
          template<> struct SuperType<jleigen::ExtendedMatrix> { using type = Eigen::Matrix<double,4,4>; };
          
          }

          mod.add_type<jleigen::ExtendedMatrix>("ExtendedMatrix", jlcxx::julia_base_type<Eigen::Matrix<double,4,4>>());
        

Smart pointers


            #include <jlcxx/smart_pointers.hpp>
            mod.method("make_shared", [] ()
            {
              return std::make_shared<Eigen::MatrixXd>(3,3);
            });
          

Standard vector


            mod.method("processvec", [] (const std::vector<Eigen::MatrixXd>& v)
            {
              for(auto val : v)
              {
                std::cout << val << std::endl;
              }
            });
          

Questions?