準備
今回はC++で生成した共有ライブラリをJuliaから呼び出す形で実験します。
cmake_minimum_required(VERSION 2.8)
add_library(myffi SHARED
ffi.cpp
)
set_target_properties(myffi PROPERTIES PREFIX "")
共有ライブラリはcmakeで生成します。以下でのべるC++コードはffi.cpp
として保存します。
ビルドした共有ライブラリはそのままだとJuliaが見つけられないと思うのでLD_LIBRARY_PATH
で指定してください。
cmake .
make # これでmyffi.soができるので、これを探せるようにする
LD_LIBRARY_PATH=$PWD julia main.jl
ポインタで渡す場合
Calling C and Fortran Codeを参考に実装します。
extern "C" {
void make_twice_ptr(int n, float *p) {
for (int i = 0; i < n; ++i) {
p[i] *= 2.0;
}
}
}
これをJuliaから呼び出します:
a = ones(Float32, 2, 3)
ccall((:make_twice_ptr, "myffi"), Cvoid, (Int32, Ptr{Float32}), 2*3, a)
println(a)
JuliaからFFIをccall
で呼び出すとき、Julia側で一旦変換が実行されてから渡されます。
Array{T,N}
When an array is passed to C as a Ptr{T} argument, it is not reinterpret-cast: Julia requires that the element type of the array matches T, and the address of the first element is passed.
とある通り、Julia側の配列型のアドレスをポインタにするのではなく、データの最初の位置を指すポインタに変換されてわたされるのでこれでちゃんと動作します
Float32[2.0 2.0 2.0; 2.0 2.0 2.0]
構造体として渡す場合
Embedding Juliaを参考に実装します。
# include <julia/julia.h>
extern "C" {
void make_twice_array(jl_value_t *ptr) {
if (jl_is_array_type(ptr) != 0) {
std::cerr << "This is not an array." << std::endl;
return; // TODO Error handling
}
auto array = reinterpret_cast<jl_array_t *>(ptr);
if (array->elsize != sizeof(float)) {
std::cerr << "This is not a float array." << std::endl;
return; // TODO Error handling
}
float *data = reinterpret_cast<float *>(array->data);
for (size_t i = 0; i < array->length; ++i) {
data[i] *= 2.0;
}
}
} // extern C
b = ones(Float32, 2, 3)
ccall((:make_twice_array, "myffi"), Cvoid, (Any,), b)
println(b)
こちらはAny
として渡してしまって、C++側でJuliaのAPIを使用して型を判定してreinterpret_cast
で変換します。JuliaのAPIはイマイチどこにドキュメントがあるか分からなかったので/usr/include/julia/julia.h
にインストールされたヘッダーをそのまま見て書いてます。
最後に
課題
- C++の例外をJuliaに渡す部分
- Juliaのパッケージの一部としてC++の拡張機能を配布する方法
今回のコードは以下に置いておきます:
https://github.com/termoshtt/julia_ffi_example