CxxWrap tutorial - matrix input and output args

Hi All,

I’m trying to create Julia bindings using CxxWrap for a project (GitHub - frli8848/DREAM: The DREAM toolbox is a free open source acoustic field simulation tool). Is there a good tutorial on how to do this? I basically want to:

  • Parse each input arg and check for correctness, for example matrix dims (number of rows, cols) etc.
  • Allocate space for the output matrix,
  • call my C++ function,
  • return the matrix to Julia.

I want to have a pointers to the (Julia) matrix data buffers (double arrays) which I can pass directly to my functions and avoid any copying of possible large buffers.

I assume Fortran style matrices (column major). I have looked at the CxxWrap README:s on github and some julia header files (array.hpp etc) but I have not found any good way to do this.

Regards,

/Fredrik

PS. I have bindings for MATLAB, Octave, and Python

I tried something like this (similar to the pybind11 code that I use for the Python bindings):

jl::ArrayRef<double> jl_dreamrect(jl::ArrayRef<double> *jl_ro,
                                     jl::ArrayRef<double> *jl_geom_par,
                                     jl::ArrayRef<double> *jl_s_par,
                                     jl::ArrayRef<double> *jl_delay,
                                     jl::ArrayRef<double> *jl_m_par,
                                     std::string err_level_str)
{

-snip-

  // Create an output matrix for the impulse responses
  jl_value_t *matrix_type = jl_apply_array_type((jl_value_t*) jl_float64_type,2);
  jl_array_t *jl_h_mat = jl_alloc_array_2d(matrix_type, nt, no);
  double *h = (double *) jl_array_data(jl_h_mat);
  //auto jl_h_mat = jl::Array<double>(nt*no);

-snip-

  return jl_h_mat;
}

JLCXX_MODULE define_julia_module(jlcxx::Module& mod)
{
  mod.method("dreamrect", &jl_dreamrect);
}

And then try to load the .so using:

module dr
using CxxWrap
@wrapmodule(joinpath("/home/fl/projects/DREAM/build/julia","dreamrect"))
function __init__()
    @initcxx
end
end

and I got this Julia output:

julia> include("/home/fl/projects/DREAM/julia/tests/test_dreamrect.jl")
WARNING: replacing module dr.
ERROR: LoadError: MethodError: no method matching rem(::String, ::Tuple{Int64, Int64, Float64})
Closest candidates are:
  rem(::Any, ::Any, ::RoundingMode{:ToZero}) at div.jl:83
  rem(::Any, ::Any, ::RoundingMode{:Down}) at div.jl:84
  rem(::Any, ::Any, ::RoundingMode{:Up}) at div.jl:85
  ...
Stacktrace:
 [1] include(fname::String)
   @ Base.MainInclude ./client.jl:476
 [2] top-level scope
   @ REPL[15]:1
in expression starting at /home/fl/projects/DREAM/julia/tests/test_dreamrect.jl:28

I have no idea what this means?

I got it to work by looking closer to the julia.h and array.hpp header files. Apparently Julia is using different data types for vectors and matrices (why?) and one should not use pointers to ArrayRef<double>s as function arguments. So the glue function should be:

jl::ArrayRef<double, 2> jl_dreamrect(jl::ArrayRef<double, 2> jl_ro,
                                     jl::ArrayRef<double> jl_geom_par,
                                     jl::ArrayRef<double> jl_s_par,
                                     jl::ArrayRef<double> jl_delay,
                                     jl::ArrayRef<double> jl_m_par,
                                     std::string err_level_str);

where jl::ArrayRef<double, 2> is a (2D) matrix and jl::ArrayRef<double is a vector. To get vector and matrix dims, and data pointers one can do:

// Vector
 dream_idx_type get_len(const jl::ArrayRef<double> &jl_arg) {
    auto len = jl_array_len(jl_arg.wrapped());
    return (dream_idx_type) len;
  };

  // Matrix
  dream_idx_type get_m(const jl::ArrayRef<double, 2> &jl_arg) {
    auto m = jl_arg.wrapped()->nrows;
    return (dream_idx_type) m;
  };

  // Matrix
  dream_idx_type get_n(const jl::ArrayRef<double, 2> &jl_arg) {
    auto n = jl_arg.wrapped()->ncols;
    return (dream_idx_type) n;
  };


  // Vector
  double* get_data(const jl::ArrayRef<double> &jl_arg) {
    return static_cast<double*>(jl_array_data(jl_arg.wrapped()));
  };

  // Matrix
  double* get_data(const jl::ArrayRef<double, 2> &jl_arg) {
    return static_cast<double*>(jl_array_data(jl_arg.wrapped()));
  };