Calling C and Fortran Code
ほとんどのコードはJuliaで書くことができますが、すでにCやFortranで書かれた高品質で成熟した数値計算用のライブラリが多数存在します。この既存のコードを簡単に利用できるように、JuliaはCおよびFortranの関数を呼び出すのを簡単かつ効率的にします。Juliaには「ボイラープレートなし」の哲学があります:関数は「グルー」コード、コード生成、またはコンパイルなしで、直接Juliaから呼び出すことができます - インタラクティブプロンプトからでも同様です。これは、適切な呼び出しを@ccallマクロ(または、あまり便利ではないccall構文、詳細はccall syntax sectionを参照)を使うことで実現されます。
呼び出されるコードは、共有ライブラリとして利用可能でなければなりません。ほとんどのCおよびFortranライブラリは、すでに共有ライブラリとしてコンパイルされて出荷されますが、GCC(またはClang)を使用して自分でコードをコンパイルする場合は、-sharedおよび-fPICオプションを使用する必要があります。JuliaのJITによって生成される機械命令は、ネイティブC呼び出しと同じであるため、結果としてのオーバーヘッドはCコードからライブラリ関数を呼び出すのと同じです。 [1]
デフォルトでは、Fortran コンパイラは generate mangled names (例えば、関数名を小文字または大文字に変換し、しばしばアンダースコアを追加します)を使用します。そのため、Fortran 関数を呼び出すには、使用している Fortran コンパイラによって従われるルールに対応するマングルされた識別子を渡す必要があります。また、Fortran 関数を呼び出す際には、すべての入力をヒープまたはスタック上の割り当てられた値へのポインタとして渡す必要があります。これは、通常ヒープに割り当てられる配列や他の可変オブジェクトだけでなく、通常スタックに割り当てられ、C や Julia の呼び出し規約を使用する際に一般的にレジスタに渡される整数や浮動小数点数などのスカラー値にも適用されます。
@ccallを使用してライブラリ関数を呼び出すための構文は次のとおりです:
@ccall library.function_name(argvalue1::argtype1, ...)::returntype
@ccall function_name(argvalue1::argtype1, ...)::returntype
@ccall $function_pointer(argvalue1::argtype1, ...)::returntypelibrary は文字列定数またはリテラルです(ただし、以下の Non-constant Function Specifications を参照してください)。ライブラリは省略可能で、その場合、関数名は現在のプロセスで解決されます。この形式は、Cライブラリ関数、Juliaランタイム内の関数、またはJuliaにリンクされたアプリケーション内の関数を呼び出すために使用できます。ライブラリへのフルパスも指定できます。あるいは、@ccall を使用して、Libdl.dlsym によって返されるような関数ポインタ $function_pointer を呼び出すこともできます。argtype はC関数のシグネチャに対応し、argvalue は関数に渡される実際の引数値です。
以下は、map C types to Julia typesの方法です。
完全でありながらシンプルな例として、以下はほとんどのUnix系システムの標準Cライブラリからclock関数を呼び出します:
julia> t = @ccall clock()::Int32
2292761
julia> typeof(t)
Int32clock は引数を取らず、Int32 を返します。環境変数の値へのポインタを取得するために getenv 関数を呼び出すには、次のように呼び出します:
julia> path = @ccall getenv("SHELL"::Cstring)::Cstring
Cstring(@0x00007fff5fbffc45)
julia> unsafe_string(path)
"/bin/bash"実際には、特に再利用可能な機能を提供する際に、一般的に @ccall の使用を引数を設定し、その後CまたはFortran関数が指定する方法でエラーをチェックするJulia関数にラップします。そして、エラーが発生した場合は、通常のJulia例外としてスローされます。これは、CおよびFortranのAPIがエラー条件を示す方法について非常に一貫性がないため、特に重要です。たとえば、getenv Cライブラリ関数は、次のJulia関数にラップされており、これは env.jl からの実際の定義の簡略版です。
function getenv(var::AbstractString)
val = @ccall getenv(var::Cstring)::Cstring
if val == C_NULL
error("getenv: undefined variable: ", var)
end
return unsafe_string(val)
endCのgetenv関数は、エラーを示すためにC_NULLを返しますが、他の標準C関数は、-1、0、1、その他の特別な値を返すなど、異なる方法でエラーを示します。このラッパーは、呼び出し元が存在しない環境変数を取得しようとした場合に、問題を示す例外をスローします:
julia> getenv("SHELL")
"/bin/bash"
julia> getenv("FOOBAR")
ERROR: getenv: undefined variable: FOOBARここに、ローカルマシンのホスト名を発見する少し複雑な例があります。
function gethostname()
hostname = Vector{UInt8}(undef, 256) # MAXHOSTNAMELEN
err = @ccall gethostname(hostname::Ptr{UInt8}, sizeof(hostname)::Csize_t)::Int32
Base.systemerror("gethostname", err != 0)
hostname[end] = 0 # ensure null-termination
return GC.@preserve hostname unsafe_string(pointer(hostname))
endこの例では、最初にバイトの配列を割り当てます。次に、Cライブラリ関数gethostnameを呼び出して、配列にホスト名を格納します。最後に、ホスト名バッファへのポインタを取得し、そのポインタをヌル終端のC文字列であると仮定して、Juliaの文字列に変換します。
Cライブラリが呼び出し元にメモリを割り当てることを要求し、それを呼び出し先に渡して populated するというパターンを使用することは一般的です。このようにしてJuliaからメモリを割り当てることは、初期化されていない配列を作成し、そのデータへのポインタをC関数に渡すことで一般的に達成されます。これが、ここでCstring型を使用しない理由です:配列が初期化されていないため、ヌルバイトを含む可能性があります。@ccallの一部としてCstringに変換すると、含まれているヌルバイトをチェックし、そのため変換エラーが発生する可能性があります。
pointer(hostname)をunsafe_stringで逆参照することは、安全でない操作です。これは、hostnameのために割り当てられたメモリにアクセスする必要があり、そのメモリはその間にガーベジコレクションされている可能性があります。マクロGC.@preserveは、これが発生するのを防ぎ、したがって無効なメモリ位置にアクセスすることを防ぎます。
最後に、パスを指定してライブラリを指定する例を示します。次の内容で共有ライブラリを作成します。
#include <stdio.h>
void say_y(int y)
{
printf("Hello from C: got y = %d.\n", y);
}gcc -fPIC -shared -o mylib.so mylib.cを使用してコンパイルします。ライブラリ名として(絶対)パスを指定することで呼び出すことができます:
julia> @ccall "./mylib.so".say_y(5::Cint)::Cvoid
Hello from C: got y = 5.Creating C-Compatible Julia Function Pointers
Juliaの関数を、関数ポインタ引数を受け入れるネイティブC関数に渡すことが可能です。例えば、次のようなCプロトタイプに一致させるために:
typedef returntype (*functiontype)(argumenttype, ...)マクロ @cfunction は、Julia関数への呼び出しのためのC互換の関数ポインタを生成します。 4d61726b646f776e2e436f64652822222c2022406366756e6374696f6e2229_40726566 への引数は:
- ジュリアの関数
- 関数の戻り値の型
- 関数シグネチャに対応する入力タイプのタプル
現在、プラットフォームデフォルトのC呼び出し規約のみがサポートされています。これは、@cfunctionで生成されたポインタが、32ビットWindowsでWINAPIがstdcall関数を期待する呼び出しに使用できないことを意味しますが、WIN64では使用できます(ここではstdcallがC呼び出し規約と統一されています)。
@cfunctionを介して公開されるコールバック関数はエラーをスローしてはいけません。そうすると、予期せずにJuliaランタイムに制御が戻り、プログラムが未定義の状態になる可能性があります。
クラシックな例は、標準Cライブラリのqsort関数で、次のように宣言されています:
void qsort(void *base, size_t nitems, size_t size,
int (*compare)(const void*, const void*));base 引数は、各要素が size バイトの長さ nitems の配列へのポインタです。compare はコールバック関数で、2 つの要素 a と b へのポインタを受け取り、a が b の前に/後に出現すべき場合はゼロより小さい/大きい整数を返します(または、任意の順序が許可される場合はゼロ)。
さて、1次元配列 A の値を Julia で qsort 関数を使用してソートしたいとします(Julia の組み込み sort 関数ではなく)。qsort を呼び出して引数を渡すことを考える前に、比較関数を書く必要があります:
julia> function mycompare(a, b)::Cint
return (a < b) ? -1 : ((a > b) ? +1 : 0)
end;qsort は C の int を返す比較関数を期待するため、戻り値の型を Cint として注釈を付けます。
この関数をCに渡すために、マクロ @cfunction を使用してそのアドレスを取得します:
julia> mycompare_c = @cfunction(mycompare, Cint, (Ref{Cdouble}, Ref{Cdouble}));@cfunction は、3つの引数を必要とします: Julia 関数(mycompare)、戻り値の型(Cint)、および入力引数の型のリテラルタプル。この場合、Cdouble(Float64)の要素をソートするためのものです。
最終的なqsortへの呼び出しは次のようになります:
julia> A = [1.3, -2.7, 4.4, 3.1];
julia> @ccall qsort(A::Ptr{Cdouble}, length(A)::Csize_t, sizeof(eltype(A))::Csize_t, mycompare_c::Ptr{Cvoid})::Cvoid
julia> A
4-element Vector{Float64}:
-2.7
1.3
3.1
4.4例に示すように、元のJulia配列 A は現在ソートされています: [-2.7, 1.3, 3.1, 4.4]。Julia takes care of converting the array to a Ptr{Cdouble})、要素型のサイズをバイト単位で計算することなど。
楽しむために、mycompareにprintln("mycompare($a, $b)")の行を挿入してみてください。これにより、qsortが実行している比較を確認でき(実際に渡したJulia関数が呼び出されていることを確認するため)、便利です。
Mapping C Types to Julia
宣言されたC型とJuliaでの宣言を正確に一致させることが重要です。不一致があると、あるシステムで正しく動作するコードが別のシステムで失敗したり、不確定な結果を生じる可能性があります。
C関数を呼び出すプロセスでは、どこにもCヘッダーファイルは使用されていないことに注意してください。あなたは、Juliaの型と呼び出しシグネチャがCヘッダーファイルのものと正確に一致するようにする責任があります。[2]
Automatic Type Conversion
Juliaは、各引数を指定された型に変換するために、Base.cconvert関数への呼び出しを自動的に挿入します。たとえば、次の呼び出し:
@ccall "libfoo".foo(x::Int32, y::Float64)::Cvoidそのように書かれたかのように振る舞います:
c_x = Base.cconvert(Int32, x)
c_y = Base.cconvert(Float64, y)
GC.@preserve c_x c_y begin
@ccall "libfoo".foo(
Base.unsafe_convert(Int32, c_x)::Int32,
Base.unsafe_convert(Float64, c_y)::Float64
)::Cvoid
endBase.cconvert は通常 convert を呼び出しますが、Cに渡すのにより適した任意の新しいオブジェクトを返すように定義できます。これは、Cコードによってアクセスされるすべてのメモリの割り当てを行うために使用されるべきです。例えば、これはオブジェクトの Array(例えば、文字列)をポインタの配列に変換するために使用されます。
Base.unsafe_convert は Ptr タイプへの変換を処理します。これは、オブジェクトをネイティブポインタに変換することで、ガーベジコレクタからオブジェクトが隠され、早期に解放される可能性があるため、安全ではないと見なされます。
Type Correspondences
まず、いくつかの関連するJuliaの型用語を見てみましょう:
| Syntax / Keyword | Example | Description |
|---|---|---|
mutable struct | BitSet | "Concrete Type" :: A group of related data that includes a type-tag, is managed by the Julia GC, and is defined by object-identity. The type parameters of a concrete type must be fully defined (no TypeVars are allowed) in order for the instance to be constructed. Also see isconcretetype. |
abstract type | Any, AbstractArray{T, N}, Complex{T} | "Super Type" :: A super-type (not a concrete type) that cannot be instantiated, but can be used to describe a group of types. Also see isabstracttype. |
T{A} | Vector{Int} | "Type Parameter" :: A specialization of a type (typically used for dispatch or storage optimization). |
"TypeVar" :: The T in the type parameter declaration is referred to as a TypeVar (short for type variable). | ||
primitive type | Int, Float64 | "Primitive Type" :: A type with no fields, but a size. It is stored and defined by-value. |
struct | Pair{Int, Int} | "Struct" :: A type with all fields defined to be constant. It is defined by-value, and may be stored with a type-tag. |
ComplexF64 (isbits) | "Is-Bits" :: A primitive type, or a struct type where all fields are other isbits types. It is defined by-value, and is stored without a type-tag. | |
struct ...; end | nothing | "Singleton" :: a concrete Type or Struct with no fields. |
(...) or tuple(...) | (1, 2, 3) | "Tuple" :: an immutable data-structure similar to an anonymous struct type, or a constant array. Represented as either an array or a struct. |
Bits Types
特別なタイプがいくつかあり、他のタイプが同じように振る舞うように定義されることはありません。
Float32Cの
float型(またはFortranのREAL*4)に正確に対応します。Float64Cの
double型(またはFortranのREAL*8)に正確に対応します。ComplexF32Cの
complex float型(またはFortranのCOMPLEX*8)に正確に対応します。ComplexF64Cの
complex double型(またはFortranのCOMPLEX*16)に正確に対応します。署名済みsigned型注釈は C のsigned型(または Fortran の任意のINTEGER型)に正確に対応します。Signedのサブタイプでない任意の Julia 型は、符号なしであると見なされます。Ref{T}Ptr{T}のように振る舞い、JuliaのGCを介してメモリを管理できる。Array{T,N}配列がCに
Ptr{T}引数として渡されるとき、それは再解釈キャストされません:Juliaは配列の要素型がTと一致することを要求し、最初の要素のアドレスが渡されます。したがって、
Arrayが間違った形式のデータを含んでいる場合、trunc.(Int32, A)のような呼び出しを使用して明示的に変換する必要があります。配列
Aを異なる型のポインタとして変換せずに渡すには(例えば、Float64配列を解釈されていないバイトで動作する関数に渡す場合)、引数をPtr{Cvoid}として宣言できます。配列の要素型
Ptr{T}がPtr{Ptr{T}}引数として渡されると、Base.cconvertは、各要素がその4d61726b646f776e2e436f64652822222c2022426173652e63636f6e766572742229_40726566バージョンに置き換えられた配列のヌル終端コピーを最初に作成しようとします。これにより、例えば、Vector{String}型のargvポインタ配列をPtr{Ptr{Cchar}}型の引数に渡すことが可能になります。
現在サポートしているすべてのシステムにおいて、基本的なC/C++の値型は次のようにJulia型に変換される場合があります。すべてのC型には、同じ名前の対応するJulia型があり、Cでプレフィックスが付けられています。これは、ポータブルなコードを書く際に役立ちます(CのintがJuliaのIntと同じではないことを思い出すため)。
システム独立型
| C name | Fortran name | Standard Julia Alias | Julia Base Type |
|---|---|---|---|
unsigned char | CHARACTER | Cuchar | UInt8 |
bool (_Bool in C99+) | Cuchar | UInt8 | |
short | INTEGER*2, LOGICAL*2 | Cshort | Int16 |
unsigned short | Cushort | UInt16 | |
int, BOOL (C, typical) | 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 and [[noreturn]] or _Noreturn | Union{} | ||
void* | Ptr{Cvoid} (or similarly Ref{Cvoid}) | ||
T* (where T represents an appropriately defined type) | Ref{T} (T may be safely mutated only if T is an isbits type) | ||
char* (or char[], e.g. a string) | CHARACTER*N | Cstring if null-terminated, or Ptr{UInt8} if not | |
char** (or *char[]) | Ptr{Ptr{UInt8}} | ||
jl_value_t* (any Julia Type) | Any | ||
jl_value_t* const* (a reference to a Julia value) | Ref{Any} (const, since mutation would require a write barrier, which is not possible to insert correctly) | ||
va_arg | Not supported | ||
... (variadic function specification) | T... (where T is one of the above types, when using the ccall function) | ||
... (variadic function specification) | ; va_arg1::T, va_arg2::S, etc. (only supported with @ccall macro) |
Cstring 型は、基本的に Ptr{UInt8} の同義語ですが、Juliaの文字列に埋め込まれたヌル文字が含まれている場合、Cstring への変換はエラーをスローします(これは、Cルーチンがヌルを終端子として扱う場合、文字列が静かに切り捨てられる原因となります)。ヌル終端を仮定しないCルーチンに char* を渡す場合(例えば、明示的な文字列の長さを渡す場合)や、Juliaの文字列にヌルが含まれていないことが確実であり、チェックをスキップしたい場合は、引数の型として Ptr{UInt8} を使用できます。Cstring は ccall の戻り値の型としても使用できますが、その場合は明らかに追加のチェックを導入せず、呼び出しの可読性を向上させるためだけに意図されています。
システム依存型
| C name | Standard Julia Alias | Julia Base Type |
|---|---|---|
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の型はCstring(ヌル終端データが期待される場合)であるべきです。それ以外の場合は、Ptr{Cchar}またはPtr{UInt8}のいずれか(これらの2つのポインタ型は同じ効果があります)であるべきです。上記のように、Stringではありません。同様に、配列引数(T[]またはT*)の場合、Juliaの型は再びPtr{T}であるべきであり、Vector{T}ではありません。
Union{}の戻り値の型は、関数が戻らないことを意味します。つまり、C++11の[[noreturn]]やC11の_Noreturn(例:jl_throwやlongjmp)です。この型は、戻り値がない(void)が実際には戻る関数には使用しないでください。その場合は、代わりにCvoidを使用してください。
wchar_t* 引数の場合、Julia の型は Cwstring (C ルーチンがヌル終端文字列を期待する場合)または Ptr{Cwchar_t} であるべきです。また、Julia の UTF-8 文字列データは内部的にヌル終端されているため、ヌル終端データを期待する C 関数にコピーを作成せずに渡すことができます(ただし、Cwstring 型を使用すると、文字列自体にヌル文字が含まれている場合にエラーが発生します)。
Cの関数でchar**型の引数を取るものは、Julia内でPtr{Ptr{UInt8}}型を使用して呼び出すことができます。例えば、次のようなC関数:
int main(int argc, char **argv);次のJuliaコードを介して呼び出すことができます:
argv = [ "a.out", "arg1", "arg2" ]
@ccall main(length(argv)::Int32, argv::Ptr{Ptr{UInt8}})::Int32Fortranの関数が可変長文字列の型 character(len=*) を受け取る場合、文字列の長さは隠れた引数として提供されます。これらの引数の型と位置はコンパイラによって異なり、コンパイラベンダーは通常、Csize_t を型として使用し、隠れた引数を引数リストの最後に追加することをデフォルトとしています。この動作は一部のコンパイラ(GNU)では固定されていますが、他のコンパイラ(Intel、PGI)はオプションで隠れた引数を文字引数の直後に配置することを許可しています。例えば、次のような形式のFortranサブルーチンが考えられます。
subroutine test(str1, str2)
character(len=*) :: str1,str2次の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) のみが許可されます)のコストがかかります。
Struct Type Correspondences
CのstructやFortran90のTYPE(またはF77のいくつかのバリアントでのSTRUCTURE / RECORD)のような複合型は、同じフィールドレイアウトを持つstruct定義を作成することでJuliaでミラーリングできます。
再帰的に使用される場合、isbits 型はインラインで保存されます。他のすべての型はデータへのポインタとして保存されます。Cの中で値として使用される構造体をミラーリングする際には、フィールドを手動でコピーしようとしないことが重要です。そうしないと、正しいフィールドのアライメントが保持されません。代わりに、isbits 構造体型を宣言し、それを使用してください。無名構造体はJuliaへの翻訳では不可能です。
Juliaでは、パックされた構造体および共用体の宣言はサポートされていません。
unionの近似値を得ることができます。事前に、最大のサイズを持つフィールド(パディングを含む可能性があります)を知っている場合です。フィールドをJuliaに変換する際には、Juliaのフィールドをその型のみに宣言してください。
パラメータの配列は NTuple を使って表現できます。例えば、C表記の構造体は次のように書かれます。
struct B {
int A[3];
};
b_a_2 = B.A[2];ジュリアでは次のように書くことができます。
struct B
A::NTuple{3, Cint}
end
b_a_2 = B.A[3] # note the difference in indexing (1-based in Julia, 0-based in C)サイズが不明な配列([] または [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)Type Parameters
@ccall と @cfunction への型引数は、使用が定義されるときに静的に評価されます。したがって、リテラルタプルの形でなければならず、変数ではなく、ローカル変数を参照することはできません。
これは奇妙な制限のように聞こえるかもしれませんが、CはJuliaのような動的言語ではないため、その関数は静的に知られた固定のシグネチャを持つ引数の型のみを受け入れることを覚えておいてください。
しかし、C ABIを計算するためには型レイアウトが静的に知られている必要がありますが、関数の静的パラメータはこの静的環境の一部と見なされます。関数の静的パラメータは、型のレイアウトに影響を与えない限り、呼び出しシグネチャの型パラメータとして使用できます。例えば、f(x::T) where {T} = @ccall valid(x::Ptr{T})::Ptr{T}は有効です。なぜなら、Ptrは常にワードサイズのプリミティブ型だからです。しかし、g(x::T) where {T} = @ccall notvalid(x::T)::Tは無効です。なぜなら、Tの型レイアウトは静的に知られていないからです。
SIMD Values
もしC/C++のルーチンが引数または戻り値としてネイティブのSIMD型を持っている場合、対応するJulia型はSIMD型に自然にマッピングされるVecElementの均一なタプルです。具体的には:
- タプルはSIMDタイプと同じサイズと要素でなければなりません。たとえば、x86上の
__m128を表すタプルは、サイズが16バイトでFloat32要素を持っている必要があります。- タプルの要素タイプは、
VecElement{T}のインスタンスでなければならず、Tは2の累乗のバイト数を持つプリミティブ型(例:1、2、4、8、16など)でなければなりません。例えば、Int8やFloat64などです。
例えば、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)));
}次のJuliaコードはccallを使用してdistを呼び出します:
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 "libdist".dist(a::m256, b::m256)::m256
end
println(call_dist(a,b))ホストマシンは必要なSIMDレジスタを持っている必要があります。たとえば、上記のコードはAVXサポートのないホストでは動作しません。
Memory Ownership
malloc/free
メモリの割り当てと解放は、使用しているライブラリの適切なクリーンアップルーチンへの呼び出しによって処理されなければなりません。これは、任意のCプログラムと同様です。JuliaでLibc.freeから受け取ったオブジェクトを解放しようとしないでください。これは、誤ったライブラリを介してfree関数が呼び出され、プロセスが中止される原因となる可能性があります。逆に、Juliaで割り当てられたオブジェクトを外部ライブラリによって解放させることも同様に無効です。
When to use T, Ptr{T} and Ref{T}
In Julia code wrapping calls to external C routines, ordinary (non-pointer) data should be declared to be of type T inside the @ccall, as they are passed by value. For C code accepting pointers, Ref{T} should generally be used for the types of input arguments, allowing the use of pointers to memory managed by either Julia or C through the implicit call to Base.cconvert. In contrast, pointers returned by the C function called should be declared to be of the output type Ptr{T}, reflecting that the memory pointed to is managed by C only. Pointers contained in C structs should be represented as fields of type Ptr{T} within the corresponding Julia struct types designed to mimic the internal structure of corresponding C structs.
外部Fortranルーチンへの呼び出しをラップするJuliaコードでは、すべての入力引数はRef{T}型として宣言する必要があります。Fortranはすべての変数をメモリ位置へのポインタとして渡すためです。戻り値の型は、Fortranサブルーチンの場合はCvoid、または型Tを返すFortran関数の場合はTである必要があります。
Mapping C Functions to Julia
@ccall / @cfunction argument translation guide
Cの引数リストをJuliaに翻訳するには:
Tは、原始型のいずれかである:char、int、long、short、float、double、complex、enumまたはそれらのtypedefの同等物。Tは、上記の表に従った同等のJulia Bits型です。Tがenumである場合、引数の型はCintまたはCuintと同等である必要があります。- 引数の値はコピーされます(値渡し)。
struct T(構造体へのtypedefを含む)T、ここでTは具体的なJulia型です- 引数の値はコピーされます(値渡し)。
vector T(または__attribute__ vector_size、または__m128のような typedef)NTuple{N, VecElement{T}}、ここでTは正しいサイズのプリミティブなJulia型であり、Nはベクター内の要素数(vector_size / sizeof Tに等しい)です。
void*- このパラメータがどのように使用されるかによります。まず、これを意図されたポインタ型に翻訳し、その後、このリストの残りのルールを使用してJuliaの同等のものを決定します。
- この引数は、実際に未知のポインタである場合、
Ptr{Cvoid}として宣言される可能性があります。
jl_value_t*任意- 引数の値は有効なJuliaオブジェクトでなければなりません。
jl_value_t* const*Ref{Any}- 引数リストは有効なJuliaオブジェクト(または
C_NULL)でなければなりません。 - 出力パラメータとして使用することはできません。ユーザーがオブジェクトをGCで保持するために別途手配できる場合を除きます。
T*Ref{T}は、Tに対応するJulia型のTです。- 引数の値は、
inlineallocタイプの場合にコピーされます(isbitsを含む)。それ以外の場合、値は有効な Julia オブジェクトでなければなりません。
T (*)(...)(例:関数へのポインタ)Ptr{Cvoid}(このポインタを作成するには、@cfunctionを明示的に使用する必要があるかもしれません)
...(例: vararg)- [for
ccall]:T...、ここでTは残りのすべての引数の単一のJulia型です。 - [for
@ccall]:; va_arg1::T, va_arg2::S, etc、ここでTとSはJuliaの型(すなわち、通常の引数と可変引数を;で区切ります) - 現在、
@cfunctionによってサポートされていません。
- [for
va_argccallまたは@cfunctionによってサポートされていません
@ccall / @cfunction return type translation guide
Cの戻り値の型をJuliaに翻訳するには:
voidCvoid(これはシングルトンインスタンスnothing::Cvoidを返します)
Tは、原始型のいずれかである:char、int、long、short、float、double、complex、enumまたはそれらのtypedefの同等物。- Cの引数リストと同じ
- 引数の値はコピーされます(値渡しで返されます)
struct T(構造体へのtypedefを含む)- Cの引数リストと同じ
- 引数の値はコピーされます(値渡しで返されます)
ベクトル T- Cの引数リストと同じ
void*- このパラメータがどのように使用されるかによります。まず、これを意図されたポインタ型に翻訳し、その後、このリストの残りのルールを使用してJuliaの同等のものを決定します。
- この引数は、実際に未知のポインタである場合、
Ptr{Cvoid}として宣言される可能性があります。
jl_value_t*任意- 引数の値は有効なJuliaオブジェクトでなければなりません。
jl_value_t**Ptr{Any}(Ref{Any}は戻り値の型として無効です)
T*もしメモリがすでにJuliaによって所有されているか、
isbits型であり、非nullであることが知られている場合:Ref{T}は、Tに対応するJulia型です。Ref{Any}の戻り値の型は無効です。Any(jl_value_t*に対応)またはPtr{Any}(jl_value_t**に対応)である必要があります。- C は絶対に
isbits型のTの場合、Ref{T}を介して返されたメモリを変更してはいけません。
Cがメモリを所有している場合:
Ptr{T}は、TがTに対応するJulia型であることを示します。
T (*)(...)(例:関数へのポインタ)Ptr{Cvoid}を直接Juliaから呼び出すには、これを@ccallの最初の引数として渡す必要があります。Indirect Callsを参照してください。
Passing Pointers for Modifying Inputs
Cは複数の戻り値をサポートしていないため、Cの関数はしばしば関数が修正するデータへのポインタを受け取ります。これを@ccall内で実現するには、まず適切な型のRef{T}の中に値をカプセル化する必要があります。このRefオブジェクトを引数として渡すと、Juliaは自動的にカプセル化されたデータへのCポインタを渡します:
width = Ref{Cint}(0)
range = Ref{Cfloat}(0)
@ccall foo(width::Ref{Cint}, range::Ref{Cfloat})::Cvoid戻ると、width と range の内容は(foo によって変更された場合)width[] と range[] で取得できます。つまり、それらはゼロ次元配列のように動作します。
C Wrapper Examples
Cラッパーの簡単な例を始めましょう。これはPtr型を返します:
mutable struct gsl_permutation
end
# The corresponding C signature is
# gsl_permutation * gsl_permutation_alloc (size_t n);
function permutation_alloc(n::Integer)
output_ptr = @ccall "libgsl".gsl_permutation_alloc(n::Csize_t)::Ptr{gsl_permutation}
if output_ptr == C_NULL # Could not allocate memory
throw(OutOfMemoryError())
end
return output_ptr
endGNU Scientific Library(ここでは:libgslを通じてアクセス可能であると仮定)では、C関数gsl_permutation_allocの戻り値の型として不透明ポインタgsl_permutation *が定義されています。ユーザーコードはgsl_permutation構造体の内部を見なくてよいため、対応するJuliaラッパーは、内部フィールドを持たず、Ptr型の型パラメータに配置されることを唯一の目的とする新しい型宣言gsl_permutationが必要です。ccallの戻り値の型はPtr{gsl_permutation}として宣言されており、output_ptrによって指されるメモリはCによって制御されています。
入力 n は値渡しされるため、関数の入力シグネチャは単に ::Csize_t と宣言され、Ref や Ptr は必要ありません。(ラッパーが Fortran 関数を呼び出している場合、対応する関数の入力シグネチャは ::Ref{Csize_t} になります。なぜなら、Fortran の変数はポインタで渡されるからです。)さらに、n は Csize_t 整数に変換可能な任意の型であることができます;ccall は暗黙的に Base.cconvert(Csize_t, n) を呼び出します。
ここに対応するデストラクタをラップする第二の例があります:
# The corresponding C signature is
# void gsl_permutation_free (gsl_permutation * p);
function permutation_free(p::Ptr{gsl_permutation})
@ccall "libgsl".gsl_permutation_free(p::Ptr{gsl_permutation})::Cvoid
endここにジュリア配列を渡す第三の例があります:
# The corresponding C signature is
# 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 "libgsl".gsl_sf_bessel_Jn_array(
nmin::Cint, nmax::Cint, x::Cdouble, result_array::Ref{Cdouble})::Cint
if errorcode != 0
error("GSL error code $errorcode")
end
return result_array
endC関数wrappedは整数エラーコードを返します。Bessel J関数の実際の評価の結果は、Julia配列result_arrayに格納されます。この変数はRef{Cdouble}として宣言されており、そのメモリはJuliaによって割り当てられ、管理されています。暗黙の呼び出しBase.cconvert(Ref{Cdouble}, result_array)は、JuliaポインタをCが理解できる形式のJulia配列データ構造に展開します。
Fortran Wrapper Example
以下の例では、ccallを使用して一般的なFortranライブラリ(libBLAS)の関数を呼び出し、ドット積を計算します。引数のマッピングは、JuliaからFortranにマッピングする必要があるため、上記とは少し異なることに注意してください。すべての引数タイプに対して、RefまたはPtrを指定します。このマングリング規則は、使用しているFortranコンパイラやオペレーティングシステムに特有であり、文書化されていない可能性があります。ただし、各引数を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 "libLAPACK".ddot(
n::Ref{Int32}, DX::Ptr{Float64}, incx::Ref{Int32}, DY::Ptr{Float64}, incy::Ref{Int32})::Float64
return product
endGarbage Collection Safety
データを @ccall に渡す際は、pointer 関数の使用を避けるのが最善です。代わりに、Base.cconvert メソッドを定義し、変数を直接 @ccall に渡してください。@ccall は、自動的にすべての引数が呼び出しが返るまでガーベジコレクションから保護されるように手配します。C API が Julia によって割り当てられたメモリへの参照を保存する場合、@ccall が返った後にオブジェクトがガーベジコレクタに見える状態を保つ必要があります。これを行うための推奨方法は、C ライブラリがそれらを使い終わったことを通知するまで、これらの値を保持するために Vector{Ref} 型のグローバル変数を作成することです。
ポインタをJuliaデータに作成した場合、ポインタの使用が終了するまで元のデータが存在することを確認する必要があります。unsafe_loadやStringなど、Juliaの多くのメソッドはバッファの所有権を取得するのではなくデータのコピーを作成するため、元のデータを解放(または変更)してもJuliaに影響を与えずに安全です。注目すべき例外はunsafe_wrapで、パフォーマンスの理由から、基盤となるバッファの所有権を取得するように指示することができます。
ガーベジコレクタはファイナライズの順序を保証しません。つまり、a が b への参照を含んでいて、a と b の両方がガーベジコレクションの対象である場合、b が a の後にファイナライズされる保証はありません。a の適切なファイナライズが b の有効性に依存する場合は、他の方法で処理する必要があります。
Non-constant Function Specifications
必要なライブラリの正確な名前やパスが事前にわからない場合があり、実行時に計算する必要があります。そのような場合に対処するために、ライブラリコンポーネントの仕様は関数呼び出しであることができます。例えば、find_blas().dgemmのようにです。この呼び出し式は、ccall自体が実行されるときに実行されます。ただし、一度決定されたライブラリの場所は変更されないと仮定されているため、呼び出しの結果はキャッシュされ再利用されることができます。したがって、式が実行される回数は不明であり、複数回の呼び出しで異なる値を返すことは未定義の動作を引き起こします。
もしさらに柔軟性が必要な場合、次のように eval を通じて計算された値を関数名として使用することが可能です:
@eval @ccall "lib".$(string("a", "b"))()::Cintこの式は string を使用して名前を構築し、その後この名前を新しい @ccall 式に置き換え、評価されます。eval はトップレベルでのみ動作するため、この式内ではローカル変数は利用できません(その値が $ で置き換えられない限り)。このため、eval は通常、ライブラリをラップする際のように、多くの類似関数を含むトップレベルの定義を形成するためにのみ使用されます。@cfunction に対しても同様の例を構築できます。
しかし、これを行うと非常に遅くなり、メモリが漏れる可能性があるため、通常はこれを避け、代わりに読み続けるべきです。次のセクションでは、間接呼び出しを使用して効率的に同様の効果を達成する方法について説明します。
Indirect Calls
@ccall の最初の引数は、実行時に評価される式でも構いません。この場合、式は Ptr に評価される必要があり、呼び出すネイティブ関数のアドレスとして使用されます。この動作は、最初の @ccall 引数がローカル変数、関数引数、または非定数のグローバル変数などの非定数への参照を含む場合に発生します。
例えば、dlsymを介して関数を検索し、そのセッションのために共有参照にキャッシュすることができます。例えば:
macro dlsym(lib, func)
z = Ref{Ptr{Cvoid}}(C_NULL)
quote
let zlocal = $z[]
if zlocal == C_NULL
zlocal = dlsym($(esc(lib))::Ptr{Cvoid}, $(esc(func)))::Ptr{Cvoid}
$z[] = zlocal
end
zlocal
end
end
end
mylibvar = Libdl.dlopen("mylib")
@ccall $(@dlsym(mylibvar, "myfunc"))()::CvoidClosure cfunctions
最初の引数 @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})
# Here, `callback` isa Base.CFunction, which will be converted to Ptr{Cvoid}
# (and protected against finalization) by the ccall
@ccall qsort(a::Ptr{T}, length(a)::Csize_t, Base.elsize(a)::Csize_t, callback::Ptr{Cvoid})
# We could instead use:
# GC.@preserve callback begin
# use(Base.unsafe_convert(Ptr{Cvoid}, callback))
# end
# if we needed to use it outside of a `ccall`
return a
endClosure @cfunction は、すべてのプラットフォーム(例えば ARM や PowerPC)で利用できない LLVM トランポリンに依存しています。
Closing a Library
ライブラリを閉じて(アンロード)再ロードできるようにすることは、時には便利です。たとえば、Juliaで使用するCコードを開発しているとき、Cコードをコンパイルし、JuliaからCコードを呼び出し、その後ライブラリを閉じて、編集を行い、再コンパイルし、新しい変更をロードする必要があるかもしれません。Juliaを再起動するか、Libdl関数を使用してライブラリを明示的に管理することができます。例えば:
lib = Libdl.dlopen("./my_lib.so") # Open the library explicitly.
sym = Libdl.dlsym(lib, :my_fcn) # Get a symbol for the function to call.
@ccall $sym(...) # Use the pointer `sym` instead of the library.symbol tuple.
Libdl.dlclose(lib) # Close the library explicitly.@ccallを使用する際、入力(例:@ccall "./my_lib.so".my_fcn(...)::Cvoid)に注意してください。このライブラリは暗黙的に開かれ、明示的に閉じられない場合があります。
Variadic function calls
可変引数のC関数を呼び出すには、引数リスト内でセミコロンを使用して必須引数と可変引数を区切ることができます。以下にprintf関数の例を示します:
julia> @ccall printf("%s = %d\n"::Cstring ; "foo"::Cstring, foo::Cint)::Cint
foo = 3
8ccall interface
別の代替インターフェースがあります @ccall。このインターフェースは少し便利さに欠けますが、calling convention を指定することができます。
ccallへの引数は次のとおりです:
(:function, "library")ペア(最も一般的な)、OR
:function名のシンボルまたは"function"名の文字列(現在のプロセスまたは libc のシンボル用)、OR
関数ポインタ(例えば、
dlsymから)。関数の戻り値の型
関数シグネチャに対応する入力タイプのタプル。一般的な間違いの一つは、引数タイプの1タプルは末尾にカンマを付けて書かなければならないことを忘れることです。
関数に渡される実際の引数値(ある場合);それぞれが別々のパラメータです。
(:function, "library") ペア、戻り値の型、および入力型はリテラル定数でなければなりません(つまり、変数ではなく、ただし Non-constant Function Specifications を参照してください)。
残りのパラメータは、含まれているメソッドが定義されるときにコンパイル時に評価されます。
マクロと関数インターフェース間の翻訳の表は以下に示されています。
@ccall | ccall |
|---|---|
@ccall clock()::Int32 | ccall(:clock, Int32, ()) |
@ccall f(a::Cint)::Cint | ccall(:a, Cint, (Cint,), a) |
@ccall "mylib".f(a::Cint, b::Cdouble)::Cvoid | ccall((:f, "mylib"), Cvoid, (Cint, Cdouble), a, b) |
@ccall $fptr.f()::Cvoid | ccall(fptr, f, Cvoid, ()) |
@ccall printf("%s = %d\n"::Cstring ; "foo"::Cstring, foo::Cint)::Cint | <unavailable> |
@ccall printf("%s = %s\n"::Cstring ; "2 + 2"::Cstring, "5"::Cstring)::Cint | ccall(:printf, Cint, (Cstring, Cstring...), "%s = %s\n", "2 + 2", "5") |
<unavailable> | ccall(:gethostname, stdcall, Int32, (Ptr{UInt8}, UInt32), hn, length(hn)) |
Calling Convention
ccallの第二引数(戻り値の型の直前)は、オプションで呼び出し規約の指定子を指定できます(現在の@ccallマクロは呼び出し規約を指定することをサポートしていません)。指定子がない場合、プラットフォームのデフォルトのC呼び出し規約が使用されます。他にサポートされている規約は、stdcall、cdecl、fastcall、およびthiscall(64ビットWindowsではノーオプ)です。例えば(base/libc.jlから)、上記と同じgethostnameccallが見られますが、Windows用の正しいシグネチャが付いています:
hn = Vector{UInt8}(undef, 256)
err = ccall(:gethostname, stdcall, Int32, (Ptr{UInt8}, UInt32), hn, length(hn))詳細については、LLVM Language Referenceを参照してください。
特別な呼び出し規約 llvmcall があり、これによりLLVMのインストリンシックを直接呼び出すことができます。これは、GPGPUのような珍しいプラットフォームをターゲットにする際に特に便利です。例えば、CUDA の場合、スレッドインデックスを読み取る必要があります。
ccall("llvm.nvvm.read.ptx.sreg.tid.x", llvmcall, Int32, ())ccallを使用する際は、引数のシグネチャを正確に指定することが重要です。また、Core.Intrinsicsによって公開されている同等のJulia関数とは異なり、現在のターゲットで意味があり、機能することを保証する互換性レイヤーは存在しないことに注意してください。
Accessing Global Variables
ネイティブライブラリによってエクスポートされたグローバル変数は、cglobal 関数を使用して名前でアクセスできます。4d61726b646f776e2e436f64652822222c202263676c6f62616c2229_40726566 への引数は、ccall で使用されるのと同じシンボル仕様と、変数に格納されている値を説明する型です:
julia> cglobal((:errno, :libc), Int32)
Ptr{Int32} @0x00007f418d0816b8結果は、値のアドレスを示すポインタです。このポインタを使用して、unsafe_load および unsafe_store! を介して値を操作できます。
この errno シンボルは、「libc」というライブラリには見つからないかもしれません。これは、システムコンパイラの実装の詳細です。通常、標準ライブラリのシンボルは名前だけでアクセスされるべきであり、コンパイラが正しいものを補完します。しかし、この例に示されている errno シンボルはほとんどのコンパイラで特別なものであり、ここで見られる値はおそらくあなたが期待するものや望むものではありません。任意のマルチスレッド対応システムで同等のコードをCでコンパイルすると、通常は異なる関数(マクロプリプロセッサのオーバーロードを介して)を実際に呼び出し、ここに表示されているレガシー値とは異なる結果を返す可能性があります。
Accessing Data through a Pointer
以下のメソッドは「安全でない」として説明されています。なぜなら、悪いポインタや型宣言が原因でJuliaが突然終了する可能性があるからです。
Ptr{T}を考えると、型Tの内容は、unsafe_load(ptr, [index])を使用して参照されたメモリからJuliaオブジェクトに一般的にコピーできます。インデックス引数はオプションで(デフォルトは1)、Juliaの1ベースのインデックス付けの規則に従います。この関数は、getindexおよびsetindex!の動作に意図的に似ています(例:[]アクセス構文)。
戻り値は、参照されたメモリの内容のコピーを含むように初期化された新しいオブジェクトになります。参照されたメモリは安全に解放またはリリースできます。
もし T が Any の場合、メモリにはJuliaオブジェクトへの参照(jl_value_t*)が含まれていると仮定され、結果はこのオブジェクトへの参照となり、オブジェクトはコピーされません。この場合、オブジェクトが常にガーベジコレクタに見えるように注意する必要があります(ポインタはカウントされませんが、新しい参照はカウントされます)ので、メモリが早期に解放されないようにしてください。オブジェクトが元々Juliaによって割り当てられていなかった場合、新しいオブジェクトは決してJuliaのガーベジコレクタによって最終化されることはありません。もし Ptr 自体が実際に jl_value_t* である場合、unsafe_pointer_to_objref(ptr) を使用して再びJuliaオブジェクト参照に変換できます。(Juliaの値 v は、pointer_from_objref(v) を呼び出すことで、Ptr{Cvoid} ポインタに変換できます。)
逆の操作(Ptr{T} にデータを書き込むこと)は、unsafe_store!(ptr, value, [index]) を使用して実行できます。現在、これはプリミティブ型または他のポインタフリー(isbits)の不変構造体型にのみサポートされています。
エラーをスローする操作は、おそらく現在未実装であり、解決できるようにバグとして報告されるべきです。
もし関心のあるポインタがプレーンデータ配列(プリミティブ型または不変構造体)の場合、関数 unsafe_wrap(Array, ptr,dims, own = false) がより便利かもしれません。最終パラメータは、Julia が基盤となるバッファの「所有権を取得」し、返された Array オブジェクトが最終化されるときに free(ptr) を呼び出すべきであれば true であるべきです。own パラメータが省略されるか false の場合、呼び出し元はすべてのアクセスが完了するまでバッファが存在し続けることを保証しなければなりません。
JuliaのPtr型における算術(例えば+を使用すること)は、Cのポインタ算術とは異なります。Juliaでは、Ptrに整数を加えると、常にポインタがバイト単位で移動します。これにより、ポインタ算術から得られるアドレス値は、ポインタの要素型に依存しません。
Thread-safety
いくつかのCライブラリは、異なるスレッドからコールバックを実行します。Juliaはスレッドセーフではないため、いくつかの追加の注意が必要です。特に、2層のシステムを設定する必要があります:Cコールバックは、あなたの「実際の」コールバックの実行をスケジュールするだけでなければなりません(Juliaのイベントループを介して)。これを行うには、AsyncConditionオブジェクトを作成し、それに対してwaitを呼び出します:
cond = Base.AsyncCondition()
wait(cond)Cに渡すコールバックは、ccallを:uv_async_sendに実行するだけで、cond.handleを引数として渡し、Juliaランタイムとの割り当てやその他の相互作用を避けるようにしてください。
イベントは統合される可能性があるため、uv_async_sendへの複数の呼び出しが条件への単一のウェイクアップ通知につながる場合があります。
More About Callbacks
Cライブラリにコールバックを渡す方法の詳細については、次のリンクを参照してください blog post。
C++
C++ バインディングを作成するためのツールについては、CxxWrap パッケージを参照してください。
- 1Non-library function calls in both C and Julia can be inlined and thus may have even less overhead than calls to shared library functions. The point above is that the cost of actually doing foreign function call is about the same as doing a call in either native language.
- 2The Clang package can be used to auto-generate Julia code from a C header file.