Aims to be like Boost
-
The Boost C++ Libraries are a set of libraries for the C++ programming language that provides support for tasks and structures such as linear algebra, pseudorandom number generation, multithreading, image processing, regular expressions, and unit testing. It contains 164 individual libraries…
the idea
-
write C++ code to wrap interfaces with Julia
- then one-liner of Julia access wrapped code
the mechanism behind this package
-
functions and types are registered in C++
-
that is compiled into a DLL -> the DLL is loaded into Julia
-
Julia uses the data provided through a C interface
-
to generate functions accessible from Julia the functions are passed as RAW FUNCTION POINTERS, for regular cpp funcs that do not need argument or return type conversion, or std::functions, for lambda expressions and auto-conversion of arguments and return types.
- Julia wraps all this automatically
-
to generate functions accessible from Julia the functions are passed as RAW FUNCTION POINTERS, for regular cpp funcs that do not need argument or return type conversion, or std::functions, for lambda expressions and auto-conversion of arguments and return types.
-
Julia uses the data provided through a C interface
-
that is compiled into a DLL -> the DLL is loaded into Julia
-
write C++ code to wrap interfaces with Julia
Hello World Example
std::string greet()
{
return "hello, world";
}
#include "jlcxx/jlcxx.hpp"
JLCXX_MODULE define_julia_module(jlcxx::Module& mod)
{
mod.method("greet", &greet);
}
- compile the code into shared lib `libhello.so`
recommended way to compile the C++ code
- use CMake to discover `libcxxwrap-julia` and the julia libs
project(TestLib)
cmake_minimum_required(VERSION 3.5)
set(CMAKE_MACOSX_RPATH 1)
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(testlib SHARED testlib.cpp)
target_link_libraries(testlib JlCxx::cxxwrap_julia)
install(TARGETS
testlib
LIBRARY DESTINATION lib
ARCHIVE DESTINATION lib
RUNTIME DESTINATION lib)
the following commands can be used to to build the CMakeLists.txt
mkdir build && cd build
cmake -DCMAKE_BUILD_TYPE=Release -DCMAKE_PREFIX_PATH=/path/to/libcxxwrap-julia-prefix /path/to/sourcedirectory
cmake --build . --config Release
path for CMAKEPREFIXPATH can be obtained using `CxxWrap.prefixpath()` in Julia
module CppHello
using CxxWrap
@wrapmodule(() -> joinpath("path/to/libbuild","libhello"))
function __init__()
@initcxx
end
end
# try it out
@show CppHello.greet()
Module entry point
JLCXXMODULE definejuliamodule(jlcxx::Module& mod)
-
defines module entry point
-
each module has its own entry point
JLCXX_MODULE define_module_a(jlcxx::Module& mod) { // add stuff for A } JLCXX_MODULE define_module_b(jlcxx::Module& mod) { // add stuff for B }
-
module A
using CxxWrap
@wrapmodule(() -> "mylib.so",:define_module_a)
end
module B
using CxxWrap
@wrapmodule(() -> "mylib.so",:define_module_b)
end
In specific cases, it may also be necessary to specify dlopen flags such as RTLDGLOBAL. These can be supplied in a third, optional argument to @wrapmodule,
Advanced Examples and Notes
the function call overhead is the same as `ccall` on a C func if the C++ func is a regular func and does not require argument conversion
- when `std::function` is used extra overhead is expected…
Exposing Classes
struct World
{
World(const std::string& message = "hello") : msg(message){}
void set(const std::string& msg) {this->msg = msg; }
std::string greet() {return msg; }
std::string msg;
~World() { std::cout << "destry world" << msg << std::endl; }
};
JLCXX_MODULE define_module_world(jlcxx::Module& mod)
{
types.add_type<World>("World")
.constructor<const std::string&>()
.method("set", &World::set)
.method("greet", &World::greet);
}
// `constructor` creates a finalizer
// disable with the arg `jlcxx::finalize_policy::no`
types.add_type<World>("World")
.constructor<const std::string&>(jlcxx::finalize_policy::no);
w = CppTypes.World()
CppTypes.greet(w) # "hello"
CppTypes.set(w, "hi")
# the add_type function actually builds 2 julia types related to World
abstract type World end
# boxed type
mutable struct WorldAllocated <: World
cpp_object::Ptr{Cvoid}
end
# variable w may get deleted when out of scope
# the caller must manage the lifetime the of the result
greet(w::World) = ccall($fpointer, Any, (Ptr{Cvoid}, WorldRef), $thunk, cconvert(WorldRef, w))
# here cconvert from WorldAllocated to WorldRef is defined automatically
WARNING: the ordering of C++ code matters!!! types used as function arguments or return types must be added before they are used in a function
Average, Sum, and Multiple Averages
#include <iostream>
#include <vector>
#include <string>
#include "jlcxx/jlcxx.hpp"
#include "jlcxx/functions.hpp"
#include "jlcxx/stl.hpp"
using namespace std;
// primitive data passed and retrieved
double cpp_avg(int a, int b) {
return (double) (a+b)/2;
}
string cpp_sum(std::vector< double > data) {
double total = 0.0;
double nelems = data.size();
for (int i = 0; i< nelems; i++){
total += data[i];
}
std::stringstream ss;
ss << "the sum is " << total << endl;
return ss.str();
}
std::vector< double > cpp_mult_avgs(std::vector < std::vector< double >> data) {
std::vector <double> avgs;
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];
}
avgs.push_back(isum/ni);
}
return avgs
}
JLCXX_MODULE define_julia_module(jlcxx::Module& mod) {
mod.method("cpp_avg", &cpp_avg);
mod.method("cpp_sum", &cpp_sum);
mod.method("cpp_mult_avgs", &cpp_mult_avgs);
}
# 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]
wrapping GeographicLib C++ for GeographicModels.jl
using GeographicLib in C++
#include <GeographicLib.LambertConformalConic.hpp>
using namespace GeographicLib;
- build code with CMake
find_package (GeographicLib REQUIRED)
include_directories (${GeographicLib_INCLUDE_DIRS})
add_executable (program source1.cpp source2.cpp)
target_link_libraries (program ${GeographicLib_LIBRARIES})
mkdir BUILD
cd BUILD
cmake -D CMAKE_INSTALL_PREFIX="/tmp/testgeog" \
-S . -B BUILD
make -C BUILD -j4