Many libraries have been created in the past but are no longer functional or maintained.
It’s inevitable since they are community-based developments…
Libraries that are working on my local machine (Linux/macOS) as of 2024
It uses C++20 features extensively and aims to support the newest Julia version, rather than focusing on backwards compatibility.
Libraries that are working on my local machine (Linux/macOS) as of 2024
This is what we will discuss today
bash
, make
, cmake
https://github.com/JuliaInterop/libcxxwrap-julia
A function that returns the received string as is
git clone https://github.com/terasakisatoshi/cmake-playground.git
cd cmake-playground/cxxwrap1
docker build -t cxxwrap1 .
docker run --rm -it -v $PWD:/work -w /work cxxwrap1 bash -c 'bash build.sh && julia callcxx.jl'
If you see logs like the following, it’s OK
greet
FunctionPrepare the following C++ code
# build.sh の一部改変
SHARED_LIB_EXT=".so" # Linux
SHARED_LIB_EXT=".dylib" # Apple
# Get Julia installation paths
rm Manifest.toml
julia --project -e 'using Pkg; Pkg.instantiate()'
JL=`julia --project -e 'joinpath(Sys.BINDIR, "..") |> abspath |> print'`
PREFIX=`julia --project -e 'using CxxWrap; CxxWrap.prefix_path() |> print'`
# Build shared library with appropriate extension
g++ -fPIC -shared -std=c++17 \
-I${PREFIX}/include/ \
-L${PREFIX}/lib/ \
-I${JL}/include/julia \
-L${JL}/lib \
-ljulia -lcxxwrap_julia hello.cpp -o libhello${SHARED_LIB_EXT}
-I
julia.h
, jlcxx/jlcxx.hpp
-L
libjulia
, libcxxwrap_julia
bash build.sh
libhello<extension>
is generated..so
, .dylib
, .dll
, etc. This shared library is loaded at runtime from the Julia sidegreet
is defined within the CppHello
moduleRepeated for emphasis
When wrapping a practical example (a large-scale C++ project), you will need to get along with CMake (a build management tool for source code).
CMakeLists.txt
, cmake ..
. You’ve seen them, right? That’s it.cmake-playground/cxxwrap2
CMakeLists.txt
cmake_minimum_required(VERSION 3.15)
project(cxxwrap2)
# とりあえず書いておく
find_package(JlCxx)
get_target_property(JlCxx_location JlCxx::cxxwrap_julia LOCATION)
get_filename_component(JlCxx_location ${JlCxx_location} DIRECTORY)
# 皆さんが触る箇所はここ
# hello という共有ライブラリを作るためのターゲットを定義
add_library(hello SHARED hello.cpp)
message(STATUS "Found JlCxx at ${JlCxx_location}")
# hello というターゲットは何に依存しているか(リンクすべきか)を記述
target_link_libraries(hello JlCxx::cxxwrap_julia)
jlcxx/jlcxx.hpp
, julia.h
julia.h
to compile hello.cpp
JlCxx::cxxwrap_julia
. Refer to herecmake
command.JlCxx
using find_package(JlCxx)
.
CXXWRAP_PREFIX
.-DCMAKE_PREFIX_PATH
option.export CMAKE_PREFIX_PATH=...
.# Get Julia installation paths
CXXWRAP_PREFIX=`julia --project -e 'using CxxWrap; CxxWrap.prefix_path() |> print'`
cmake -S . -B ./build -DCMAKE_BUILD_TYPE=Release -DCMAKE_PREFIX_PATH=$CXXWRAP_PREFIX
cmake --build ./build --config Release -j `nproc`
./build/libhello<extension>
will be created.In cmake-playground/cxxwrap4
, the standard directory structure of Julia is adopted.
./deps
directory.julia> using Pkg; Pkg.build()
.Pkg.build()
executes the Julia script deps/build.jl
.For example, in cmake-playground/cxxwrap4
, it is done as follows:
cmake
)../deps
).For those who are only interested in the Julia interface, building locally can be tough (high learning cost for setting up the environment). Using BinaryBuilder.jl, you can provide pre-built JLL packages (a pun on “Dynamic-Link Library”, with the J standing for Julia) like LibHello_jll
.
Although not maintained, the concept is still relevant (probably).
How to handle the part “Prepare code to connect C++ and Julia”.
In the context of numerical computation, it would be good if you can write a program that passes arrays and returns arrays, but there are few examples to try easily…
Therefore, cmake-playground/cxxwrap6.
Created an example that performs operations on arrays (1D, 2D arrays) with elements double(C++), Float64(Julia)
.
Someone please teach me how to wrap functions using templates.
Example of Doubling Elements
void inplace_twice(jlcxx::ArrayRef<double, 2> jlx) {
for (size_t i = 0; i < jlx.size(); i++) {
jlx[i] = 2 * jlx[i];
}
}
Calling the corresponding Julia function doubles each element of the two-dimensional array.
Refer to Const arrays in the README.md of CxxWrap.jl
const double* const_vector()
{
// static キーワードが重要
static double d[] = {1., 2., 3};
return d;
}
const double* const_matrix()
{
// static キーワードが重要
static double d[2][3] = {{1., 2., 3}, {4., 5., 6.}};
return &d[0][0];
}
// ...module definition skipped...
mymodule.method("const_vector", []() { return jlcxx::make_const_array(const_vector(), 3); });
mymodule.method("const_matrix", []() { return jlcxx::make_const_array(const_matrix(), 3, 2); });
In the above example, it seems to handle only fixed-length and fixed-size arrays?
Want to return a dynamic size?
// これは実行時にセグフォする. 辛い
mymodule.method("array", [] () {
jlcxx::Array<int> data{ };
data.push_back(1);
data.push_back(2);
data.push_back(3);
return data;
});
std::vector
can be returned. On the Julia side, it can be obtained as an instance of CxxWrap.StdVector
, a subtype of AbstractVector
.static Eigen::MatrixXd y;
and storing values in y.#include <Eigen/Dense>
// 要素を 3 倍にする
auto triple(jlcxx::ArrayRef<double, 2> jlx) {
size_t size0 = jl_array_dim(jlx.m_array, 0);
size_t size1 = jl_array_dim(jlx.m_array, 1);
// static キーワードをつけなければいけない
static Eigen::MatrixXd y;
auto x = Eigen::Map<Eigen::MatrixXd>(jlx.data(), size0, size1);
// Do something
y = 2 * x + x;
return jlcxx::make_julia_array(y.data(), size0, size1);
}
x::Matrix{Float64}
with different sizes as input.allows smooth transfer of numerical data held by Julia’s jlx
to the C++ side.
In the case of the Const Arrays example, it is understood as a 3x2 matrix on the Julia side.
If you don’t loop the column variable c
first, you won’t get intuitive results.
void f(jlcxx::ArrayRef<double, 2> jlx) {
size_t size0 = jl_array_dim(jlx.m_array, 0);
size_t size1 = jl_array_dim(jlx.m_array, 1);
std::cout << "[";
for(size_t r = 0; r < size0; r++){
for(size_t c = 0; c < size1; c++){
std::cout << jlx[r + size0 * c];
if (c == size1 - 1){
if (r != size0 - 1){
std::cout << "; ";
}
} else {
std::cout << " ";
}
}
}
std::cout << "]";
std::cout << std::endl;
}
The previous examples are convenient but cannot handle functions that return C++ classes.
You can do it using mod.add_type
.
The following can be executed as Julia code:
The wrapper is created here: EasyEigenInterface.jl/deps/src/jl_easy_eigen_interface.cpp
It’s good that it was created, but I don’t know how to utilize the MatrixXd type associated with EasyEigenInterface.jl
in other Julia libraries…
CxxWrap.StdVector
…It remains as a “super-strong self-made implementation.”
It hasn’t been fully tested yet, but it seems that the C++ implementation of high-energy physics called Geant4 automatically generates wrapper functions.
It seems amazing but I don’t really understand it (small impression) I didn’t have time, so I hope someone writes an explanation.
Congratulations. You don’t need to read this slide at all. Let’s use Clang.jl.
Example of creating bindings using Clang.jl
Forked from libqrean - https://github.com/terasakisatoshi/libqrean/tree/julia/LibQREAN
go build -buildmode=c-shared -o export.so
will create export.h
. Could make something nice combined with Clang.jl?
It looks like you haven’t pasted the Markdown content yet. Please provide the text you want translated, and I’ll get started on it right away.