Glaze Language Interoperability Interface¶
The Glaze interop interface provides a C-compatible API for accessing C++ types and their members from other programming languages. This enables seamless integration of Glaze-based C++ code with languages like Julia, Python, Rust, and others.
Overview¶
The interop interface allows:
- Type Registration: Register C++ types with their member information
- Instance Management: Create and destroy C++ objects from other languages
- Member Access: Get/set data members and call member functions
- Type Descriptors: Rich type information for proper marshalling
- Container Support: Work with vectors, maps, optionals, variants, and other STL containers
- Async Support: Handle
std::shared_future
types (Note:std::future
is not supported as it's move-only)
Quick Start¶
C++ Side¶
#include "glaze/interop/interop.hpp"
// Define your struct
struct Person {
std::string name;
int age;
std::vector<std::string> hobbies;
void greet() const {
std::cout << "Hello, I'm " << name << "\n";
}
};
// Define glz::meta
template <>
struct glz::meta<Person> {
using T = Person;
static constexpr auto value = object(
&T::name,
&T::age,
&T::hobbies,
&T::greet
);
};
// Register the type
int main() {
glz::register_type<Person>("Person");
// Create an instance
Person alice{"Alice", 30, {"reading", "hiking"}};
// Register with error handling
if (!glz::register_instance("alice", alice)) {
std::cerr << "Error: " << glz::get_last_error_message() << std::endl;
}
}
From Other Languages (e.g., Julia)¶
[!NOTE]
Do not actually write code like this unless you're developing a new receiving library. For Julia the glaze-interop.jl library abstracts this away for cleaner and safer usage.
# Load the shared library
using Libdl
lib = dlopen("libglaze_interop")
# Get type information
type_info = ccall((:glz_get_type_info, lib), Ptr{GlzTypeInfo}, (Cstring,), "Person")
# Access a registered instance
alice_ptr = ccall((:glz_get_instance, lib), Ptr{Cvoid}, (Cstring,), "alice")
# Get type info and find member function
type_info = ccall((:glz_get_type_info, lib), Ptr{GlzTypeInfo}, (Cstring,), "Person")
# Find the greet member
greet_member = find_member(type_info, "greet")
# Call member function
ccall((:glz_call_member_function_with_type, lib), Ptr{Cvoid},
(Ptr{Cvoid}, Cstring, Ptr{GlzMemberInfo}, Ptr{Ptr{Cvoid}}, Ptr{Cvoid}),
alice_ptr, "Person", greet_member, C_NULL, C_NULL)
Building¶
Add to your CMakeLists.txt:
option(glaze_BUILD_INTEROP "Build language interoperability library" ON)
if(glaze_BUILD_INTEROP)
add_library(glaze_interop SHARED
src/interop/interop.cpp
)
target_link_libraries(glaze_interop PUBLIC glaze::glaze)
endif()
API Reference¶
Type Registration¶
Register a C++ type for interop. The type must have aglz::meta
specialization.
Instance Registration¶
Register a global instance that can be accessed from other languages. The type must be registered first usingregister_type
. Returns true on success, false on error.
C API Functions¶
Type Information¶
glz_get_type_info(const char* type_name)
- Get type metadataglz_get_type_info_by_hash(size_t type_hash)
- Get type by hash
Instance Management¶
glz_create_instance(const char* type_name)
- Create new instanceglz_destroy_instance(const char* type_name, void* instance)
- Destroy instanceglz_get_instance(const char* instance_name)
- Get registered instance
Member Access¶
glz_get_member_ptr(void* instance, const glz_member_info* member)
- Get member pointerglz_call_member_function_with_type(...)
- Call member function
Container Operations¶
glz_vector_view(void* vec_ptr, const glz_type_descriptor* type_desc)
- Get vector viewglz_vector_resize(...)
- Resize vectorglz_vector_push_back(...)
- Add element to vectorglz_optional_has_value(...)
- Check if optional has valueglz_optional_get_value(...)
- Get optional valueglz_optional_set_value(...)
- Set optional valueglz_variant_index(...)
- Get active variant alternative indexglz_variant_get(...)
- Get active variant valueglz_variant_set(...)
- Set variant alternativeglz_variant_holds_alternative(...)
- Check if variant holds specific type
Type Descriptors¶
Type descriptors provide detailed type information for proper marshalling:
enum glz_type_kind : uint8_t {
GLZ_TYPE_PRIMITIVE = 0, // Fundamental types (int, float, etc.)
GLZ_TYPE_STRING, // std::string and std::string_view
GLZ_TYPE_VECTOR, // std::vector<T>
GLZ_TYPE_MAP, // std::unordered_map<K,V>
GLZ_TYPE_COMPLEX, // std::complex<T>
GLZ_TYPE_STRUCT, // User-defined structs
GLZ_TYPE_OPTIONAL, // std::optional<T>
GLZ_TYPE_FUNCTION, // Member functions
GLZ_TYPE_SHARED_FUTURE, // std::shared_future<T>
GLZ_TYPE_VARIANT // std::variant<Ts...>
};
Error Handling¶
The interop system provides FFI-safe error handling using thread-local error state:
C++ API¶
// Returns false on error, true on success
if (!glz::register_instance("alice", alice)) {
// Access error information
auto error_code = glz::get_last_error();
auto error_msg = glz::get_last_error_message();
std::cerr << "Error " << error_code << ": " << error_msg << std::endl;
}
// Clear error state
glz::clear_error();
C API¶
// All C API functions now use error handling
glz_type_info* info = glz_get_type_info("Person");
if (!info) {
const char* msg = glz_get_last_error_message();
printf("Error: %s\n", msg);
}
// Create instance with error checking
void* instance = glz_create_instance("Person");
if (!instance) {
printf("Failed to create: %s\n", glz_get_last_error_message());
}
// Register instance (note: type name no longer needed)
if (!glz_register_instance("alice", alice_ptr)) {
glz_error_code code = glz_get_last_error();
const char* msg = glz_get_last_error_message();
printf("Error %d: %s\n", code, msg);
}
// Clear error state
glz_clear_error();
Error Codes¶
GLZ_ERROR_NONE
(0) - No errorGLZ_ERROR_TYPE_NOT_REGISTERED
(1) - Type must be registered before useGLZ_ERROR_INSTANCE_ALREADY_EXISTS
(2) - Instance name already in useGLZ_ERROR_INSTANCE_NOT_FOUND
(3) - Instance not foundGLZ_ERROR_INVALID_PARAMETER
(4) - Invalid parameter (null pointer, etc.)GLZ_ERROR_ALLOCATION_FAILED
(5) - Memory allocation failedGLZ_ERROR_TYPE_MISMATCH
(6) - Type mismatch in operationGLZ_ERROR_MEMBER_NOT_FOUND
(7) - Member not found in typeGLZ_ERROR_INTERNAL
(99) - Internal error
Member Functions¶
Member functions are automatically exposed when included in glz::meta
:
struct Calculator {
int add(int a, int b) { return a + b; }
std::vector<int> range(int n) {
std::vector<int> result;
for (int i = 0; i < n; ++i) result.push_back(i);
return result;
}
};
template <>
struct glz::meta<Calculator> {
using T = Calculator;
static constexpr auto value = object(
"add", &T::add,
"range", &T::range
);
};
Variant Support¶
The interop interface fully supports std::variant
types:
struct Configuration {
std::variant<int, std::string, std::vector<double>> value;
std::string get_type() const {
return std::visit([](auto&& arg) -> std::string {
using T = std::decay_t<decltype(arg)>;
if constexpr (std::is_same_v<T, int>) return "integer";
else if constexpr (std::is_same_v<T, std::string>) return "string";
else return "array";
}, value);
}
};
template <>
struct glz::meta<Configuration> {
using T = Configuration;
static constexpr auto value = object(
&T::value,
&T::get_type
);
};
// Usage from other languages:
// 1. Check active alternative: glz_variant_index()
// 2. Get current value: glz_variant_get()
// 3. Set new alternative: glz_variant_set(index, new_value)
// 4. Check if holds type: glz_variant_holds_alternative(index)
Variant API Functions¶
glz_variant_index(void* variant_ptr, const glz_type_descriptor* type_desc)
- Returns the zero-based index of the currently active alternativeglz_variant_get(void* variant_ptr, const glz_type_descriptor* type_desc)
- Returns a pointer to the currently active valueglz_variant_set(void* variant_ptr, const glz_type_descriptor* type_desc, uint64_t index, const void* value)
- Sets the variant to hold the alternative at the given index with the provided valueglz_variant_holds_alternative(void* variant_ptr, const glz_type_descriptor* type_desc, uint64_t index)
- Checks if the variant currently holds the alternative at the given indexglz_variant_type_at_index(const glz_type_descriptor* type_desc, uint64_t index)
- Returns the type descriptor for the alternative at the given indexglz_create_variant(const glz_type_descriptor* type_desc, uint64_t initial_index, const void* initial_value)
- Creates a new variant with the specified initial alternativeglz_destroy_variant(void* variant_ptr, const glz_type_descriptor* type_desc)
- Destroys a variant created withglz_create_variant
Async Operations¶
Support for std::shared_future
:
struct AsyncWorker {
std::shared_future<int> compute_async() {
return std::async(std::launch::async, []() {
std::this_thread::sleep_for(std::chrono::seconds(1));
return 42;
}).share();
}
};
// From other language:
// 1. Call the async function to get a future handle
// 2. Check if ready: glz_shared_future_is_ready()
// 3. Wait if needed: glz_shared_future_wait()
// 4. Get result: glz_shared_future_get()
C++ Client Library¶
The interop interface includes a C++ client library for consuming Glaze-enabled shared libraries. This enables plugin architectures and dynamic module loading in C++ applications.
Using the C++ Client¶
#include "glaze/interop/client.hpp"
// Load a shared library with Glaze interop
glz::interop::interop_library lib("./my_plugin.so"); // .dll on Windows, .dylib on macOS
// Get type information
auto type_info = lib.get_type("MyPluginClass");
// Create an instance
auto instance = lib.create_instance("MyPluginClass");
// Access members
instance->set_member("name", std::string("Example"));
int value = instance->get_member<int>("value");
// Call member functions
double result = instance->call_function<double>("calculate", 10.0, 20.0);
// Access global instances
auto global = lib.get_instance("global_instance");
Plugin Architecture Example¶
// Plugin interface (shared by all plugins)
struct PluginInterface {
std::string name;
std::string version;
virtual void execute() = 0;
};
// Load plugins dynamically
class PluginManager {
std::vector<glz::interop::interop_library> plugins;
public:
void load_plugin(const std::string& path) {
plugins.emplace_back(path);
auto& lib = plugins.back();
// Each plugin registers a "Plugin" type
auto plugin = lib.create_instance("Plugin");
std::cout << "Loaded: " << plugin->get_member<std::string>("name") << "\n";
}
};
Building C++ Clients¶
# Enable interop in your project
option(glaze_BUILD_INTEROP "Build language interoperability library" ON)
# Client application
add_executable(my_app main.cpp)
target_link_libraries(my_app PRIVATE glaze_interop_client)
# Plugin library
add_library(my_plugin SHARED plugin.cpp)
target_link_libraries(my_plugin PRIVATE glaze_interop)
Performance Considerations¶
- Type information is cached after first access
- Member function pointers are resolved at registration time
- Minimal overhead for primitive types
- Efficient handling of containers through view structures
- C++ client adds minimal overhead over raw C API
Thread Safety¶
- Type registration should be done at startup (not thread-safe)
- Instance access is thread-safe for const operations
- Mutable operations require external synchronization
Examples¶
See tests/interop/
for complete examples including:
- Basic type registration and access (
interop_basic_test.cpp
) - Container manipulation and member function calls
- Plugin architecture (
interop_plugins_test.cpp
) - Complex nested structures (
interop_structures_test.cpp
) - Variant types and type switching (
interop_variant_test.cpp
) - C FFI compatibility (
test_c_ffi.c
) - Performance benchmarks (
interop_performance_test.cpp
)
Language Bindings¶
- Julia: Full support via glaze-interop.jl
Limitations¶
- Virtual functions are not directly supported
- Move-only types require special handling
- Maximum function arity depends on implementation