简体   繁体   中英

Calling C++ function having std::vectors as input and output parameters from Julia

I am new to Julia and and I am trying to access my C++ code from Julia. More precisely, I am trying to call a C++ function from Julia using Cxx. The input and output parameters of the C++ function are std::vectors, see the function compute_sum in the example below:

#include <vector>
#include <iostream>

std::vector< int > compute_sum( const std::vector< std::vector<int> >& input )
{
    std::vector< int > resut( input.size() , 0 );
    for ( std::size_t i = 0 ; i != input.size() ; ++i )
    {
        for ( std::size_t j = 0 ; j != input[i].size() ; ++j )
        {
            resut[i] += input[i][j];
        }
    }
    return resut;
}

void simple_function( int i )
{
    std::cout << "The numbers is : " << i << std::endl;
}

Assuming this function is stored as code.cpp I am compiling it to a shared object code.so using:

 g++ -shared -fPIC code.cpp -o code.so

and as a result I obtain a file code.so

Having this, I run Julia in the same folder as code.so. My version of Julia is 0.6.2. Then I import Cxx and the code.so file using:

julia> using Cxx
julia> const path_to_lib = pwd()
julia> addHeaderDir(path_to_lib, kind=C_System)
julia> Libdl.dlopen(path_to_lib * "/code.so", Libdl.RTLD_GLOBAL)
Ptr{Void} @0x00000000044bda30
julia> cxxinclude("code.cpp")

In odder to test if the process is successful I am calling the simple_function and obtain the correct results:

julia> @cxx simple_function(1234)
The numbers is : 1234

Then I want to call compute_sum function. For that I need somehow to create, or convert Julia vector into C++ std::vector< std::vector >. I am trying the following:

julia> cxx" std::vector< std::vector<int> > a;"
true
julia> icxx" a.push_back( std::vector<int>(1,2) ); "
julia> icxx" a.push_back( std::vector<int>(1,3) ); "
julia> icxx" a.push_back( std::vector<int>(1,4) ); "
julia> icxx" a.size(); "
0x0000000000000003

So I assume that the vector is created in a correct way. Then I trying to call the function with it, but I fail:

julia> @cxx compute_sum(a)
ERROR: UndefVarError: a not defined
julia> @cxx compute_sum("a")
ERROR: Got bad type information while compiling Cxx.CppNNS{Tuple{:compute_sum}} (got String for argument 1)
julia> icxx " compute_sum(a);"
ERROR: syntax: extra token """ after end of expression

Could anyone help me please with the following question(s):

  1. How to call compute_sum function from Julia? I am happy to use any technique (not necessary Cxx) that works and is reasonably fast.
  2. How to convert the result of compute_sum to Julia array?

Thank you very much!

pawel

Because you are willing to work on Julia arrays , I assume that you would like to work on matrices , ie , arrays having constant lengths in the dimensions. For this reason, I would suggest you do not use vector s of vector s, but instead use just vector s. Then, you should remember that Julia uses column-major arrays, whereas in C/C++, the memory layout is row-major .

Below, you can find a templated version of your code using iterators. This way, you can compile compute_sum for use in C++ with your favourite std::vector s. Or else, you can ask your compiler to generate the appropriate code with pointers, to be able to use with other languages, such as Julia.

#include <cstdint>
#include <iterator>
#include <vector>

template <class RandomIt, class OutputIt>
OutputIt compute_sum(const std::uint64_t nrows, RandomIt xbegin, RandomIt xend,
                     OutputIt rbegin) {
  const std::size_t ncols{std::distance(xbegin, xend) / nrows};
  typename std::iterator_traits<OutputIt>::value_type sum{0};
  for (std::size_t row = 0; row < nrows; row++) {
    for (std::size_t col = 0; col < ncols; col++)
      sum += xbegin[col * nrows + row];

    *rbegin++ = sum;
    sum = 0;
  }
  return rbegin;
}

/* you can use the above code in your C++ applications as follows */
// int main() {
//   std::vector<int> matrix{1, 2, 3, 4, 5,
//                           6, 7, 8, 9}; /* 3x3 matrix in column-major */
//   std::vector<int> result(3);
//   compute_sum(3, std::begin(matrix), std::end(matrix), std::begin(result));
//   return 0;
// }

/* or, ask your compiler to generate code with C linkage (no name mangling) */
extern "C" {
void compute_sum(const std::uint64_t m /* use fixed-size integers */,
                 const std::uint64_t n /* use fixed-size integers */,
                 const std::int64_t *xbegin /* use fixed-size integers */,
                 std::int64_t *rbegin /* use fixed-size integers */) {
  compute_sum(m, xbegin, xbegin + m * n, rbegin);
}
}

then, compile the code as usual:

g++ -Wall -std=c++11 -O3 -fPIC -shared code.cpp -o code.so

Then, use Julia's capabilities to call the compiled C code:

const libhandle = Libdl.dlopen(joinpath(pwd(), "code.so"))
const funhandle = Libdl.dlsym(libhandle, :compute_sum)

function compute_sum(A::Matrix{Int64})
    result = Vector{Int64}(size(A, 1))
    ccall(funhandle, Void, (UInt64, UInt64, Ref{Int64}, Ref{Int64}),
          size(A, 1), size(A, 2), A, result)
    return result
end

result = compute_sum(ones(Int64, 5, 6)) # prints Int64[6, 6, 6, 6, 6]

I hope this helps. Cheers!

The trick is to use the icxx string macro, this allows interpolating Julia variables using $ . Here is a full example:

using Cxx

cxx"""
#include <iostream>
#include <vector>

std::vector<int> compute_sum(const std::vector<std::vector<int>> &input)
{
  std::vector<int> result(input.size(), 0);
  for (std::size_t i = 0; i != input.size(); ++i)
  {
    for (std::size_t j = 0; j != input[i].size(); ++j) // corrected to ++j here
    {
      result[i] += input[i][j];
    }
  }
  return result;
}
"""

cxx_v = icxx"std::vector<std::vector<int>>{{1,2},{1,2,3}};"
println("Input vectors:")
for v in cxx_v
  println("  ", collect(v))
end

cxx_sum = icxx"compute_sum($cxx_v);"
println("Cxx sums: $(collect(cxx_sum))")

Running this in Julia should print:

Input vectors:
  Int32[1, 2]
  Int32[1, 2, 3]
Cxx sums: Int32[3, 6]

To do this using a shared library, create vector.hpp like this:

#include <vector>
std::vector<int> compute_sum(const std::vector<std::vector<int>> &input);

vector.cpp :

#include "vector.hpp"

std::vector<int> compute_sum(const std::vector<std::vector<int>> &input)
{
     // same as before
}

Compile:

g++ -shared -fPIC -o libvector.so vector.cpp

In Julia:

using Cxx

const path_to_lib = pwd()
addHeaderDir(path_to_lib, kind=C_System)
Libdl.dlopen(joinpath(path_to_lib, "libvector"), Libdl.RTLD_GLOBAL)
cxxinclude("vector.hpp")

cxx_v = icxx"std::vector<std::vector<int>>{{1,2},{1,2,3}};"
println("Input vectors:")
for v in cxx_v
  println("  ", collect(v))
end

cxx_sum = icxx"compute_sum($cxx_v);"
println("Cxx sums: $(collect(cxx_sum))")

I would like to thank Arda Aytekin and Bart Janssens for their great help and suggestions. Both solutions works perfectly and I would like to mark them both as an answer to my question, but it seems I can only mark one answer...
In the next days I will run speed comparison test to see if one the solution which is based on pure C interface is faster or not to the one using Cxx. I will update you once this is ready.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM