LoginSignup
2
1

More than 1 year has passed since last update.

JuliaからCを呼び出すときの注意点: `Ref{T}`と`Ptr{T}`の使いわけ

Last updated at Posted at 2021-10-14

はじめに

Juliaはccallあるいは@ccallでCの関数を呼び出すことが手軽にできます。

しかし、公式ドキュメントの内容が誤解を招くっぽいので、適切な戻り値の型と引数の型の選択に迷ったユーザーがかなりあります。

この記事には、ポインタ型の選択ルールを簡単に解説します。

要点

ccallのポインタ引数および戻り値のデータ型を指定する時、いつもPtr{T}を使っても構いません。

例えば、Julia側の引数の型にかかわらず、下記のように書けます。

Julia
# C関数`foo`のシグネチャ:
# const unsigned char * foo(int x, float * y);
ccall(
    (:foo, "libfoo"),        # 関数名, ライブラリ名
    Ptr{Cuchar},          # `foo`の戻り値の型
    (Cint, Ptr{Cfloat}),     # `foo`の引数1の型, 引数2の型
    x,                       # Julia側の引数x
    y,                       # Julia側の引数y
)

実際は、ccallは普通の関数じゃなくて1、C関数にJulia側の引数を直接に渡すわけじゃないです。

引数yと引数2の型が一致しない場合には、Base.cconvertBase.unsafe_convertによる, 引数yの型が引数2の型と暗黙に変換されます。Base.cconvertBase.unsafe_convert呼び出しの順番は下記のようになります2

Julia
Base.unsafe_convert(Ptr{Cfloat}, Base.cconvert(Ptr{Cfloat}, 引数))

JuliaのBaseモジュールの中には、ここでのunsafe_convert関数とcconvert関数を事前に定義してありますので、引数の型を自動的に変換することができます。しかも、適切なメソッドが見つからない場合、オーバーロードする必要があります。

Julia
julia> methods(Base.cconvert)
# 21 methods for generic function "cconvert":
[1] cconvert(::Type{T}, x::Enum{T2}) where {T<:Integer, T2<:Integer} in Base.Enums at Enums.jl:20
[2] cconvert(::Type{Ref{T}}, t::Tuple{Vararg{T, N}}) where {N, T} in Base at refpointer.jl:170
...

julia> methods(Base.unsafe_convert)
# 69 methods for generic function "unsafe_convert":
[1] unsafe_convert(T::Union{Type{Ptr{Nothing}}, Type{Ptr{Base.Libc.FILE}}}, f::Base.Libc.FILE) in Base.Libc at libc.jl:94
[2] unsafe_convert(::Type{Ref{BigFloat}}, x::Ptr{BigFloat}) in Base.MPFR at mpfr.jl:130
[3] unsafe_convert(::Type{Ref{BigFloat}}, x::Ref{BigFloat}) in Base.MPFR at mpfr.jl:131
...

Ref{T}を使うべき主な場面

Ref{T}3は、実際にRefValue{T}4として使う場面が多いです。

例えば、前文のJulia側の引数yは、下記のように定義します。

Julia
julia> y = Ref{Cfloat}(0.0f0)
Base.RefValue{Float32}(0.0f0)

なぜこれが必要なのかというと、JuliaのInt, Float32, Ptr{Float32}といったisbitstype型は、固定のアドレスがないからです。

C関数がこのJuliaから受け取ったアドレスに何を記入するかもしれないので、固定のアドレスを使わなければならないでしょう。

Julia
julia> pointer_from_objref(0.0f0)
ERROR: pointer_from_objref cannot be used on immutable objects
Stacktrace:
 [1] error(s::String)
   @ Base ./error.jl:33
 [2] pointer_from_objref(x::Any)
   @ Base ./pointer.jl:146
 [3] top-level scope
   @ REPL[13]:1

julia> pointer_from_objref(y)
Ptr{Nothing} @0x000000010f65f8b0

まとめ

  • ccall/@ccallに関わるポイント型を選択する必要がない。常にPtr{T}が使えます。

  • デフォルトメソッドで、Ptr{T}に変換エラーが発生する時、Base.unsafe_convertをオーバーロードする必要があります。

  • Cの関数を呼び出すために、Julia側のポインタ型変数をRef{T}で定義するのがほとんどです。

クイズ

下記のコードの中に、間違えたところがありますか。なぜですか。

Julia
y = Ref{Cfloat}(0.0f0)
y1 = Base.cconvert(Ptr{Cfloat}, y)
y2 = Base.unsafe_convert(Ptr{Cfloat}, y1)
ccall((:foo, "libfoo"), Ptr{Cuchar}, (Cint, Ptr{Cfloat}), 0, y2)
y[]
2
1
1

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
2
1