C と Fortran コードの呼び出し

ほぼ全てのコードは Julia で書くことができますが、数値計算の分野では C や Fortran で書かれた質の高い成熟したライブラリが多く存在します。こういった既存のコードを簡単に使えるように、Julia は C や Fortran の関数を単純かつ効率的に呼び出すための仕組みを持ちます。Julia は "no boilerplate" 哲学を持ちます: Julia からの外部関数の呼び出しにはグルーコード・コード生成・コンパイルが必要ありません ──さらに対話プロンプトから呼び出すこともできます。適切に ccall 構文を使えば外部関数を呼び出すことができ、この構文の見た目は通常の関数呼び出しと変わりません。

呼び出される関数は共有ライブラリとして利用可能である必要があります。C と Fortran で書かれるライブラリの多くは最初から共有ライブラリで配布されますが、もし GCC (あるいは Clang) でコードを自分でコンパイルするなら、-shared-fPIC のオプションを付けるようにしてください。Julia の JIT が生成する機械命令はネイティブの C 呼び出しと同じなので、実行時のオーバーヘッドはライブラリ関数を C コードから呼び出すのと同程度です1

共有ライブラリと関数は (:function, "library") または ("function", "library") という形のタプルで参照されます。function は C がエクスポートした関数の名前で、library は共有ライブラリの名前です。共有ライブラリの名前解決はプラットフォーム固有のロードパスを使って行われます。ライブラリの完全パスも指定可能です。

関数を表すタプルを使う場所で関数の名前 (:function あるいは "function" だけ) を使うこともできます。こうすると現在のプロセス内で名前が解決されます。この形式は C ライブラリの関数・Julia ランタイムの関数・Julia にリンクされたアプリケーションの関数を呼び出すのに利用できます。

Fotran コンパイラはデフォルトで名前修飾 (関数の名前を小文字あるいは大文字に変換してアンダースコアを付けるといった処理) を行います。そのため Fortran 関数を ccall を使って呼び出すには、コンパイルに使われた Fortran コンパイラが使う規則を確認して修飾された後の識別子を指定しなければなりません。さらに Fortran 関数を呼び出すときは、全ての入力をヒープまたはスタックにアロケートされた値へのポインタとして渡す必要があります。配列などの普段からヒープにアロケートされる可変オブジェクトだけではなく、整数や浮動小数点数といった通常はスタックにアロケートされ C や Julia の呼び出し規則ではレジスタで渡されるスカラー値でもこの規則が適用されます。

最後に、ライブラリ関数の呼び出しを生成するには ccall を使います。ccall の引数は次の通りです:

  1. (:function, "library") の組 (最も一般的)

      または

    関数名を表すシンボル :function または文字列 "function" (function は現在のプロセスまたは libc が持つシンボル)

      または

    関数ポインタ (例えば dlsym が返す値)

  2. 関数の返り値の型

  3. 関数のシグネチャに対応した、入力の型を表すタプル

  4. 関数に渡される実際の値 (省略可能/それぞれが一つのパラメータに対応する)

情報

(:function, "library") の組・返り値の型・入力の型はリテラル定数である必要があります。言い換えると、変数を使うことはできません。ただし非定数による関数の指定に示すように例外もあります。

これらのパラメータはコンパイル時、つまり ccall を含むメソッドが定義されるときに評価されます。

情報

C の型と Julia の型の対応については後述されます。

完全で簡単な例を示します。次のコードは多くの Unix 系システムで利用可能な標準 C ライブラリの clock 関数を呼び出します:

julia> t = ccall(:clock, Int32, ())
2292761

julia> t
2292761

julia> typeof(ans)
Int32

clock は引数を取らず、Int32 を返します。

ccall を使うときによくある間違いが、引数型を表すタプルの要素が一つのときに最後のコンマを忘れてしまうことです。例えば getenv 関数を読んで環境変数の値に対するポインタを取得するには、次のように呼び出します:

julia> path = ccall(:getenv, Cstring, (Cstring,), "SHELL")
Cstring(@0x00007fff5fbffc45)

julia> unsafe_string(path)
"/bin/bash"

引数型を表すタプルは (Cstring) ではなく (Cstring,) と書く必要があることに注意してください。(Cstring)Cstring を括弧で囲っただけであり、Cstring という一つの要素からなるタプルにはならないためです:

julia> (Cstring)
Cstring

julia> (Cstring,)
(Cstring,)

実際にコードを書くとき、特に再利用できる機能を提供するときは、ccall の呼び出しを Julia 関数で包み、Julia コードで C/Fortran 関数が持つ規則に対するエラーチェックや引数の組み立てを行うことになります。もしそこでエラーが起きたなら通常の Julia 例外として送出されるべきです。C や Fortran の API はエラーの発生を示す方法が一貫していないことで悪名高いので、こうした処理は特に重要です。例えば C 関数 getenv は次のような Julia 関数で包みます。これは env.jl にある実装を単純にしたものです:

function getenv(var::AbstractString)
    val = ccall(:getenv, Cstring, (Cstring,), var)
    if val == C_NULL
        error("getenv: undefined variable: ", var)
    end
    return unsafe_string(val)
end

C 関数 getenvNULL を返すことでエラーを表しますが、他の標準 C 関数がエラーを示す方法はバラバラです。-1, -1, 0 がエラーになる関数もあれば、エラー専用の特殊な値を返す関数もあります。このラッパーで存在しない環境変数を取得しようとすると、問題を分かりやすく示した例外が送出されます:

julia> getenv("SHELL")
"/bin/bash"

julia> getenv("FOOBAR")
getenv: undefined variable: FOOBAR

これより少し複雑な例として、ローカルマシンのホストネームを確認する関数を考えます。この例はネットワークライブラリのコードが libc という名前の共有ライブラリで利用可能だと仮定しています2。実際にはこの関数は C 標準ライブラリに含まれることが多いので "libc" の部分は省略できますが、ここでは構文を使い方を示すために使っています:

function gethostname()
    hostname = Vector{UInt8}(undef, 256) # 256 = MAXHOSTNAMELEN
    err = ccall((:gethostname, "libc"), Int32,
                (Ptr{UInt8}, Csize_t),
                hostname, sizeof(hostname))
    Base.systemerror("gethostname", err != 0)
    hostname[end] = 0 # ヌル終端にする。
    return unsafe_string(pointer(hostname))
end

この関数は最初にバイトの配列をアロケートし、C ライブラリ関数 gethostname を呼び出してその配列をホスト名で埋めます。そしてホスト名を収めた配列をヌル終端の C 文字列に変換し、最後に配列を指すポインタを Julia 文字列に変換します。呼び出し側が配列を用意して関数がその内容を埋めるというこのパターンは、C ライブラリでよく使われます。上記のコードのように Julia でメモリを確保してそれを C 関数で使うときは、未初期化の配列を生成してそのデータへのポインタを C 関数に渡すことで行います。これは ccall で引数の型を Cstring にしない理由です: 配列が未初期化なので、ヌルバイトを含む可能性があります。ヌルバイトがあると、ccall の一部として行われる Cstring への変換でエラーが発生します。

C 互換な Julia 関数ポインタの作成

関数ポインタを引数に受け取るネイティブの C 関数に Julia 関数を渡すことができます。つまり、Julia の関数を次の形をした C プロトタイプを持つ関数ポインタに変換できます:

typedef returntype (*functiontype)(argumenttype, ...)

Julia 関数に対する C 互換の関数ポインタは @cfunction で生成します。@cfunction の引数は次の通りです:

  1. Julia 関数
  2. 関数の返り値の型
  3. 関数のシグネチャに対応する、入力の型を表すタプル
情報

ccall と同様に、入力の型のタプルと返り値の型はリテラル定数である必要があります。

情報

現在、プラットフォームでデフォルトの C 呼び出し規約だけがサポートされます。これは 32-bit Windows 上で WINAPI が stdcall 関数を期待するときは @cfunction で生成したポインタを利用できないことを意味します (stdcall が C の呼び出し規約と統合された WIN64 では利用できます)。

関数ポインタを受け取る標準ライブラリの関数といえば qsort 関数が古典的な例です。qsort の宣言を示します:

void qsort(void *base, size_t nmemb, size_t size,
           int (*compare)(const void*, const void*));

引数 base は要素数が nmemb で各要素が size バイトの配列を指します。compare は二つの a, b を指すポインタを受け取り、ab の前にあるか後ろにあるかに応じて正または負の整数 (どんな順序でも構わなければ 0) を返す関数です。

Julia の値からなる Julia の一次元配列 A を持っていて、A を (Julia 組み込みの sort ではなく) qsort 関数を使ってソートしたいとします。qsort を使う前に、まず比較関数が必要です:

julia> function mycompare(a, b)::Cint
           return (a < b) ? -1 : ((a > b) ? +1 : 0)
       end
mycompare (generic function with 1 method)

qsort は C の int を返す比較関数を要求するので、返り値の型を Cint とする注釈を付けます。

この関数を C に渡すために、@cfunction マクロを使って関数のアドレスを取得します:

julia> mycompare_c = @cfunction(mycompare, Cint, (Ref{Cdouble}, Ref{Cdouble}));

@cfunction は三つの引数を取ります: Julia 関数 (mycompare)・返り値の型 (Cint)・そして入力の型のリテラルタプルです。今の例でソートしようとしている配列の型は Cdouble (Float64) です。

qsort の呼び出しは次のように行います:

julia> A = [1.3, -2.7, 4.4, 3.1]
4-element Array{Float64,1}:
  1.3
 -2.7
  4.4
  3.1

julia> ccall(:qsort, Cvoid, (Ptr{Cdouble}, Csize_t, Csize_t, Ptr{Cvoid}),
             A, length(A), sizeof(eltype(A)), mycompare_c)

julia> A
4-element Array{Float64,1}:
 -2.7
  1.3
  3.1
  4.4

オリジナルの Julia 配列 A[-2.7, 1.3, 3.1, 4.4] とソートされたことが分かります。要素型のサイズの計算といった配列から Ptr{Cdouble} への変換は Julia が自動的に行うことに注意してください。

mycompareprintln("mycompare($a, $b)") を追加すれば、qsort がどのような比較を行っているか (そして渡された Julia 関数を qsort が本当に使っていること) を確認できます。

C の型と Julia の型の対応

Julia で宣言される C 関数が C で宣言されたのと同じ型を持つことは非常に重要です。両者の型が一致しないと、一つのシステムで正しく動くコードが他のシステムでは正しく動かなかったり、実行するたびに異なる結果が得られるようになります。

C 関数を呼ぶときに C ヘッダーファイルは利用できない点に注意してください: Julia における型と呼び出しシグネチャを C ヘッダーファイルにあるものと完全に一致させるのはプログラマーの責任です3

自動的な型変換

Julia は ccall の引数を指定された型に変換するために、Base.cconvert 関数の呼び出しを自動的に挿入します。例として次の呼び出しを考えます:

ccall((:foo, "libfoo"), Cvoid, (Int32, Float64), x, y)

この呼び出しは、次の呼び出しと同じ処理となります:

ccall((:foo, "libfoo"), Cvoid, (Int32, Float64),
      Base.unsafe_convert(Int32, Base.cconvert(Int32, x)),
      Base.unsafe_convert(Float64, Base.cconvert(Float64, y)))

Base.cconvert は通常 convert を呼ぶだけですが、C に渡す用の新しいオブジェクトを任意に作って返すこともできます。この関数は C コードがアクセスするメモリのアロケートを全て行うのに使われるべきです。例えば、オブジェクト (文字列など) の Array はポインタの配列に変換されます。

Base.unsafe_convertPtr 型への変換を処理します。これが安全でない (unsafe) とされているのは、オブジェクトのネイティブポインタへの変換によりオブジェクトがガベージコレクタから見えなくなり、解放されるべきタイミングよりも早くそのメモリを解放することが可能になるためです。

型の対応

最初に、Julia の型に関する用語をまとめておきます:

構文/キーワード 説明
mutable struct BitSet "葉型" (leaf type): 関連するデータをまとめたグループを表す。型タグを含み、Julia GC によって管理され、オブジェクトのアイデンティティによって定義される。葉型のインスタンスを構築するには、葉型の型パラメータは完全に定義されていなければならない (TypeVars は許されない)。
abstract type Any,
AbstractArray{T, N},
Complex{T}
"上位型" (super type): インスタンス化できない (葉型でない) 上位型を表す。型の集合を記述するのに利用される。
T{A} Vector{Int} "型パラメータ" (type parameter): 特殊化可能な型を表す (ストレージの最適化やディスパッチによく使われる)。
"型変数" (TypeVar): 型パラメータに含まれる T のこと (type variable の略)。
primitive type Int, Float64 "プリミティブ型" (primitive type): サイズを持ちフィールドを持たない型のこと。フィールドではなく直接値によって保存・定義される。
struct Pair{Int, Int} "構造体" (struct): 全てのフィールドが定数と定義される型のこと。値によって定義され、型タグを持つこともある。
isbits ComplexF64 "ビット型" (bits type): プリミティブ型、または全てのフィールドがビット型の構造体のこと。値によって定義され、型タグを持たずに保存される。
struct ...; end nothing "シングルトン" (singleton): フィールドを持たない葉型もしくは構造体のこと。
(...) または tuple(...) (1, 2, 3) "タプル" (tuple): 無名の構造体型や定数配列に似た不変データ構造のこと。配列または構造体として表現される。

ビット型

特殊な型がいくつかあります。ここで言う特殊とは、これらの型と同じように振る舞う型を定義できないということです:

現在 Julia がサポートする全てのシステムにおいて、基本的な C/C++ の値型は以下のように変換されます。ここにある全ての C 型には C を先頭に付けた名前の Julia 型も存在します。この形の型はポータブルなコードを書くときに役立ちます (C の int と Julia の Int が同じでないことを強調することもできます)。

システムに依存しない型

C の型 Fortran の型 Julia が提供する別名 Julia Base の型
unsigned char CHARACTER Cuchar UInt8
bool (C99 以降では _Bool) Cuchar UInt8
short INTEGER*2,
LOGICAL*2
Cshort Int16
unsigned short Cushort UInt16
int, BOOL (通常 C に存在する) INTEGER*4,
LOGICAL*4
Cint Int32
unsigned int Cuint UInt32
long long INTEGER*8,
LOGICAL*8
Clonglong Int64
unsigned long long Culonglong UInt64
intmax_t Cintmax_t Int64
uintmax_t Cuintmax_t UInt64
float REAL*4i Cfloat Float32
double REAL*8 Cdouble Float64
complex float COMPLEX*8 ComplexF32 Complex{Float32}
complex double COMPLEX*16 ComplexF64 Complex{Float64}
ptrdiff_t Cptrdiff_t Int
ssize_t Cssize_t Int
size_t Csize_t UInt
void Cvoid
void かつ [[noreturn]]
または「_Noreturn
Union{}
void* Ptr{Cvoid}
T* (T は適切に定義された型) Ref{T}
char*/char[]
(文字列など)
CHARACTER*N Cstring (ヌル終端) または Ptr{UInt8} (ヌル終端でない)
char**/*char[] Ptr{Ptr{UInt8}}
jl_value_t* (任意の Julia 型) Any
jl_value_t** (Julia 型への参照) Ref{Any}
va_arg サポートされない
... (可変長引数関数の指定) T... (T はこの表にある型/異なる引数型を持つ可変長引数はサポートされない)

Cstring 型は本質的に Ptr{UInt8} と同じですが、Julia の文字列を Cstring に変換するとき途中にヌル文字が含まれるとエラーが発生する点が異なります (C ルーチンがヌル文字を終端と認識していると、文字列が途中で切れてしまうからです)。ヌル終端となっていない char* を C ルーチンに渡すとき (例えば文字列の長さを明示的に指定するとき) や、Julia 文字列にヌル文字が含まれないことが最初から分かっていてチェックをスキップしたいときは、引数の型に Ptr{UInt8} を使ってください。Cstringccall の返り値の型としても指定できますが、そのときはもちろんチェックが行われないので、返り値としての Cstring 型は呼び出しの読みやすさを向上させるためだけにあります。

システムに依存する型

C の型 Julia が提供する別名 Julia Base の型
char Cchar Int8 (x86, x86_64) または UInt8 (powerpc, arm)
long Clong Int (UNIX) または Int32 (Windows)
unsigned long Culong UInt (UNIX) または UInt32 (Windows)
wchar_t Cwchar_t Int32 (UNIX) または UInt16 (Windows)
情報

Fortran コードを呼び出すときは、全ての入力がヒープもしくはスタックにアロケートされた値に対するポインタである必要があります。つまり上の表に示した対応関係にある型を Ptr{..} または Ref{..} で包んでください。

注意

文字列引数 (char*) に対して使うべき Julia の型は String ではなく、データがヌル終端なら Cstring で、そうでなければ Ptr{Cchar} または Ptr{UInt8} です (最後の二つは同じ型を表します)。同様に、配列引数 T[]T* に対応する Julia 型は Ptr{T} であり、Vector{T} ではありません。

注意

Julia の Char 型は 32 ビットであり、これは全てのプラットフォームのワイド文字型 (wchar_twint_t) と一致するわけではありません。

注意

返り値 Union{} は関数が返らないことを意味します。つまり C++11 における [[noreturn]] および C11 における _Noreturn です (例えば jl_throwlongjmp を使う関数など)。値を返さずに返る (返り値が void 型の) 関数に対しては Union{} ではなく Cvoid としてください。

情報

wchar_t* の引数に対して使うべき Julia の型は、C ルーチンがヌル終端文字列を期待するなら Cwstring で、そうでなければ Ptr{Cwchar_t} です。また Julia が内部に持つ UTF-8 文字列データはヌル終端なので、ヌル終端データを受け付ける C 関数には文字列をコピーせずに渡せることに注意してください (ただし Cwstring 型を使った場合には、文字列の途中にヌル文字があるとエラーが発生します)。

情報

char** 型の引数を受け取る C 関数は Julia の Ptr{Ptr{UInt8}} 型を使って呼び出せます。例えば

int main(int argc, char **argv);

という形の C 関数を呼び出すには、次の Julia コードを使います:

argv = [ "a.out", "arg1", "arg2" ]
ccall(:main, Int32, (Int32, Ptr{Ptr{UInt8}}), length(argv), argv)
情報

character(len=*) 型の可変長文字列を受け取る Fortran 関数では、文字列の長さが暗黙の引数として渡されます。この長さを表す値の型と引数リストにおける位置はコンパイラごとに異なりますが、コンパイラベンダーは通常 Csize_t 型を引数リストの最後に置くという方法をデフォルトで使います。必ずこの振る舞いを使うコンパイラ (GNU) もありますが、文字列の長さを文字列の直後に配置することもできるコンパイラ (Intel, PGI) も存在します。例えば

subroutine test(str1, str2)
character(len=*) :: str1,str2

という形をした Fortran サブルーチンを呼び出すには、次の Julia コードを使います。ここでは文字列の長さを最後に付け足しています:

str1 = "foo"
str2 = "bar"
ccall(:test, Cvoid, (Ptr{UInt8}, Ptr{UInt8}, Csize_t, Csize_t),
                    str1, str2, sizeof(str1), sizeof(str2))
注意

Fortran コンパイラはポインタに対する暗黙の引数として、仮定された形状 (:) と仮定されたサイズ (*) の配列を追加する可能性があります。この振る舞いは ISO_C_BINDING を使った上でサブルーチンの定義に bind(c) を含めることで回避できます。互換性のあるコードを書くために、この振る舞いを回避することが強く推奨されます。一部の言語機能を犠牲にすれば暗黙の引数が生まれなくなります (文字列を渡すときには character(len=1) だけが許される、など)。

情報

返り値が Cvoid と宣言された C 関数は Julia で値 nothing を返します。

構造体型の対応

C の struct や Fortran90 の TYPE (F77 の亜種の一部では STRUCTURE/RECORD) といった複合型を Julia とやり取りするには、同じフィールドレイアウトを持った struct の定義を Julia で作成します。

再帰的に使われる isbits 型はインラインに格納され、他の全ての型はデータへのポインタとして格納されます。C で構造体の中に他の構造体が値として含まれる場合には、外側の構造体を表す Julia 型の定義で内側の構造体のフィールドを "開いた" 状態で書いてはいけません: フィールドのアライメントがおかしくなるためです。そうではなく、内側の構造体を表す isbits 構造体型を個別に宣言して、それを外側の構造体型で使ってください。なお Julia への変換では無名構造体を使うことはできません。

パックされた構造体 (packed struct) や共用体の宣言は Julia でサポートされていません。

union が表す一番大きい型のサイズ (およびそのパディング) が前もって分かっているなら、その union に対応する Julia のフィールドをその型で宣言することでデータの受け渡しは可能です。

固定長配列は NTuple で表現できます。例えば C で書かれた構造体とそれを使う式

struct B {
    int A[3];
};

b_a_2 = B.A[2];

は、Julia で次のように書けます:

struct B
    A::NTuple{3, Cint}
end

b_a_2 = B.A[3]  # 添え字の違いに注意 (Julia は 1 始まり、C は 0 始まり)

長さが定まっていない配列 ([][0] で指定される C99 準拠の可変長構造体) に対する直接のサポートはありません。バイトオフセットを直接操作するのが通常は一番の方法です。例えば C ライブラリが次に示す独自の文字列型を持っていて、それへのポインタを返すとします:

struct String {
    int strlen;
    char data[];
};

二つのフィールドに別々にアクセスすれば、この文字列のコピーを Julia から作成できます:

str = from_c::Ptr{Cvoid}
len = unsafe_load(Ptr{Cint}(str))
unsafe_string(str + Core.sizeof(Cint), len)

型パラメータ

ccall@cfunction に対する型引数はその呼び出しを含むメソッドが定義されるときに静的に評価されます。そのため型引数はリテラルタプルである必要があり、ローカル引数を参照することはできません。

これは奇妙な制限に思えるかもしれませんが、C は Julia のような動的言語ではないので、その関数は静的に判明している固定されたシグネチャを持つ引数だけを受け取れることを思い出してください。

関数が利用する C ABI を計算するには型レイアウトが静的に判明していなければならないものの、関数の静的パラメータが型レイアウトを定める情報に含まれるとは限りません。つまり、関数の静的なパラメータは型レイアウトに影響しない限り呼び出しシグネチャの型パラメータとして利用できます。例えば Ptr{T}T の値に関わらずワードサイズのプリミティブ型なので、f(x::T) where {T} = ccall(:valid, Ptr{T}, (Ptr{T},), x) は正当な定義です。一方で T 自身のレイアウトは静的に判明しないので、g(x::T) where {T} = ccall(:notvalid, T, (T,), x) は不当な定義となります。

SIMD 値

注意: 現在この機能は 64-bit x86 と AArch64 のプラットフォームでのみ実装されています。

C/C++ のルーチンがネイティブな SIMD 型の引数もしくは返り値を持つときは、対応する Julia 型はその SIMD 型に自然にマップされる VecElement だけからなるタプルとなります。具体的には次の通りです:

例えば、AVX 組み込み命令を使う次の C ルーチンを考えます:

#include <immintrin.h>

__m256 dist( __m256 a, __m256 b ) {
    return _mm256_sqrt_ps(_mm256_add_ps(_mm256_mul_ps(a, a),
                                        _mm256_mul_ps(b, b)));
}

ccall を使って dist を呼び出す Julia コードを示します:

const m256 = NTuple{8, VecElement{Float32}}

a = m256(ntuple(i -> VecElement(sin(Float32(i))), 8))
b = m256(ntuple(i -> VecElement(cos(Float32(i))), 8))

function call_dist(a::m256, b::m256)
    ccall((:dist, "libdist"), m256, (m256, m256), a, b)
end

println(call_dist(a,b))

ホストマシンは必要とされる SIMD レジスタを持つ必要があります。例えば、上記のコードは AVX をサポートしないホストでは実行できません。

メモリの所有権

malloc/free

オブジェクト用のメモリの確保と解放は使っているライブラリが持つ適切なクリーンアップルーチンを呼び出して行う必要があり、この点は通常の C プログラムと同様です。例えば C ライブラリから受け取ったオブジェクトを Julia の Libc.free で解放してはいけません。free が間違ったライブラリから呼ばれ、プロセスが停止する可能性があります。この逆 (Julia が確保したオブジェクトを外部ライブラリに解放させる) も同様に無効な操作です。

T, Ptr{T}, Ref{T} の使い分け

外部 C ルーチンの呼び出しをラップする Julia コードの ccall では、値で渡す通常の (ポインタでない) データは T 型として宣言されるべきです。そしてポインタを受け取る C 関数に対しては、基本的に Ref{T} を入力の型として使うべきです。なぜなら、Ref{T} を使うと自動的な Base.cconvert の呼び出しによって Julia または C によって管理されるメモリを指すポインタが利用できるからです。一方で、呼び出された C 関数が返すポインタは Ptr{T} 型として宣言されるべきです。Ptr{T} 型は参照するメモリを管理できるのが C コードだけであることを表します。ポインタを含む C 構造体に対応する Julia の構造体型では、ポインタに対応するフィールドは Ptr{T} とするべきです。

Fortan は全ての変数をメモリ位置へのポインタでやり取りするので、外部 Fortran ルーチンの呼び出しをラップする Julia コードでは全ての入力引数を Ref{T} と宣言するべきです。返り値は Fortran サブルーチンが値を返さないなら Cvoid として、T 型の値を返すなら T としてください。

C 関数から Julia 関数への型変換

ccall/@cfunction の引数変換ガイド

C の引数リストを Julia の型に変換するガイドを示します:

ccall/@cfunction の返り値変換ガイド

C の返り値を Julia の型に変換するガイドを示します:

ポインタ渡しを使った入力の改変

C は複数の返り値をサポートしないので、C 関数は改変するデータへのポインタを引数に受け取ることがよくあります。ccall でこれを行うには、値を適切な型の Ref{T} で包む必要があります。Ref オブジェクトを Ref{T} の引数に渡すと、Julia は自動的に Ref が包んでいるデータへのポインタを C に渡します:

width = Ref{Cint}(0)
range = Ref{Cfloat}(0)
ccall(:foo, Cvoid, (Ref{Cint}, Ref{Cfloat}), width, range)

foo が返った後に変更された widthrange を取り出すには width[] および range[] とします。つまり二つのオブジェクトはゼロ次元の配列のように扱われます。

C ラッパーの例

Ptr 型を返す C ラッパーの簡単な例を示します。このコードでは GNU Scientific Librarylibgsl という名前でアクセスできることが仮定されています:

mutable struct gsl_permutation
end

# 対応する C のシグネチャ:
#     gsl_permutation * gsl_permutation_alloc (size_t n);
function permutation_alloc(n::Integer)
    output_ptr = ccall(
        (:gsl_permutation_alloc, :libgsl), # C 関数とライブラリの名前
        Ptr{gsl_permutation},              # 出力の型
        (Csize_t,),                        # 入力の型のタプル
        n                                  # 関数に渡す Julia 変数の名前
    )
    if output_ptr == C_NULL # メモリのアロケートに失敗した
        throw(OutOfMemoryError())
    end
    return output_ptr
end

libgsl に含まれる C 関数 gsl_permutation_alloc は不透明ポインタ gsl_permutation * を返します。ユーザーコードがgsl_permutation 構造体の中を見ることはないので、対応する Julia ラッパーには新しい空の型 (gsl_permutation) の宣言だけが必要です。ccall の返り値の型が Ptr{gsl_permutation} と宣言されるのは、output_ptr が指す新しくアロケートされるメモリが C によって管理されるためです。

入力の n は値渡しなので、ccall の入力シグネチャは RefPtr の付いていない (Csize_t,) となります (呼び出すのが Fortran 関数なら、入力の型は (Ref{Csize_t},) とする必要があります)。また n には Csize_t に変換可能な任意の型の値を渡すことができます: ccall が自動的に Base.cconvert(Csize_t, n) を呼ぶためです。

二つ目の例を示します。上記の例に対応するデストラクタをラップしています4:

# 対応する C のシグネチャ
#     void gsl_permutation_free (gsl_permutation * p);
function permutation_free(p::Ref{gsl_permutation})
    ccall(
        (:gsl_permutation_free, :libgsl), # C 関数とライブラリの名前
        Cvoid,                            # 出力の型
        (Ptr{gsl_permutation},),          # 入力の型のタプル
        p                                 # 関数に渡す Julia 変数の名前
    )
end

p の型 Ref{gsl_permutation}p が指すメモリが Julia または C によって管理されることを意味します。通常 C によってアロケートされるメモリを指すポインタは Ptr{gsl_permutation} 型であるべきですが、Base.cconvert を使えば Ref{gsl_permutation}Ptr{gsl_permutation} に変換できるので、ccall の引数に p を渡すことができます (型の共変性)。

Julia によってアロケートされたメモリを指すポインタは Ref{gsl_permutation} 型で表し、ポインタが指すメモリが正当であること、そして Julia のガベージコレクタがそのメモリを正しく管理・解放できることを保証する必要があります。そのため permutation_free の引数 p に対する Ref{gsl_premutation} という型宣言は、p が指すメモリを管理するのは C と Julia のどちらでも構わないことを表しています。

これまでの説明とこの例を注意深く照らし合わせると、ラッパー permutation_free に含まれる間違いに気付くはずです。何か分かりますか? ...この関数はメモリを解放しますが、この種の操作は Julia オブジェクトに対して行ってはいけません (Julia のクラッシュやメモリの破損を引き起こします)。そのため p の型は Ptr{gsl_permutation} として、gsl_permutation_alloc 以外の方法で取得されたオブジェクトを渡しにくくするべきです。

三つ目の例を示します。Julia 配列を C 関数に渡しています:

# 対応する C シグネチャ:
#    int gsl_sf_bessel_Jn_array (int nmin, int nmax, double x,
#                                double result_array[])
function sf_bessel_Jn_array(nmin::Integer, nmax::Integer, x::Real)
    if nmax < nmin
        throw(DomainError())
    end
    result_array = Vector{Cdouble}(undef, nmax - nmin + 1)
    errorcode = ccall(
        (:gsl_sf_bessel_Jn_array, :libgsl), # C 関数とライブラリの名前
        Cint,                               # 出力の型
        (Cint, Cint, Cdouble, Ref{Cdouble}),# 入力の型のタプル
        nmin, nmax, x, result_array         # 関数に渡す Julia 変数の名前
    )
    if errorcode != 0
        error("GSL error code $errorcode")
    end
    return result_array
end

ラップされる C 関数 gsl_sf_bessel_Jn_array は Julia 配列 result_array をベッセル関数の評価結果で埋め、整数のエラーコードを返します。変数 gsl_sf_bessel_Jn_arrayRef{Cdouble} と宣言されるのは、このメモリが Julia によってアロケート・管理されるためです。間接的に呼び出される Base.cconvert(Ref(Cdouble), result_array) が Julia の配列データ構造を指す Julia ポインタを C が理解できる形式に変換します。

Fortran ラッパーの例

有名な Fortran ライブラリ (libBLAS) に含まれる関数を ccall で呼び出してドット積を計算する例を次に示します。Julia の引数を Fortran の引数に対応させるときの対応関係がこれまでの例と異なることに注目してください。全ての引数の型に Ref または Ptr が付いていますが、こういったマングリングの規則は Fortran コンパイラや OS によって異なる可能性もあります (そして、おそらくドキュメントされていません)。ただし、引数を Ref (または Ref と等価な Ptr) に包むことは多くの Fortran コンパイラ実装において要件とされます。

function compute_dot(DX::Vector{Float64}, DY::Vector{Float64})
    @assert length(DX) == length(DY)
    n = length(DX)
    incx = incy = 1
    product = ccall((:ddot_, "libLAPACK"),
                    Float64,
                    (Ref{Int32}, Ptr{Float64}, Ref{Int32}, Ptr{Float64}, Ref{Int32}),
                    n, DX, incx, DY, incy)
    return product
end

ガベージコレクションの安全性

ccall にデータを渡すときは、pointer 関数を使うのを避けるべきです。代わりに変換メソッドを定義して、変数を直接 ccall に渡してください。ccall は呼び出した関数が返るまで全ての引数がガベージコレクションによって回収されないようにする処理を行います。もし Julia によってアロケートされたメモリへの参照を C API がどこかに保存する場合には、ccall の後にそのオブジェクトがガベージコレクタから見えているようにするのはプログラマーの責任です。Array{Ref,1} 型のグローバル変数を作って、削除してもよいという通知が C ライブラリから来るまでその値を保持する方法が推奨されます。

Julia データを指すポインタを作るときは、ポインタを使い終わるまで元のデータが存在し続けることを必ず保証してください。unsafe_loadString といった Julia メソッドを使えばバッファの所有権を得ることなくデータのコピーが作成できるので、そうすれば元のデータを削除 (あるいは変更) しても Julia に影響は及びません。注目に値する例外は unsafe_wrap 関数であり、この関数は性能を向上させるために内部バッファの共有 (あるいはその所有権の取得) を行います。

ガベージコレクタはファイナライザの実行順序を保証しません。例えば ab への参照を含んでいて、ab が同時に回収対象になったとしても、b のファイナライザが a のファイナライザより後に実行される保証はありません。もし a のファイナライザが正当な状態の b に依存しているなら、その処理は他の方法で行う必要があります。

非定数による関数の指定

関数を指定するときの (name, library) は定数式である必要があります。ただし、次のように eval を間に挟めば計算した値を使うこともできます:

@eval ccall(($(string("a", "b")), "lib"), ...

この式は関数の名前を string で構築し、その名前を置換してできる ccall の呼び出しを評価します。eval はトップレベルでのみ処理を行うので、この式ではローカル変数を利用できないことに注意してください (この例のように $ による置換を通せば利用できます)。このため、eval を使った ccall はトップレベルの定義を作るためだけに使われます。例えば似たような関数を多く含むライブラリのラップでこのテクニックが使われます。@cfunction でも同様の例が構築できます。

しかしこの処理は非常に低速でメモリがリークする可能性もあるので、基本的には使わないことを推奨します。次の節では間接呼び出しを使って同じことを効率的に実現する方法を説明します。

間接呼び出し

ccall の第一引数は実行時に評価される式とすることもできます。このとき第一引数は Ptr に評価される必要があり、その値が指すアドレスにあるネイティブ関数が呼び出されます。この振る舞いは ccall の第一引数が定数でない参照 (ローカル変数・関数の引数・非定数グローバル変数など) を含むときに起こります。

Libdl.dlsym を使って外部関数のポインタを取得し、ccall を使ってその関数を呼び出す簡単な例を示します5:

import Libdl

lib = Libdl.dlopen("mylib")
ccall(Libdl.dlsym(lib, "myfunc"), Cvoid, ())

cfunction を使ったクロージャ

@cfunction の第一引数に $ を付けると、引数に渡した関数を "閉じ込めた" クロージャを表す struct CFunction が返ります。返り値のオブジェクトは利用が全て終わるまで生存させておく必要があります。cfunction ポインタが指す関数の内容とコードはこの参照が落とされるとき (あるいは atexit を実行するとき) に finalizer を通して削除されます。この機能は C に存在しないので通常は必要となりませんが、個別のクロージャ環境パラメータを提供しない、上手く設計されていない API では有用になる場合があります。

function qsort(a::Vector{T}, cmp) where T
    isbits(T) || throw(ArgumentError("this method can only qsort isbits arrays"))
    callback = @cfunction $cmp Cint (Ref{T}, Ref{T})
    # callback は Base.CFunction 型となる。
    # ccall は callback を Ptr{Cvoid} に変換する (さらにファイナライザを起動させない)。
    ccall(:qsort, Cvoid, (Ptr{T}, Csize_t, Csize_t, Ptr{Cvoid}),
        a, length(a), Base.elsize(a), callback)
    # ccall の外側で callback を使うには、次のようにする:
    #    GC.@preserve callback begin
    #        use(Base.unsafe_convert(Ptr{Cvoid}, callback))
    #    end
    return a
end
情報

@cfunction によるクロージャの作成には LLVM trampoline が使われています。この機能は一部のプラットフォーム (ARM や PowerPC) で利用できません。

ライブラリのクローズ

ライブラリをクローズ (アンロード) して再読み込みできると便利な場合があります。例えば Julia で使う C コードを開発するときは、C コードをコンパイルし、Julia から C コードを呼び出し、結果を確認し、ライブラリをクローズし、編集を行い、再度コンパイルし、新しい変更を読み込む必要があります。Julia を再起動しても構いませんが、次のように Libdl モジュールの関数を使ってライブラリを明示的に管理することもできます:

lib = Libdl.dlopen("./my_lib.so") # ライブラリを明示的に読み込む。
sym = Libdl.dlsym(lib, :my_fcn)   # 呼び出す関数のシンボル (ポインタ) を取得する。
ccall(sym, ...)    # (symbol, library) のタプルではなくポインタ sym を使う。
                   # 他の引数は同じ。
Libdl.dlclose(lib) # 明示的にライブラリを閉じる。

ccall((:my_fcn, "./my_lib.so"), ...) のようにタプルを入力して ccall を使うときはライブラリが自動的に読み込まれますが、そのときは明示的なクローズを行ってはいけません。

呼び出し規則

ccall の第二引数 (返り値の型の直前) で呼び出し規則を指定することもできます。指定しなければプラットフォームでデフォルトの C 呼び出し規則が使われます。サポートされる呼び出し規則は stdcall, cdecl, fastcall, thiscall です (thiscall は 64 ビット Windows では効果を持ちません)。例えば libc.jl にある gethostname は上述したコードと同様ですが、Windows では呼び出し規則に stdcall が指定されています:

hn = Vector{UInt8}(undef, 256)
err = ccall(:gethostname, stdcall, Int32, (Ptr{UInt8}, UInt32), hn, length(hn))

詳しくは LLVM 言語のリファレンスを参照してください。

特殊な呼び出し規則として llvmcall があります。これは LLVM の組み込み命令を直接挿入する規則であり、GPGPU などの特殊なプラットフォームをターゲットとする場合に非常に有用です。例えば CUDA ではスレッドのインデックスを読む必要があります:

ccall("llvm.nvvm.read.ptx.sreg.tid.x", llvmcall, Int32, ())

ccall と同様に、引数のシグネチャが正しいことがここでも重要です。また Core.Intrinsics が公開する Julia 関数とは異なり、llvmcall を使った場合には組み込み命令が現在のターゲットで動作するかを検証する互換性レイヤーは存在しません。

グローバル変数へのアクセス

ネイティブライブラリが公開するグローバル変数には cglobal 関数を使って名前でアクセスできます。cglobal の引数は ccall と同様であり、シンボルによる変数の指定と変数が持つ値の型を渡します:

julia> cglobal((:errno, :libc), Int32)
Ptr{Int32} @0x00007f418d0816b8

返り値は値のアドレスを与えるポインタです。グローバル変数の値はポインタに対して unsafe_loadunsafe_store! を呼び出せば取り出せます。

情報

上記の errno シンボルの実装はシステムコンパイラが勝手に決められるので、このシンボルは "libc" という名前のライブラリに存在しない可能性があります。標準ライブラリのシンボルは基本的に名前だけを使ってアクセスできるようになっており、コンパイラが正しいものを選択します。しかし、この例で使った errno というシンボルは多くのコンパイラで特別扱いされており、おそらく上記の実行結果はあなたが期待するものではないと思います。等価な C コードをマルチスレッドが有効なシステムでコンパイルすると、異なる関数が (マクロプリプロセッサによるオーバーロードを通じて) 呼び出されることでしょう。すると上に示したレガシーな値とは異なる値が得られる可能性があります。

ポインタを通じたデータアクセス

次に示すメソッドが "安全でない" (unsafe) とされているのは、ポインタや型宣言が間違っていると Julia が突然終了する可能性があるためです。

unsafe_load(ptr, [index]) を使うと Ptr{T} 型の ptr が指すメモリに存在する T 型の Julia オブジェクトをコピーできます。 添え字を表す引数は省略可能 (デフォルトは 1) で、Julia と同じ 1 始まりの添え字を使います。この関数の振る舞いは getindexsetindex! と意図的に似せてあります (例えば [] によるアクセス構文など)。

unsafe_load(ptr, [index]) の返り値は参照されるメモリのコピーで初期化された新しいオブジェクトです。この呼び出しの後に ptr が参照するメモリを解放しても構いません。

TAny のときは、ptr が指すメモリには Julia オブジェクト (jl_value_t *) への参照が含まれるとみなされます。このとき unsafe_load はそのオブジェクトへの参照を返し、コピーは起きません。このオブジェクトがガベージコレクタから常に見えるようにプログラマーは注意深く調整を行う必要があり、調整に失敗するとメモリが必要よりも早く解放されます (ポインタはガベージコレクタによってカウントされませんが、新しい参照はカウントされます)。オブジェクトが元々 Julia によってアロケートされていない場合、新しいオブジェクトは Julia のガベージコレクタによってファイナライズされないことに注意してください。Ptr 自身が jl_value_t* の場合には、unsafe_pointer_to_objref(ptr) で Julia オブジェクトの参照に変換できます (Julia の値 vpointer_from_objref(v)jl_value_t* ポインタ (Ptr{Cvoid}) に変換できます)。

逆の操作 (Ptr{T} へのデータの書き込み) は unsafe_store!(ptr, value, [index]) で行えます。現在、この関数はプリミティブ型とポインタフリーな (isbits な) 不変構造体型に対してだけサポートされます。

エラーが発生する操作はおそらく現在実装されていない操作であり、解決できるようバグとして報告されるべきです。

考えているポインタがプレーンデータ (プリミティブ型または不変構造体) の配列を指しているときは、unsafe_wrap(Array, ptr,dims, own = false) 関数がより便利になります。最後の own パラメータを true にすると Julia が内部バッファの所有権を獲得し、返り値の Array オブジェクトのファイナライザにおいて free(ptr) が呼ばれます。own が省略されるか false のときは、全てのアクセスが完了するまでバッファが存在し続けることを呼び出し側が保証することになります。

Ptr 型に対する算術 (+ など) の振る舞いは C のポインタ算術と異なります。Julia では Ptr に整数を足すと必ずそのバイト数だけポインタが移動します。要素数ではありません。この方法だと、ポインタ算術が返すアドレス値はポインタの要素型に依存しません。

スレッド安全性

C ライブラリの中にはコールバックを異なるスレッドで実行するものがあります。Julia はスレッドセーフでないので、いくつか注意点があります。具体的に言うと、二つのレイヤーからなるシステムが必要になります: C コールバックは "本当の" コールバックのスケジュールだけを行い、本当のコールバックは Julia のイベントループで実行する必要があります。これを行うには AsyncCondition オブジェクトを作成し、それに対して wait を呼び出します:

cond = Base.AsyncCondition()
wait(cond)

C に渡すコールバックは cond.handle を引数とした :uv_async_send への ccall だけを行うべきです。こうすればメモリのアロケートや Julia ランタイムとの対話を C コードが気にする必要がありません。

この方法だと複数のイベントが合体する可能性があることに注意してください。uv_async_send を複数回呼び出しても条件が一度しか起動されない可能性があります。

さらにコールバックについて

C ライブラリへコールバックを渡す方法についてさらに詳しくは Passing Julia Callback Functions to C を参照してください。

C++

C++ との直接的な対話には Cxx.jl パッケージ、C++ バインディングの作成には CxxWrap.jl パッケージを参照してください。


  1. C および Julia におけるライブラリでない関数呼び出しはインライン化が可能なので、オーバーヘッドは共有ライブラリの関数を呼ぶよりも小さくなる可能性があります。この一文の要点は、外部関数の呼び出しのコストが他のネイティブ言語と同程度であるということです。[return]

  2. 訳注: Windows では "libc" の部分を "Ws2_32" とすると動く。[return]

  3. Clang.jl を使えば C ヘッダーファイルから Julia コードを自動生成できます。[return]

  4. 訳注: 説明に合わせるために、ccall の第三引数を Ref{gsl_permutation} から Ptr{gsl_permutation} に変更した。また英語版はこのコードの解説がなぜか途中で切れているので、この部分については英語版が残っている最後のバージョン Julia 1.2 のマニュアルを参照して訳した。[return]

  5. 訳注: 英語版にある例はそのままでは実行できなかったので、単純な例に変更した。[return]

広告