5.3.19. C++ Integration: Class Adapters
This tutorial shows how to let daslang classes derive from a C++ abstract base class and have C++ call their virtual methods seamlessly. This is the key pattern for game object systems where scripts define behavior but C++ owns the update loop.
Topics covered:
The 3-layer adapter pattern (base → generated adapter → bridge)
Pre-generated class adapter via
daslib/cpp_bindcompileBuiltinModulewith XDD-embedded.das.incfilesRTTI (
StructInfo *) for adapter constructiongetDasClassMethod/das_invoke_functionfor virtual dispatchVirtual dispatch across the C++/daslang boundary
5.3.19.1. Architecture
The pattern uses three layers:
C++ base class (
BaseClass) — pure virtual interface that C++ code programs against.Generated adapter (
TutorialBaseClass) — generated bydaslib/cpp_bind’slog_cpp_class_adapter. For each virtual method, it providesget_<method>(checks if the daslang class overrides the method) andinvoke_<method>(calls it viadas_invoke_function).Dual-inheritance bridge (
BaseClassAdapter) — inherits both the real C++ class and the generated adapter. When C++ callsobj->update(dt)through the vtable, the bridge checks for a daslang override and invokes it.
5.3.19.2. Prerequisites
Tutorial 08 (tutorial_integration_cpp_methods) — binding methods.
Tutorial 10 (tutorial_integration_cpp_custom_modules) — custom modules.
Understanding of
ManagedStructureAnnotationanddas_invoke_function.
5.3.19.3. Layer 1: C++ base class
A simple abstract interface that C++ code iterates over:
class BaseClass {
public:
virtual ~BaseClass() = default;
virtual void update(float dt) = 0;
virtual float3 getPosition() = 0;
};
C++ maintains a list of shared_ptr<BaseClass> objects and calls
update / getPosition polymorphically.
5.3.19.4. Layer 2: Generated adapter
The adapter is generated by daslib/cpp_bind (see log_cpp_class_adapter).
It lives in a .inc file that you #include in your C++ source:
#include "19_class_adapters_gen.inc"
The generated code provides:
TutorialBaseClass— storesStructInfoand method lookup dataget_update(classPtr)— returns theFuncif the daslang class overridesupdate, ornullptrif it does notinvoke_update(ctx, fn, classPtr, dt)— calls the override viadas_invoke_function<void>::invoke
5.3.19.5. Layer 3: Bridge class
The bridge inherits both BaseClass (for the vtable) and
TutorialBaseClass (for daslang method lookup):
class BaseClassAdapter : public BaseClass,
public TutorialBaseClass {
public:
BaseClassAdapter(char * pClass,
const StructInfo * info,
Context * ctx)
: TutorialBaseClass(info),
classPtr(pClass), context(ctx) {}
void update(float dt) override {
if (auto fn = get_update(classPtr)) {
invoke_update(context, fn, classPtr, dt);
}
}
float3 getPosition() override {
if (auto fn = get_get_position(classPtr)) {
return invoke_get_position(context, fn, classPtr);
}
return float3(0.0f);
}
protected:
void * classPtr;
Context * context;
};
5.3.19.6. Module with XDD-embedded daslang
The abstract base class definition lives in a .das file that is
embedded into C++ using XDD (a CMake macro that converts files to
byte arrays). The module loads it with compileBuiltinModule:
#include "class_adapters_module.das.inc"
class Module_Tutorial19 : public Module {
public:
Module_Tutorial19() : Module("tutorial_19") {
ModuleLibrary lib(this);
lib.addBuiltInModule();
addBuiltinDependency(lib, Module::require("rtti"));
addExtern<DAS_BIND_FUN(addObject)>(*this, lib, "add_object",
SideEffects::modifyExternal, "addObject");
compileBuiltinModule("class_adapters_module.das",
class_adapters_module_das,
sizeof(class_adapters_module_das));
}
};
REGISTER_MODULE(Module_Tutorial19);
5.3.19.7. The daslang side
Scripts derive from the exposed abstract class and use def override
to provide implementations:
options gen2
require tutorial_19
require rtti
class ExampleObject : TutorialBaseClass {
position : float3
speed : float
def override update(dt : float) : void {
position.x += speed * dt
}
def override get_position() : float3 {
return position
}
}
[export]
def test {
var obj = new ExampleObject()
obj.position = float3(0.0, 0.0, 0.0)
obj.speed = 10.0
unsafe {
add_object(addr(*obj), class_info(*obj), this_context())
}
let avg = tick(0.5)
print("After tick(0.5): avg position = ({avg.x}, {avg.y}, {avg.z})\n")
}
5.3.19.8. Build & run
cmake --build build --config Release --target integration_cpp_19
bin/Release/integration_cpp_19
Expected output:
After tick(0.5): avg position = (5, 0, 0)
See also
Full source:
19_class_adapters.cpp,
19_class_adapters.das,
class_adapters_module.das,
19_class_adapters_gen.inc
Previous tutorial: tutorial_integration_cpp_dynamic_scripts
Next tutorial: tutorial_integration_cpp_standalone_contexts
Related: tutorial_integration_cpp_methods, tutorial_integration_cpp_custom_modules