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
の引数は次の通りです:
-
(:function, "library")
の組 (最も一般的)または
関数名を表すシンボル
:function
または文字列"function"
(function
は現在のプロセスまたは libc が持つシンボル)または
関数ポインタ (例えば
dlsym
が返す値) -
関数の返り値の型
-
関数のシグネチャに対応した、入力の型を表すタプル
-
関数に渡される実際の値 (省略可能/それぞれが一つのパラメータに対応する)
(: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 関数 getenv
は NULL
を返すことでエラーを表しますが、他の標準 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
の引数は次の通りです:
- Julia 関数
- 関数の返り値の型
- 関数のシグネチャに対応する、入力の型を表すタプル
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
を指すポインタを受け取り、a
が b
の前にあるか後ろにあるかに応じて正または負の整数 (どんな順序でも構わなければ 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 が自動的に行うことに注意してください。
mycompare
に println("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_convert
は Ptr
型への変換を処理します。これが安全でない (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): 無名の構造体型や定数配列に似た不変データ構造のこと。配列または構造体として表現される。 |
ビット型
特殊な型がいくつかあります。ここで言う特殊とは、これらの型と同じように振る舞う型を定義できないということです:
-
Float32
C の
float
(Fortran のREAL*4
) と正確に対応します。 -
Float64
C の
double
(Fortran のREAL*8
) と正確に対応します。 -
ComplexF32
C の
complex float
(Fortran のCOMPLEX*8
) と正確に対応します。 -
ComplexF64
C の
complex double
(Fortran のCOMPLEX*16
) と正確に対応します。 -
Signed
C の型注釈
signed
(Fortran の任意のINTEGER
型) と正確に対応します。Signed
の部分型でない Julia の型は符号無しとみなされます。
-
Ref{T}
Ptr{T}
のように振る舞いますが、メモリが Julia の GC によって管理されるものとされます。
-
Array{T,N}
Julia の配列が
Ptr{T}
として C に渡されるとき、reinterpret_cast
に相当する処理は行われません: 配列の要素型はT
である必要があり、その最初の要素のアドレスが渡されます。このため Julia 配列の要素型が変換先のポインタが指す型と異なるときは、
trunc(Int32, a)
のような明示的な変換が必要になります。配列を異なる型のポインタとして C 関数に渡すには、引数を
Ptr{Cvoid}
と宣言してください。例えば値を解釈することなくバイト列として配列を処理する C 関数にArray{Float64}
を渡すといったことができます。要素型が
Ptr{T}
の配列をPtr{Ptr{T}}
型の引数に渡すと、Base.cconvert
は配列の各要素をBase.cconvert
した値に置き換えた配列のコピーを作成し、コピーの最後にヌル値を追加します。これにより、Vector{String}
型のポインタ配列argv
をPtr{Ptr{Cchar}}
の引数に渡すといった処理が可能になります。
現在 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}
を使ってください。Cstring
は ccall
の返り値の型としても指定できますが、そのときはもちろんチェックが行われないので、返り値としての 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_t
や wint_t
) と一致するわけではありません。
返り値 Union{}
は関数が返らないことを意味します。つまり C++11 における [[noreturn]]
および C11 における _Noreturn
です (例えば jl_throw
や longjmp
を使う関数など)。値を返さずに返る (返り値が 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
だけからなるタプルとなります。具体的には次の通りです:
- 対応するタプル型と SIMD 型は同じサイズを持つ必要があります。例えば x86 の
__m128
に対応するタプルのサイズは 16 バイトでなくてはなりません。 - タプルの要素型は
VecElement{T}
のインスタンスで、T
はサイズが 1, 2, 4, 8 バイトのいずれかのプリミティブ型である必要があります。
例えば、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 の型に変換するガイドを示します:
-
T
(プリミティブ型char
,int
,long
,short
,float
,double
,complex
,enum
あるいはこれらをtypedef
した型)T
(等価な Julia のビット型/上記の表を参照)Cint
またはCuint
に等価な型 (T
がenum
のとき)- 引数の値はコピーされる (値渡し)
-
struct T
(および構造体をtypedef
した型)T
(Julia の葉型)- 引数の値はコピーされる (値渡し)
-
void*
- このパラメータの使われ方によって異なる。まずポインタ型がどのような型として使われるのかを調べ、その型をこのリストを使って Julia の型に変換する。
- どのように使われるのかが本当に分からないなら、
Ptr{Cvoid}
と宣言することもできる。
-
jl_value_t*
Any
- 引数の値は正当な Julia オブジェクトである必要がある。
-
jl_value_t**
Ref{Any}
- 引数の値は正当な Julia オブジェクト (または
C_NULL
) である必要がある。
-
T*
Ref{T}
(T
は C のT
に対応する Julia の型)- 引数の値が
isbits
型ならコピーされる。それ以外のときは、引数の値は正当な Julia オブジェクトである必要がある。
-
T (*)(...)
(関数ポインタ)-
Ptr{Cvoid}
(このポインタを明示的に作成するには、おそらく@cfunction
が必要になる)
-
-
...
(可変長引数)T...
(T
は Julia 型)- 現在
@cfunction
ではサポートされない。
-
va_arg
ccall
と@cfunction
ではサポートされない。
ccall
/@cfunction
の返り値変換ガイド
C の返り値を Julia の型に変換するガイドを示します:
-
void
Cvoid
(シングルトンのインスタンスnothing::Cvoid
が返る)
-
T
(プリミティブ型char
,int
,long
,short
,float
,double
,complex
,enum
あるいはこれらをtypedef
した型)T
(等価な Julia のビット型/上記の表を参照)Cint
またはCuint
に等価な型 (T
がenum
のとき)- 引数の値はコピーされる (値渡し)
-
struct T
(および構造体をtypedef
した型)T
(Julia の葉型)- 引数の値はコピーされる (値渡し)
-
void*
- このパラメータの使われ方によって異なる。まずポインタ型がどのような型として使われるのかを調べ、その型をこのリストを使って Julia の型に変換する。
- どのように使われるのかが本当に分からないなら、
Ptr{Cvoid}
と宣言することもできる。
-
jl_value_t*
Any
- 引数の値は正当な Julia オブジェクトである必要がある。
-
jl_value_t**
Ptr{Any}
(Ref{Any}
は返り値の型として正当でない)- 引数の値は正当な Julia オブジェクト (または
C_NULL
) である必要がある。
-
T*
-
Julia がメモリを保有するとき、あるいは
T
がisbits
型でポインタがヌルでないとき:Ref{T}
(T
は C のT
に対応する型)Ref{Any}
は返り値の型として正しくない。jl_value_t*
に対応するAny
か、jl_value_t**
に対応するPtr{Any}
なら正しい。T
がisbits
型のとき、C はRef{T}
で渡されるメモリを変更してはいけない。
-
C がメモリを保有するとき:
Ptr{T}
(T
は C のT
に対応する型)
-
-
T (*)(...)
(関数ポインタ)Ptr{Cvoid}
ポインタ渡しを使った入力の改変
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
が返った後に変更された width
と range
を取り出すには width[]
および range[]
とします。つまり二つのオブジェクトはゼロ次元の配列のように扱われます。
C ラッパーの例
Ptr
型を返す C ラッパーの簡単な例を示します。このコードでは GNU Scientific Library に libgsl
という名前でアクセスできることが仮定されています:
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
の入力シグネチャは Ref
や Ptr
の付いていない (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_array
が Ref{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_load
や String
といった Julia メソッドを使えばバッファの所有権を得ることなくデータのコピーが作成できるので、そうすれば元のデータを削除 (あるいは変更) しても Julia に影響は及びません。注目に値する例外は unsafe_wrap
関数であり、この関数は性能を向上させるために内部バッファの共有 (あるいはその所有権の取得) を行います。
ガベージコレクタはファイナライザの実行順序を保証しません。例えば a
が b
への参照を含んでいて、a
と b
が同時に回収対象になったとしても、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_load
や unsafe_store!
を呼び出せば取り出せます。
上記の errno
シンボルの実装はシステムコンパイラが勝手に決められるので、このシンボルは "libc"
という名前のライブラリに存在しない可能性があります。標準ライブラリのシンボルは基本的に名前だけを使ってアクセスできるようになっており、コンパイラが正しいものを選択します。しかし、この例で使った errno
というシンボルは多くのコンパイラで特別扱いされており、おそらく上記の実行結果はあなたが期待するものではないと思います。等価な C コードをマルチスレッドが有効なシステムでコンパイルすると、異なる関数が (マクロプリプロセッサによるオーバーロードを通じて) 呼び出されることでしょう。すると上に示したレガシーな値とは異なる値が得られる可能性があります。
ポインタを通じたデータアクセス
次に示すメソッドが "安全でない" (unsafe) とされているのは、ポインタや型宣言が間違っていると Julia が突然終了する可能性があるためです。
unsafe_load(ptr, [index])
を使うと Ptr{T}
型の ptr
が指すメモリに存在する T
型の Julia オブジェクトをコピーできます。 添え字を表す引数は省略可能 (デフォルトは 1) で、Julia と同じ 1 始まりの添え字を使います。この関数の振る舞いは getindex
や setindex!
と意図的に似せてあります (例えば []
によるアクセス構文など)。
unsafe_load(ptr, [index])
の返り値は参照されるメモリのコピーで初期化された新しいオブジェクトです。この呼び出しの後に ptr
が参照するメモリを解放しても構いません。
T
が Any
のときは、ptr
が指すメモリには Julia オブジェクト (jl_value_t *
) への参照が含まれるとみなされます。このとき unsafe_load
はそのオブジェクトへの参照を返し、コピーは起きません。このオブジェクトがガベージコレクタから常に見えるようにプログラマーは注意深く調整を行う必要があり、調整に失敗するとメモリが必要よりも早く解放されます (ポインタはガベージコレクタによってカウントされませんが、新しい参照はカウントされます)。オブジェクトが元々 Julia によってアロケートされていない場合、新しいオブジェクトは Julia のガベージコレクタによってファイナライズされないことに注意してください。Ptr
自身が jl_value_t*
の場合には、unsafe_pointer_to_objref(ptr)
で Julia オブジェクトの参照に変換できます (Julia の値 v
は pointer_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 パッケージを参照してください。
-
C および Julia におけるライブラリでない関数呼び出しはインライン化が可能なので、オーバーヘッドは共有ライブラリの関数を呼ぶよりも小さくなる可能性があります。この一文の要点は、外部関数の呼び出しのコストが他のネイティブ言語と同程度であるということです。[return]
-
訳注: Windows では
"libc"
の部分を"Ws2_32"
とすると動く。[return] -
訳注: 説明に合わせるために、
ccall
の第三引数をRef{gsl_permutation}
からPtr{gsl_permutation}
に変更した。また英語版はこのコードの解説がなぜか途中で切れているので、この部分については英語版が残っている最後のバージョン Julia 1.2 のマニュアルを参照して訳した。[return] -
訳注: 英語版にある例はそのままでは実行できなかったので、単純な例に変更した。[return]