Skip to content

Commit

Permalink
Add support for dynamic casting on the Julia side (#427)
Browse files Browse the repository at this point in the history
  • Loading branch information
barche authored Apr 27, 2024
1 parent 9d06993 commit bc6eeff
Show file tree
Hide file tree
Showing 3 changed files with 38 additions and 2 deletions.
23 changes: 23 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -294,6 +294,29 @@ auto multi_vector_base = mod.add_type<Parametric<TypeVar<1>>>("MultiVectorBase")
auto vector_base = mod.add_type<Parametric<TypeVar<1>>>("VectorBase", multi_vector_base.dt());
```

### Conversion

Conversion to the base type happens automatically, or can be forced by calling convert, e.g.

```julia
convert(A,b)
```

Where we have `b::B` and `B <: A`

For the equivalent of a C++ `dynamic_cast`, we need to use pointers because the conversion may fail, i.e:

```julia
convert(CxxPtr{B},CxxPtr(a))
```

This is equivalent to the C++ code:
```c++
dynamic_cast<B*>(&a);
```

Use `isnull` on the result to check if the conversion was successful or not.

See the test at [`examples/inheritance.cpp`](https://github.com/JuliaInterop/libcxxwrap-julia/tree/master/examples/inheritance.cpp) and [`test/inheritance.jl`](test/inheritance.jl).

## Enum types
Expand Down
7 changes: 6 additions & 1 deletion src/CxxWrap.jl
Original file line number Diff line number Diff line change
Expand Up @@ -294,13 +294,17 @@ Base.setindex!(x::CxxBaseRef, val, i::Int) = Base.setindex!(x[], val, i)
Base.unsafe_convert(to_type::Type{<:CxxBaseRef}, x) = to_type(x.cpp_object)

# This is defined on the C++ side for each wrapped type
cxxdowncast(::Type{T}, x::CxxPtr{T}) where {T} = x
cxxupcast(x) = cxxupcast(CxxRef(x))
cxxupcast(x::CxxRef) = error("No upcast for type $(supertype(typeof(x))). Did you specialize SuperType to enable automatic upcasting?")
function cxxupcast(::Type{T}, x) where {T}
cxxupcast(T, cxxupcast(x))
end
cxxupcast(::Type{T}, x::CxxBaseRef{T}) where {T} = x

# Dynamic cast equivalent
Base.convert(::Type{CxxPtr{DerivedT}}, x::CxxPtr{SuperT}) where {SuperT, DerivedT <: SuperT} = cxxdowncast(DerivedT, x)

struct ConstArray{T,N} <: AbstractArray{T,N}
ptr::ConstCxxPtr{T}
size::NTuple{N,Int}
Expand Down Expand Up @@ -582,6 +586,7 @@ map_julia_arg_type(t::Type{ConstCxxPtr{CxxChar}}) = Union{ConstPtrTypes{Cchar},

# names excluded from julia type mapping
const __excluded_names = Set([
:cxxdowncast,
:cxxupcast,
:__cxxwrap_smartptr_dereference,
:__cxxwrap_smartptr_construct_from_other,
Expand Down Expand Up @@ -808,7 +813,7 @@ function readmodule(so_path_cb::Function, funcname, m::Module, flags)
so_path = so_path_cb()
fptr = Libdl.dlsym(Libdl.dlopen(so_path, flags), funcname)
register_julia_module(m, fptr)
include_dependency(so_path)
include_dependency(Libdl.dlpath(so_path))
end

function wrapmodule(so_path::String, funcname, m::Module, flags)
Expand Down
10 changes: 9 additions & 1 deletion test/inheritance.jl
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,15 @@ global d = D()
@test take_ref(d) == "D"

# factory function returning an abstract type A
@test message(create_abstract()) == "B"
let abstract_b = create_abstract()
@test message(abstract_b) == "B"
abstract_b_ptr = CxxPtr(abstract_b)
@test !isnull(convert(CxxPtr{B},abstract_b_ptr))
@test message(convert(CxxPtr{B},abstract_b_ptr)) == "B"
@test isnull(convert(CxxPtr{C},abstract_b_ptr))
@test isnull(convert(CxxPtr{D},abstract_b_ptr))
@test convert(CxxPtr{A},abstract_b_ptr) === abstract_b_ptr
end

@test dynamic_message_c(c) == "C"

Expand Down

0 comments on commit bc6eeff

Please sign in to comment.