Skip to content

Commit

Permalink
Add back the hacky ccall wrapper so that we can use derived types in …
Browse files Browse the repository at this point in the history
…at-objc wrappers.
  • Loading branch information
maleadt committed Mar 2, 2023
1 parent f9d613a commit 7285bfe
Show file tree
Hide file tree
Showing 4 changed files with 72 additions and 10 deletions.
8 changes: 4 additions & 4 deletions src/foundation.jl
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand All @@ -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
3 changes: 2 additions & 1 deletion src/primitives.jl
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
58 changes: 54 additions & 4 deletions src/syntax.jl
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down Expand Up @@ -54,21 +102,23 @@ 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

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
Expand Down
13 changes: 12 additions & 1 deletion test/runtests.jl
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand Down

0 comments on commit 7285bfe

Please sign in to comment.