From ed9519f8fa3467874dfd905c7346431ff56674ee Mon Sep 17 00:00:00 2001 From: Justin Willmert Date: Sun, 6 Dec 2020 13:55:03 -0600 Subject: [PATCH] Add ability to automatically box scalars in wrapped API with special case argument type --- gen/api_defs.jl | 2 +- gen/bind_generator.jl | 62 ++++++++++++++++++++++++++++++++++++------- src/api.jl | 7 ++++- 3 files changed, 59 insertions(+), 12 deletions(-) diff --git a/gen/api_defs.jl b/gen/api_defs.jl index 97a4145ea..81305c988 100644 --- a/gen/api_defs.jl +++ b/gen/api_defs.jl @@ -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" diff --git a/gen/bind_generator.jl b/gen/bind_generator.jl index 537641462..d3d6b4264 100644 --- a/gen/bind_generator.jl +++ b/gen/bind_generator.jl @@ -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: @@ -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 @@ -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") @@ -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) @@ -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 @@ -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 diff --git a/src/api.jl b/src/api.jl index 360b621e1..74e45d829 100644 --- a/src/api.jl +++ b/src/api.jl @@ -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