diff --git a/src/foundation.jl b/src/foundation.jl index 288011a..9c1f38d 100644 --- a/src/foundation.jl +++ b/src/foundation.jl @@ -21,9 +21,9 @@ struct NSString <: Object end Base.unsafe_convert(::Type{id}, str::NSString) = str.ptr -NSString() = NSString(@objc [NSString string]::id) -NSString(data::String) = NSString(@objc [NSString stringWithUTF8String :data::Ptr{UInt8}]::id) -Base.length(s::NSString) = Int(@objc [s::id length]::NSUInteger) +NSString() = @objc [NSString string]::NSString +NSString(data::String) = @objc [NSString stringWithUTF8String :data::Ptr{UInt8}]::NSString +Base.length(s::NSString) = Int(@objc [s::NSString length]::NSUInteger) export NSHost, hostname @@ -33,6 +33,6 @@ end Base.unsafe_convert(::Type{id}, host::NSHost) = host.ptr hostname() = - unsafe_string(@objc [[[NSHost currentHost]::id localizedName]::id UTF8String]::Ptr{UInt8}) + unsafe_string(@objc [[[NSHost currentHost]::NSHost localizedName]::NSString UTF8String]::Ptr{UInt8}) end diff --git a/src/primitives.jl b/src/primitives.jl index 4dbb3b4..27b1b12 100644 --- a/src/primitives.jl +++ b/src/primitives.jl @@ -88,7 +88,8 @@ abstract type OpaqueObject end const id = Ptr{OpaqueObject} abstract type Object end -# interface: object of a subtype of Object should be convertible to id +# interface: subtypes of Object should be constructible from and convertible to `id`s +# (i.e., requiring `MyObj(::id)` and `Base.unsafe_convert(::Type{id}, ::MyObj)`) class(obj::Union{Object,id}) = ccall(:object_getClass, Ptr{Cvoid}, (id,), obj) |> Class diff --git a/src/syntax.jl b/src/syntax.jl index 39f1806..9ea4ef5 100644 --- a/src/syntax.jl +++ b/src/syntax.jl @@ -2,6 +2,54 @@ export @objc, @classes callerror() = error("ObjectiveC call: use [obj method]::typ or [obj method :param::typ ...]::typ") +ctype(T::Type) = T +ctype(T::Type{<:Object}) = id + +# version of ccall that calls `ctype` on types to figure out if we need pass-by-reference, +# further relying on `Base.unsafe_convert` to get a hold of such a reference. +# it works around the limitation that ccall type tuples need to be literals. +# this is a bit of hack, as it relies on internals of ccall. +@inline @generated function byref_ccall(f::Ptr, _rettyp, _types, vals...) + ex = quote end + + rettyp = _rettyp.parameters[1] + types = _types.parameters[1].parameters + args = [:(vals[$i]) for i in 1:length(vals)] + + # unwrap + reference_rettyp = ctype(rettyp) + reference_types = map(ctype, types) + + # cconvert + cconverted = [Symbol("cconverted_$i") for i in 1:length(vals)] + for (dst, typ, src) in zip(cconverted, types, args) + append!(ex.args, (quote + $dst = Base.cconvert($typ, $src) + end).args) + end + + # unsafe_convert + unsafe_converted = [Symbol("unsafe_converted_$i") for i in 1:length(vals)] + for (dst, typ, src) in zip(unsafe_converted, types, cconverted) + append!(ex.args, (quote + $dst = Base.unsafe_convert($typ, $src) + end).args) + end + + call = Expr(:foreigncall, :f, reference_rettyp, Core.svec(reference_types...), 0, QuoteNode(:ccall), unsafe_converted..., cconverted...) + push!(ex.args, call) + + # re-wrap if necessary + if rettyp != reference_rettyp + ex = quote + val = $(ex) + $rettyp(val) + end + end + + return ex +end + function objcm(ex) # handle a single call, [dst method: param::typ]::typ @@ -54,12 +102,14 @@ function objcm(ex) return ex end +const msgSend = cglobal(:objc_msgSend) + function class_message(class_name, msg, rettyp, argtyps, argvals) quote class = Class($(String(class_name))) sel = Selector($(String(msg))) - ccall(:objc_msgSend, $(esc(rettyp)), - (Ptr{Cvoid}, Ptr{Cvoid}, $(map(esc, argtyps)...)), + byref_ccall(msgSend, $(esc(rettyp)), + Tuple{Ptr{Cvoid}, Ptr{Cvoid}, $(map(esc, argtyps)...)}, class, sel, $(map(esc, argvals)...)) end end @@ -67,8 +117,8 @@ end function instance_message(instance, msg, rettyp, argtyps, argvals) quote sel = Selector($(String(msg))) - ccall(:objc_msgSend, $(esc(rettyp)), - (id, Ptr{Cvoid}, $(map(esc, argtyps)...)), + byref_ccall(msgSend, $(esc(rettyp)), + Tuple{id, Ptr{Cvoid}, $(map(esc, argtyps)...)}, $instance, sel, $(map(esc, argvals)...)) end end diff --git a/test/runtests.jl b/test/runtests.jl index fa174c4..6288934 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -3,7 +3,7 @@ using Test using ObjectiveC -# smoke test +# basic @objc test let data = "test" ptr = @objc [NSString stringWithUTF8String :data::Ptr{UInt8}]::id @test ptr isa id @@ -12,6 +12,17 @@ let data = "test" @test 4 == @objc [ptr::id length]::Culong end +# test re-wrapping of Objective-C objects (which are passed by-reference) +struct TestNSString <: Object + canary::Int + ptr::id + TestNSString(ptr::id) = new(42, ptr) +end +let str = @objc [NSString string]::TestNSString + @assert str isa TestNSString + @assert str.canary == 42 +end + @testset "foundation" begin using .Foundation