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
4 Likes
Would probably be appreciated if you made a PR to include these nice examples into the documentation/Readme of CxxWrap