JuliaInterop / CxxWrap.jl

Package to make C++ libraries available in Julia
Other
417 stars 67 forks source link

julia Array -> c++ of map_type #255

Open TheidenHD opened 4 years ago

TheidenHD commented 4 years ago

Hey, I need to convert an array from Julia to C++ and back. The array contains objects that I map't from C++. For primitive data types (like Integer) it works fine. Thanks.

rgcv commented 4 years ago

I'm not entirely sure if this is what you want, but what I can interpret from the issue is you want to get an array of a C++ mapped typed into C++ and return a julia array, is that correct?

If so, there is such a class in libcxxwrap-julia that allows one to represent julia arrays in C++, called ArrayRef. You can see some usage of it in the package's README.md.

There's a caveat when you're using types other than primitive ones: The element type of the array is in fact CxxRef{T} where T is a non-primitive type you've mapped. Let me hopefully clarify with an example. Let's assume we have the following:

#include <jlcxx/module.hpp>

class A{
  int _val;
public:
  A(int val) : _val(val) {}
  int val() { return _val; }
  void inc() { _val++; }
};

JLCXX_API define_julia_module(jlcxx::Module& m) {
  m.add_type<A>("A")
   .constructor<int>
   .method("val", &A::val)
   .method("inc!", &A::inc)
   ;
  m.method("inc!", [](jlcxx::ArrayRef<A> as) {
    for (A& a : as) a.inc();
  });
}
module M
using CxxWrap

@wrapmodule("/path/to/libmyclass.so")
__init__() = @initcxx

export A, val, inc!
Base.show(io::IO, a::A) = print(io, "A($(val(a)))") # convenience

end

In order to use the inc! method for arrays of A, you would have to obtain an array of references of A. You can achieve that by applying CxxRef to every element, either via broadcasting, a loop, a map, or whatever you'd prefer. Here's an example way of doing so:

julia> using M
julia> as = A.([1, 2, 3])
3-element Array{M.AAllocated,1}:
 A(1)
 A(2)
 A(3)
julia> val(as[1])
1
julia> import M.CxxWrap: CxxRef
julia> inc!(CxxRef.(as)) # here's the kicker
julia> val.(as)
3-element Array{Int64,1}:
 2
 3
 4

If you'd like, you can return the array itself as is and you would obtain an Array{CxxRef{A},1}. Changing the inc! method for an array of As:

m.method("inc!", [](jlcxx::ArrayRef<A> as) {
  for (A& a : as) a.inc();
  return as;
});

This would now be an example output of inc!:

julia> inc!(CxxRef.(as))
3-element Array{CxxRef{A},1}:
 CxxRef{A}(Ptr{A} @0xbdd1877677bfd27e)
 CxxRef{A}(Ptr{A} @0x963bfde3b19e92ec)
 CxxRef{A}(Ptr{A} @0x816b2597e03cfb3d)

If you'd like, you could create a julia array using jlcxx::Array instead. Looking back at inc! one more time:

m.method("inc!", [](jlcxx::ArrayRef<A> as) {
  jlcxx::Array<T> jlarr;
  for (A& a : as) {
    a.inc();
    jlarr.push_back(a);
  }
  return jlarr;
});

Now, instead, you'd get this:

julia> inc!(CxxRef.(as))
3-element Array{M.AAlocated,1}:
 A(2)
 A(3)
 A(4)

Excuse me if any of the examples' syntax or outputs are wrong, I did it mostly from memory.

EDIT: Alternatively, you could have a look at CxxWrap's STL capabilities and just use something like std::vector instead. Take a look at the README, maybe scour through some code and issues around here if you get stuck.