Skip to content

Commit ed9519f

Browse files
committed
Add ability to automatically box scalars in wrapped API with special case argument type
1 parent 69e23c4 commit ed9519f

File tree

3 files changed

+59
-12
lines changed

3 files changed

+59
-12
lines changed

gen/api_defs.jl

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -225,7 +225,7 @@
225225
@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"
226226
@bind h5t_copy(dtype_id::hid_t)::hid_t "Error copying datatype"
227227
@bind h5t_create(class_id::Cint, sz::Csize_t)::hid_t error("Error creating datatype of id ", class_id)
228-
@bind h5t_enum_insert(dtype_id::hid_t, name::Cstring, value::Ptr{Cvoid})::herr_t error("Error adding ", name, " to enum datatype")
228+
@bind h5t_enum_insert(dtype_id::hid_t, name::Cstring, value::Ref{Cvoid})::herr_t error("Error adding ", name, " to enum datatype")
229229
@bind h5t_equal(dtype_id1::hid_t, dtype_id2::hid_t)::htri_t "Error checking datatype equality"
230230
@bind h5t_get_array_dims(dtype_id::hid_t, dims::Ptr{hsize_t})::Cint "Error getting dimensions of array"
231231
@bind h5t_get_array_ndims(dtype_id::hid_t)::Cint "Error getting ndims of array"

gen/bind_generator.jl

Lines changed: 52 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,31 @@ expression. If not provided, no error check is done. If it is a `String`, the st
4747
as the message in an `error()` call, otherwise the expression is used as-is. Note that the
4848
expression may refer to any arguments by name.
4949
50+
## Special behaviors
51+
52+
**Library names:**
53+
54+
It is assumed that the HDF library names are given in global constants named `libhdf5`
55+
and `libhdf5_hl`. The former is used for all `ccall`s, except if the C library name begins
56+
with one of "H5DO", "H5DS", "H5L5", or "H5TB" then the latter library is used.
57+
58+
**Argument type:**
59+
60+
All arguments are automatically converted to the declared type following Julia's standard
61+
[Calling C and Fortran Code](https://docs.julialang.org/en/v1/manual/calling-c-and-fortran-code/)
62+
rules, _except_ in the special case where the argument type is declared as `::Ref{Cvoid}`.
63+
In this exceptional case, the argument `arg::Ref{Cvoid}` is transformed following the rule
64+
```julia
65+
c_arg = arg isa Ref{<:Any} ? arg : Base.cconvert(Ref{eltype(arg)}, arg)
66+
```
67+
and the `ccall` is expanded as if the original argument had been declared as
68+
`c_arg::Ptr{Cvoid}`.
69+
This conversion rule allows for automatically boxing scalars while passing through
70+
explicit `Ref` values.
71+
(`Array` arguments are wrapped with a benign `RefArray`.)
72+
73+
**Return types:**
74+
5075
The declared return type in the function-like signature must be the return type of the C
5176
function, and the Julia return type is inferred as one of the following possibilities:
5277
@@ -58,7 +83,9 @@ function, and the Julia return type is inferred as one of the following possibil
5883
5984
3. Otherwise, the C function return value is returned from the Julia function.
6085
61-
Furthermore, the C return value is interpreted to automatically generate error checks
86+
**Error checking:**
87+
88+
The C return value is also interpreted to automatically generate error checks
6289
(only when `ErrorStringOrExpression` is provided):
6390
6491
1. If `ReturnType === :herr_t` or `ReturnType === :htri_t`, an error is raised when the return
@@ -71,10 +98,6 @@ Furthermore, the C return value is interpreted to automatically generate error c
7198
equal to `C_NULL`.
7299
73100
3. For all other return types, it is assumed a negative value indicates error.
74-
75-
It is assumed that the HDF library names are given in global constants named `libhdf5`
76-
and `libhdf5_hl`. The former is used for all `ccall`s, except if the C library name begins
77-
with "H5DO" or "H5TB" then the latter library is used.
78101
"""
79102
macro bind(sig::Expr, err::Union{String,Expr,Nothing} = nothing)
80103
sig.head === :(::) || error("return type required on function signature")
@@ -90,15 +113,30 @@ macro bind(sig::Expr, err::Union{String,Expr,Nothing} = nothing)
90113
funcargs = funcsig.args[2:end]
91114

92115
# Pull apart argument names and types
93-
args = Vector{Symbol}()
94-
argt = Vector{Union{Expr,Symbol}}()
116+
args = Vector{Symbol}() # arguments in function signature
117+
argv = Vector{Symbol}() # arguments passed in ccall
118+
argt = Vector{Union{Expr,Symbol}}() # types of ccall arguments
119+
argc = Vector{Expr}() # optional conversions taking args => argv
95120
for ii in 1:length(funcargs)
96121
argex = funcargs[ii]
97122
if !isexpr(argex, :(::)) || !(argex.args[1] isa Symbol)
98123
error("expected `name::type` expression in argument ", ii, ", got ", funcargs[ii])
99124
end
100125
push!(args, argex.args[1])
101-
push!(argt, argex.args[2])
126+
if isexpr(argex.args[2], :curly) && argex.args[2] == :(Ref{Cvoid})
127+
# Special case: maybe box the argument in Ref
128+
arg = argex.args[1]::Symbol
129+
sym = Symbol("#", arg, "#")
130+
push!(argc, :($sym = $arg isa Ref{<:Any} ? $arg : $(GlobalRef(Base, :cconvert))(Ref{eltype($arg)}, $arg)))
131+
push!(argv, sym)
132+
push!(argt, :(Ptr{Cvoid}))
133+
# in-place modify funcargs so that the doc generation (`string(funcsig)` below)
134+
# shows the C argument type.
135+
funcargs[ii].args[2] = :(Ptr{Cvoid})
136+
else
137+
push!(argv, argex.args[1])
138+
push!(argt, argex.args[2])
139+
end
102140
end
103141

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

123161
# The ccall(...) itself
124162
cfunclib = Expr(:tuple, quot(cfuncname), lib)
125-
ccallexpr = :(ccall($cfunclib, $rettype, ($(argt...),), $(args...)))
163+
ccallexpr = :(ccall($cfunclib, $rettype, ($(argt...),), $(argv...)))
126164

127165
# The error condition expression
128166
errexpr = err isa String ? :(error($err)) : err
@@ -157,7 +195,11 @@ macro bind(sig::Expr, err::Union{String,Expr,Nothing} = nothing)
157195
# avoids inserting the line number nodes for the macro --- the call site
158196
# is instead explicitly injected into the function body via __source__.
159197
jlfuncsig = Expr(:call, jlfuncname, args...)
160-
jlfuncbody = Expr(:block, __source__, :($statsym = $ccallexpr))
198+
jlfuncbody = Expr(:block, __source__)
199+
if !isempty(argc)
200+
append!(jlfuncbody.args, argc)
201+
end
202+
push!(jlfuncbody.args, :($statsym = $ccallexpr))
161203
if errexpr !== nothing
162204
push!(jlfuncbody.args, errexpr)
163205
end

src/api.jl

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -885,7 +885,12 @@ function h5t_create(class_id, sz)
885885
end
886886

887887
function h5t_enum_insert(dtype_id, name, value)
888-
var"#status#" = ccall((:H5Tenum_insert, libhdf5), herr_t, (hid_t, Cstring, Ptr{Cvoid}), dtype_id, name, value)
888+
var"#value#" = if value isa Ref{<:Any}
889+
value
890+
else
891+
Base.cconvert(Ref{eltype(value)}, value)
892+
end
893+
var"#status#" = ccall((:H5Tenum_insert, libhdf5), herr_t, (hid_t, Cstring, Ptr{Cvoid}), dtype_id, name, var"#value#")
889894
var"#status#" < 0 && error("Error adding ", name, " to enum datatype")
890895
return nothing
891896
end

0 commit comments

Comments
 (0)