Interfaces

Interfaces are C++ structures that only have pure virtual functions. Luna SDK provides most its functionalities through interfaces, so that the implementation detail can be encapsulated and may be different on different platforms.

Declaring interfaces

#include <Luna/Runtime/Interface.hpp>

To declare one interface, declare one structure with I name prefix, and virtually inherit from Interface structure. Every interface should have one GUID, which can be declared using luiid macro. Methods of the interface is represented by pure virtual functions.

struct IStream : virtual Interface
{
    luiid("{0345f636-ca5c-4b4d-8416-29834377d239}");

    virtual RV read(void* buffer, usize size, usize* read_bytes = nullptr) = 0;
    virtual RV write(const void* buffer, usize size, usize* write_bytes = nullptr) = 0;
};

One interface can inherit Interface directly, or it can inherit multiple other interfaces. The behavior is correctly defined since virtual inheritance is used.

struct ISeekableStream : virtual IStream
{
    luiid("{42F66080-C388-4EE0-9C4D-1EEC1B82F692}");

    virtual R<u64> tell() = 0;
    virtual RV seek(i64 offset, SeekMode mode) = 0;
    virtual u64 get_size() = 0;
    virtual RV set_size(u64 sz) = 0;
};

Implementing interfaces

Interfaces can be implemented by declaring structures that inherit from them.

struct WindowsFile : ISeekableStream
{
    lustruct("WindowsFile", "{95a2e5b2-d48a-4f19-bfb8-22c273c0ad4b}");
    luiimpl();

    HANDLE m_file;
    virtual RV read(void* buffer, usize size, usize* read_bytes) override;
    virtual RV write(const void* buffer, usize size, usize* write_bytes) override;
    virtual R<u64> tell() override;
    virtual RV seek(i64 offset, SeekMode mode) override;
    virtual u64 get_size() override;
    virtual RV set_size(u64 sz) override;
};

Note that interfaces only work for boxed objects. So the structure type that implements the interface should be registered to the type system either by register_boxed_type or by register_struct_type, and the object that implements the interface should only be created as boxed objects using new_object. Luna SDK also requires you to register interface implementation information to the system, so the registration code for the type above may looks like this:

register_boxed_type<WindowsFile>();
impl_interface_for_type<WindowsFile, ISeekableStream, IStream>();

You can always use is_interface_implemented_by_type to check whether one interface is implemented by the specified type.

Interface conversion

Besides the dynamic casting functionality provided by boxed objects, Luna SDK provides additional functionalities for casting between interface pointers and boxed object pointers safely at run time.

Casting typed object pointers to interface pointers

Casting typed object pointers to interface pointers can be done directly using static_cast or C-style pointer casting, since the boxed type inherits from the interface type by declaration.

Casting object_t to interface pointers

If the underlying type of the interface is not exposed to the user, the user can use query_interface to fetch one pointer interface from object_t. This function will check whether the specified interface is implemented by the type of the specified object, and returns nullptr if not. The returned pointer can be casting to the specified interface type safely by using static_cast or C-style pointer casting.

Casting interface pointers to object_t

Casting interface pointers to object_t can be done by calling get_object function of the interface. This function is declared in Interface structure, and is implemented by luiimpl macro, so all interfaces support this function. The returned type of get_object is object_t, which is a type-less pointer, the user can then casting the pointer to one typed pointer using dynamic casting.

Smart pointer for interface types

#include <Luna/Runtime/Ref.hpp>

Ref<T> and WeakRef<T> support interface types. You can use Ref<IStream> to refer one boxed object that comforms to IStream interface directly. Ref<T> handles type conversions automatically, so you can assign Ref of any type to each other, and the destination pointer will be set to nullptr if type casting fails.