6
5

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

他言語からJulia言語を呼び出すヒント(前編 : 実行形式でC言語から呼び出すよ)

Last updated at Posted at 2019-07-17

Juliaを他の言語から呼びたいじゃん???

PyCall.jl をご存知でしょうか.
JuliaからPythonのAPIを呼び出す事ができる便利なパッケージです.大抵の場合意図している挙動をするような仕組みを提供します.
環境によっては(例えば,何も考えず,miniconda をインストールした時やpyenvで環境を入れた場合)python-jl を使えというメッセージが出るでしょう.Troubleshooting
Your Python interpreter is statically linked to libpython
が参考になります.

さて,AからBを呼び出すのは気楽でもBからAを呼び出すのは難しいケースが多いです.例えば,Juliaの機能をC言語から呼び出したい!そういう需要はあるでしょう.Cから呼びだせば最悪それを経由してC以外の言語からJuliaを呼び出せることができるわけです.本日はその試みをしたのでその経過報告をQiitaにまとめます.

PackageCompiler「出番か???」

と思うじゃないですか,実行形式は問題なく動くんです.ただ,このIssue の通りにすると一応ビルドはできるんですが,呼び出しの時にセグメンテーションフォルトで失敗します.原因は99.9999999% jl_init をしていないからだと思われます.PackageCompiler.jl のソースコードを追わないとわからないですが,snoop_fileのオプションで jl_init を呼び出せるインターフェースを共有ライブラリに持たせるようなロジックは必要でしょう.

追記:build_shared_lib の オプションに init_shared=true を追加すれば良いみたいですが.手元のマシンでは解決していません.juliaac.jlから呼び出せばいいのかな・・・???

ここでは何を話すか

ひとまず Embedding Julia を元にC言語からJuliaを呼び出す事ができたのでそれの報告をします.

CからJuliaを呼び出す(まずは動かせるようになろう)

プログラミングはまがいなりでも動いたら嬉しいわけです.見た目立派でも動かなかったらどうしようもないと言いますが・・・いやはや.使う立場から見れば何かしらの成功体験が欲しいわけです.

まずはこれをビルド&動かそう

embed_example.c
#include <julia.h>
JULIA_DEFINE_FAST_TLS() // only define this once, in an executable (not in a shared library) if you want fast code.

int main(int argc, char *argv[])
{
    /* required: setup the Julia context */
    jl_init();

    /* run Julia commands */
    (void)jl_eval_string("println(sqrt(2.0))");
    /* strongly recommended: notify Julia that the
         program is about to terminate. this allows
         Julia time to cleanup pending write requests
         and run all finalizers
    */
    jl_atexit_hook(0);
    return 0;
}

ひとまず公式サンプルがビルドできるようにします. 公式ドキュメントを見ると

$ gcc -o test -fPIC -I$JULIA_DIR/include/julia -L$JULIA_DIR/lib test.c -ljulia $JULIA_DIR/lib/julia/libstdc++.so.6

と書かれてますが,Macですと libstdc++.so.6 が見つかりません(Ubuntuでは見つかりました).
実はこっちを見るんじゃなくてこのあと数行以降に書いてあるやり方

/usr/local/julia/share/julia/julia-config.jl --cflags --ldflags --ldlibs | xargs gcc embed_example.c

を見つけてそれに従うのが吉です.Macの場合ですと julia-config.jl は $which julia なんかで検索して

/Applications/Julia-1.1.app/Contents/Resources/julia/share/julia/julia-config.jl `

などが見つかるでしょうこれをお元に手元のMacでは

$ /Applications/Julia-1.1.app/Contents/Resources/julia/share/julia/julia-config.jl --cflags --ldflags --ldlibs | xargs gcc test.c 

とするとビルドができます../a.out とすると $\sqrt{2}$ の値がでます.

お?できたやん?もっと面白い例が欲しい.

trivial な例に止まらず,何か一つできると別の例を試したくなるのは自然な発想だと思うので公式ドキュメントに出現する例を関数化してみます.

コードはどんなものになる?

そうですね下記の通りになります. call_julia_from.c みたいなのを作りましょう.

call_julia_from.c
include <julia.h>
JULIA_DEFINE_FAST_TLS() // only define this once, in an executable (not in a shared library) if you want fast code.


void evaluate_julia_scripts(){
    jl_eval_string("x=3");
    jl_eval_string("y = -10; z=x+y; @show z");
}

void evaluate_julia_package(){
    jl_eval_string("using Pkg; Pkg.add(\"UnicodePlots\");using UnicodePlots");
    jl_eval_string("println(lineplot(1:100, sin.(range(0, stop=2π, length=100))))");
}

double get_float64(double x) {
    jl_value_t *argument = jl_box_float64(2.0);
    jl_function_t *func = jl_get_function(jl_base_module, "sqrt");
    jl_value_t *ret = jl_call1(func, argument);
    double unboxed = jl_unbox_float64(ret);
    return unboxed;
}

void array_example(size_t sz) {
    jl_value_t* array_type = jl_apply_array_type((jl_value_t*)jl_float64_type, 1);
    jl_array_t* x          = jl_alloc_array_1d(array_type, sz);
    double *xData = (double*)jl_array_data(x);

    printf("allocate\n");
    for (size_t i = 0; i < jl_array_len(x); i++) {
        xData[i] = i;
    }
    for (size_t i = 0; i < jl_array_len(x); i++) {
        printf("xData[%zu]=%f\n", i, xData[i]);
    }
    jl_function_t *func  = jl_get_function(jl_base_module, "reverse");
    jl_array_t *y = (jl_array_t*)jl_call1(func, (jl_value_t*)x);
    printf("print result\n");
    double* yData = (double*)jl_array_data(y);
    for (size_t i = 0; i < jl_array_len(y); i++) {
        printf("yData[%zu]=%f\n", i, yData[i]);
    }
}

void matrix_example(size_t row, size_t col) {
    // Create 2D array of float64 type
    jl_value_t *array_type = jl_apply_array_type(jl_float64_type, 2);
    jl_array_t *x  = jl_alloc_array_2d(array_type, row, col);

    jl_function_t *func  = jl_get_function(jl_base_module, "println");

    // Get array pointer
    double *p = (double*)jl_array_data(x);
    // Get number of dimensions
    int ndims = jl_array_ndims(x);
    // Get the size of the i-th dim
    size_t rows = jl_array_dim(x, 0);
    size_t cols = jl_array_dim(x, 1);

    // Fill array with data
    for (size_t r = 0; r < rows; r++) {
        for (size_t c = 0; c < cols; c++) {
            double value = c + cols * r;
            p[r + rows * c] = value;
            printf("p[%zu,%zu]=%f\n", 1 + r, 1 + c, value);
        }
    }
    (void*)jl_call1(func, (jl_value_t*)x);
}

int main(int argc, char *argv[])
{
    /* required: setup the Julia context */
    jl_init();

    /* run Julia commands */
    evaluate_julia_scripts();
    evaluate_julia_package();
    printf("%f\n", get_float64(2.0));
    array_example(15);
    matrix_example(10, 5);
    /* strongly recommended: notify Julia that the
         program is about to terminate. this allows
         Julia time to cleanup pending write requests
         and run all finalizers
    */
    jl_atexit_hook(0);
    return 0;
}

ざらりと述べる

jl_init を呼び出しておく

main 関数中に記述していますが,jl_init を呼び出す必要があります

evaluate_julia_scripts.jl

void evaluate_julia_scripts(){
    jl_eval_string("x=3");
    jl_eval_string("y = -10; z=x+y; @show z");
}

"と"で挟まれる式をJulia中のREPLと同じ感覚で操作できるようです."y = -10; z=x+y; @show z" で x が見た目未定義ですが,jl_eval_string("x=3"); によってスコープに x が入っているからだと思われます.もちろん

jl_eval_string("using Pkg; Pkg.add(\"UnicodePlots\");using UnicodePlots");

のようにしてパッケージをインストールすることも可能ですしそれを using でインポートして呼び出したいということも可能です.
ここでは eval をしてるだけでCとJulia明示的なデータをやりと知りてませんが,Float64の型をもつ値との変換や,1,2次元テンソルの扱いなどは matrix_example や array_example などを参照ください.なお,このサンプルコードを作った後に気づきましたが,

/julia/test/embeddin

を見ると公式ドキュメントのコードを塊としてまとめてたみたいです,

Makefileでビルド・実行・クリーンを管理したい

個人開発の範囲ではちょろっとMakefileを書いてビルドの手順を書いておくと色々楽です.下記のファイルは特にオリジナルな部分はなくここのMakefileのヒントを参考にして動くようにしています.

JL_SHARE = $(shell julia -e 'print(joinpath(Sys.BINDIR, Base.DATAROOTDIR, "julia"))')
CFLAGS   += $(shell $(JL_SHARE)/julia-config.jl --cflags)
CXXFLAGS += $(shell $(JL_SHARE)/julia-config.jl --cflags)
LDFLAGS  += $(shell $(JL_SHARE)/julia-config.jl --ldflags)
LDLIBS   += $(shell $(JL_SHARE)/julia-config.jl --ldlibs)

TARGET_NAME=call_julia_from
C_PROGRAM_FILE=call_julia_from.c

all: run

embed_example: $(C_PROGRAM_FILE)
	gcc $< -o $(TARGET_NAME) $(CFLAGS) $(LDFLAGS) $(LDLIBS)

run: embed_example
	./$(TARGET_NAME)

clean:
	rm -f ./$(TARGET_NAME)

これを call_julia_from.c と同じディレクトリに配置して

$ make
# または
$ make run
# 無事ビルドが通るとプログラムが動く・・・・

をします.これは gcc コンパイラに必要なパス・オプションを指定してビルドしてから生成されたバイナリーを走らせることと同じです.

力尽きたので別記事で

次は共有ライブラリとコンパイルされたC言語のプログラムにJuliaを埋め込む方法を書いていきます.
何もわからん(´・ω・`)

中編を書きました

6
5
2

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
6
5

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?