Skip to content

Commit

Permalink
Add ability to automatically box scalars in wrapped API with special …
Browse files Browse the repository at this point in the history
…case argument type
  • Loading branch information
jmert committed Dec 6, 2020
1 parent 099c6cd commit 2e968af
Show file tree
Hide file tree
Showing 3 changed files with 59 additions and 12 deletions.
2 changes: 1 addition & 1 deletion gen/api_defs.jl
Original file line number Diff line number Diff line change
Expand Up @@ -225,7 +225,7 @@
@bind h5t_commit(loc_id::hid_t, name::Ptr{UInt8}, dtype_id::hid_t, lcpl_id::hid_t, tcpl_id::hid_t, tapl_id::hid_t)::herr_t "Error committing type"
@bind h5t_copy(dtype_id::hid_t)::hid_t "Error copying datatype"
@bind h5t_create(class_id::Cint, sz::Csize_t)::hid_t error("Error creating datatype of id ", class_id)
@bind h5t_enum_insert(dtype_id::hid_t, name::Cstring, value::Ptr{Cvoid})::herr_t error("Error adding ", name, " to enum datatype")
@bind h5t_enum_insert(dtype_id::hid_t, name::Cstring, value::Ref{Cvoid})::herr_t error("Error adding ", name, " to enum datatype")
@bind h5t_equal(dtype_id1::hid_t, dtype_id2::hid_t)::htri_t "Error checking datatype equality"
@bind h5t_get_array_dims(dtype_id::hid_t, dims::Ptr{hsize_t})::Cint "Error getting dimensions of array"
@bind h5t_get_array_ndims(dtype_id::hid_t)::Cint "Error getting ndims of array"
Expand Down
62 changes: 52 additions & 10 deletions gen/bind_generator.jl
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,31 @@ expression. If not provided, no error check is done. If it is a `String`, the st
as the message in an `error()` call, otherwise the expression is used as-is. Note that the
expression may refer to any arguments by name.
## Special behaviors
**Library names:**
It is assumed that the HDF library names are given in global constants named `libhdf5`
and `libhdf5_hl`. The former is used for all `ccall`s, except if the C library name begins
with one of "H5DO", "H5DS", "H5L5", or "H5TB" then the latter library is used.
**Argument type:**
All arguments are automatically converted to the declared type following Julia's standard
[Calling C and Fortran Code](https://docs.julialang.org/en/v1/manual/calling-c-and-fortran-code/)
rules, _except_ in the special case where the argument type is declared as `::Ref{Cvoid}`.
In this exceptional case, the argument `arg::Ref{Cvoid}` is transformed following the rule
```julia
c_arg = arg isa Ref{<:Any} ? arg : Base.cconvert(Ref{eltype(arg)}, arg)
```
and the `ccall` is expanded as if the original argument had been declared as
`c_arg::Ptr{Cvoid}`.
This conversion rule allows for automatically boxing scalars while passing through
explicit `Ref` values.
(`Array` arguments are wrapped with a benign `RefArray`.)
**Return types:**
The declared return type in the function-like signature must be the return type of the C
function, and the Julia return type is inferred as one of the following possibilities:
Expand All @@ -58,7 +83,9 @@ function, and the Julia return type is inferred as one of the following possibil
3. Otherwise, the C function return value is returned from the Julia function.
Furthermore, the C return value is interpreted to automatically generate error checks
**Error checking:**
The C return value is also interpreted to automatically generate error checks
(only when `ErrorStringOrExpression` is provided):
1. If `ReturnType === :herr_t` or `ReturnType === :htri_t`, an error is raised when the return
Expand All @@ -71,10 +98,6 @@ Furthermore, the C return value is interpreted to automatically generate error c
equal to `C_NULL`.
3. For all other return types, it is assumed a negative value indicates error.
It is assumed that the HDF library names are given in global constants named `libhdf5`
and `libhdf5_hl`. The former is used for all `ccall`s, except if the C library name begins
with "H5DO" or "H5TB" then the latter library is used.
"""
macro bind(sig::Expr, err::Union{String,Expr,Nothing} = nothing)
sig.head === :(::) || error("return type required on function signature")
Expand All @@ -90,15 +113,30 @@ macro bind(sig::Expr, err::Union{String,Expr,Nothing} = nothing)
funcargs = funcsig.args[2:end]

# Pull apart argument names and types
args = Vector{Symbol}()
argt = Vector{Union{Expr,Symbol}}()
args = Vector{Symbol}() # arguments in function signature
argv = Vector{Symbol}() # arguments passed in ccall
argt = Vector{Union{Expr,Symbol}}() # types of ccall arguments
argc = Vector{Expr}() # optional conversions taking args => argv
for ii in 1:length(funcargs)
argex = funcargs[ii]
if !isexpr(argex, :(::)) || !(argex.args[1] isa Symbol)
error("expected `name::type` expression in argument ", ii, ", got ", funcargs[ii])
end
push!(args, argex.args[1])
push!(argt, argex.args[2])
if isexpr(argex.args[2], :curly) && argex.args[2] == :(Ref{Cvoid})
# Special case: maybe box the argument in Ref
arg = argex.args[1]::Symbol
sym = Symbol("#", arg, "#")
push!(argc, :($sym = $arg isa Ref{<:Any} ? $arg : $(GlobalRef(Base, :cconvert))(Ref{eltype($arg)}, $arg)))
push!(argv, sym)
push!(argt, :(Ptr{Cvoid}))
# in-place modify funcargs so that the doc generation (`string(funcsig)` below)
# shows the C argument type.
funcargs[ii].args[2] = :(Ptr{Cvoid})
else
push!(argv, argex.args[1])
push!(argt, argex.args[2])
end
end

prefix, rest = split(string(jlfuncname), "_", limit = 2)
Expand All @@ -122,7 +160,7 @@ macro bind(sig::Expr, err::Union{String,Expr,Nothing} = nothing)

# The ccall(...) itself
cfunclib = Expr(:tuple, quot(cfuncname), lib)
ccallexpr = :(ccall($cfunclib, $rettype, ($(argt...),), $(args...)))
ccallexpr = :(ccall($cfunclib, $rettype, ($(argt...),), $(argv...)))

# The error condition expression
errexpr = err isa String ? :(error($err)) : err
Expand Down Expand Up @@ -157,7 +195,11 @@ macro bind(sig::Expr, err::Union{String,Expr,Nothing} = nothing)
# avoids inserting the line number nodes for the macro --- the call site
# is instead explicitly injected into the function body via __source__.
jlfuncsig = Expr(:call, jlfuncname, args...)
jlfuncbody = Expr(:block, __source__, :($statsym = $ccallexpr))
jlfuncbody = Expr(:block, __source__)
if !isempty(argc)
append!(jlfuncbody.args, argc)
end
push!(jlfuncbody.args, :($statsym = $ccallexpr))
if errexpr !== nothing
push!(jlfuncbody.args, errexpr)
end
Expand Down
7 changes: 6 additions & 1 deletion src/api.jl
Original file line number Diff line number Diff line change
Expand Up @@ -885,7 +885,12 @@ function h5t_create(class_id, sz)
end

function h5t_enum_insert(dtype_id, name, value)
var"#status#" = ccall((:H5Tenum_insert, libhdf5), herr_t, (hid_t, Cstring, Ptr{Cvoid}), dtype_id, name, value)
var"#value#" = if value isa Ref{<:Any}
value
else
Base.cconvert(Ref{eltype(value)}, value)
end
var"#status#" = ccall((:H5Tenum_insert, libhdf5), herr_t, (hid_t, Cstring, Ptr{Cvoid}), dtype_id, name, var"#value#")
var"#status#" < 0 && error("Error adding ", name, " to enum datatype")
return nothing
end
Expand Down

0 comments on commit 2e968af

Please sign in to comment.