Can someone provide a simple example of using CxxWrap?

Hello, I am trying to look at CxxWrap as the super-easy Cxx.jl package seems lost :-(((((

However, as my usage of CMAKE is very limited, I am unable to follow even the example in the home page (and there is no separate doc site) as it doesn’t go in the details on how to compile (and I have an error when I try with the few details given - I am in Ubuntu Linux and I have gcc/cmake installed)

Can someone provide the most basic way (using no modules if these are not required) to compile a function (let’s say sum_me(x,y) -> Int) in C++ using CxxWrap and calling it from Julia?

I think the example could be simpler to follow if everything is done from Julia, e.g. using write for the C+ code and run for the commands to run, so for example the CxxWrap path could be a variable in the command…

More in detail, this is the error I get when trying to replicate the example in the Readme:

using Pkg
cd(@__DIR__)
Pkg.activate(".")

using CxxWrap
cxxpath    = CxxWrap.prefix_path()
srcpath    = pwd()
buildpath  = joinpath(sourcepath,"build")

write("hello.cpp",
"""
std::string greet()
{
   return "hello, world";
}
""")

write("hello.h",
"""
#include "jlcxx/jlcxx.hpp"

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

mkpath(buildpath)
cd(buildpath)

cmd1 = `cmake -DCMAKE_BUILD_TYPE=Release -DCMAKE_PREFIX_PATH=$cxxpath $srcpath`
cmd2 = `cmake --build . --config Release`
run(cmd1) # process error

When I try from command line the error is : make: invalid option -- 'D'

I have tried to remove the D and to use two hypens (--), but still no success…

I’ve fixed your example, and this works on my Ubuntu 22.04 machine, producing build/lib/libhello.so.

using Pkg
cd(@__DIR__)
Pkg.activate(".")

using CxxWrap
cxxpath    = CxxWrap.prefix_path()
srcpath    = pwd()
buildpath  = joinpath(srcpath,"build")

write("hello.cpp",
"""
#include <string>
#include "jlcxx/jlcxx.hpp"

std::string greet()
{
   return "hello, world";
}

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

write("CMakeLists.txt",
"""
project(hello)

cmake_minimum_required(VERSION 3.5)
set(CMAKE_LIBRARY_OUTPUT_DIRECTORY "\${CMAKE_BINARY_DIR}/lib")

find_package(JlCxx)
get_target_property(JlCxx_location JlCxx::cxxwrap_julia LOCATION)
get_filename_component(JlCxx_location \${JlCxx_location} DIRECTORY)
set(CMAKE_INSTALL_RPATH "\${CMAKE_INSTALL_PREFIX}/lib;\${JlCxx_location}")

message(STATUS "Found JlCxx at \${JlCxx_location}")

add_library(hello SHARED hello.cpp)

target_link_libraries(hello JlCxx::cxxwrap_julia)

install(TARGETS
  hello
LIBRARY DESTINATION lib
ARCHIVE DESTINATION lib
RUNTIME DESTINATION lib)
""")

mkpath(buildpath)
cd(buildpath)

cmd1 = `cmake -DCMAKE_BUILD_TYPE=Release -DCMAKE_PREFIX_PATH=$cxxpath $srcpath`
cmd2 = `cmake --build . --config Release`
run(cmd1)
run(cmd2)

2 Likes

Thank you. This indeed works.
After the build, I can use the lib in julia with:

# Load the module and generate the functions
module CppHello
  using CxxWrap
  @wrapmodule(() -> joinpath(pwd(),"lib","libhello"))
  function __init__()
    @initcxx
  end
end

# Call greet and show the result
@show CppHello.greet()

This is awesome to port for full libraries, but is there a simpler way for single functions ? Can I compile them and link them without using make/CMake ? Or at least without writing the CMakeLists.txt configuration file ?
And on the julia side, do I necessarily need to wrap the C++ call in a module ?

Answering the second question, yes, I can just use

@wrapmodule(() -> joinpath(pwd(),"lib","libhello"))
@show greet()

Instead of creating the module CppHello…

1 Like

I think I got it.

This compiles the “lib”, loads it and calls it in the simplest way:

using Pkg
cd(@__DIR__)
Pkg.activate(".")
using CxxWrap

write("hello.cpp",
"""
#include <string>
#include "jlcxx/jlcxx.hpp"

std::string greet()
{
   return "hello, world";
}

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

cxx_include_path   = joinpath(CxxWrap.prefix_path(),"include")
julia_include_path = joinpath(Sys.BINDIR,"..","include","julia")

# Compile 
cmd = `g++ -shared -fPIC -o libhello.so -I $julia_include_path -I $cxx_include_path  hello.cpp`
run(cmd)
# Generate the function for Julia
@wrapmodule(() -> joinpath(pwd(),"libhello"))
# Call greet and show the result
@show greet()
4 Likes

A more extensive set of examples. They all focus on the CxxWrap API rather than on how to build the C++ app, and are easily repeatable:

using Pkg
cd(@__DIR__)
Pkg.activate(".")
using CxxWrap

write("libcpp.cpp",
"""
#include <string>
#include <iostream>
#include <vector>

#include "jlcxx/jlcxx.hpp"
#include "jlcxx/functions.hpp"
#include "jlcxx/stl.hpp"

using namespace std;

// Fist example: hello world without any data exchange between caller and called function.. 
void cpp_hello() {
  std::cout << "Hello world from a C++ function" << std::endl;
  return;
}

// Second example: primitive data passed and retrieved..
double cpp_average(int a, int b) {
  return (double) (a+b)/2;
}

// Third example: data relate to STD objects...
string cpp_sum (std::vector< double > data) {
  // Compute sum..
  double total = 0.0;
  double nelements = data.size();
  for (int i = 0; i< nelements; i++){
      total += data[i];
  }
  std::stringstream ss;
  ss << "The sum is " << total << endl;
  return ss.str();
}

// 4th example: complex, nested STD objects...
std::vector<double> cpp_multiple_averages (std::vector< std::vector<double> > data) {
  // Compute average of each element..
  std::vector <double> averages;
  for (int i = 0; i < data.size(); i++){
    double isum = 0.0;
    double ni= data[i].size();
    for (int j = 0; j< data[i].size(); j++){
      isum += data[i][j];
    }
    averages.push_back(isum/ni);
  }
  return averages;
}

JLCXX_MODULE define_julia_module(jlcxx::Module& mod) {
  mod.method("cpp_hello", &cpp_hello);
  mod.method("cpp_average", &cpp_average);
  mod.method("cpp_sum", &cpp_sum);
  mod.method("cpp_multiple_averages", &cpp_multiple_averages);
}
""")

cxx_include_path   = joinpath(CxxWrap.prefix_path(),"include")
julia_include_path = joinpath(Sys.BINDIR,"..","include","julia")

# Compile 
cmd = `g++ --std=c++20 -shared -fPIC -o libcpp.so -I $julia_include_path -I $cxx_include_path  libcpp.cpp`
run(cmd)
# Generate the functions for Julia
# Once the lib is wrappd you can't wrap it again nor modify the C++ code, you need to restart Julia
@wrapmodule(() -> joinpath(pwd(),"libcpp"))
# Call the functions
cpp_hello() # Prints "Hello world from a C++ function"
avg        = cpp_average(3,4) # 3.5
data_julia = [1.5,2.0,2.5]
data_sum   = cpp_sum(StdVector(data_julia)) # Returns "The sum is 6"
typeof(data_sum)
typeof(data_sum) <: AbstractString
data_julia = [[1.5,2.0,2.5],[3.5,4.0,4.5]]
data       = StdVector(StdVector.(data_julia))
data_avgs  = cpp_multiple_averages(data) # [2.0, 4.0]
typeof(data_avgs)
typeof(data_avgs) <: AbstractArray
data_avgs[1]

By the way, this is going to the second edition of the book Julia Quick Syntax Reference that I am preparing with Apress :slight_smile:

4 Likes

Would probably be appreciated if you made a PR to include these nice examples into the documentation/Readme of CxxWrap :slight_smile: