Binding virtual methods in GDExtension - v4.4.1

Godot Version

4.4.1.stable

Question

Good morning everyone. I’m experimenting with GDExtension to understand how the technology works in general. I’m having trouble with method binding, specifically I can’t seem to override them on the GDScript side, even though I can create my class and Godot sees it without any issues.

When I create a GDScript class that inherits from the one written in C++, which should override the benchmark and log_message methods, I always get the default results, the ones written in the C++ code… Does anyone know where I’m going wrong? Thank you so much.

Header of C++ class

#ifndef GDBENCKMARKWORK_H
#define GDBENCKMARKWORK_H

#include <godot_cpp/classes/node3d.hpp>
#include <godot_cpp/core/binder_common.hpp>
#include <godot_cpp/core/gdvirtual.gen.inc>

namespace godot
{

	class GDBenchmarkWork : public Node3D
	{
		GDCLASS(GDBenchmarkWork, Node3D)

	protected:
		static void _bind_methods();
		uint64_t _benchmark_execute();		

	public:
		GDBenchmarkWork();
		~GDBenchmarkWork();
		
		void run();		
		
		virtual void benchmark();		
		virtual String log_message();					

		GDVIRTUAL0(benchmark);		
		GDVIRTUAL0R(String, log_message);

	};

}

#endif

Implementation of C++ class

#include "gdbenchmarkwork.h"
#include <godot_cpp/core/class_db.hpp>
#include <godot_cpp/classes/time.hpp>
#include <godot_cpp/classes/os.hpp>
#include <godot_cpp/classes/engine.hpp>

using namespace godot;

// Protected
void GDBenchmarkWork::_bind_methods()
{
    ClassDB::bind_method(D_METHOD("run"), &GDBenchmarkWork::run);             
    GDVIRTUAL_BIND(benchmark);
    GDVIRTUAL_BIND(log_message);
    
    ADD_SIGNAL(MethodInfo("job_ended", PropertyInfo(Variant::STRING, "message")));
}

uint64_t GDBenchmarkWork::_benchmark_execute()
{
    uint64_t elapsed_time = Time::get_singleton()->get_ticks_msec();
        benchmark();
    elapsed_time = Time::get_singleton()->get_ticks_msec() - elapsed_time;    
    return elapsed_time;
}

// Public
GDBenchmarkWork::GDBenchmarkWork() {}
GDBenchmarkWork::~GDBenchmarkWork() {}

void GDBenchmarkWork::run()
{    
    uint64_t elapsed_time = _benchmark_execute();    
    String message = vformat(log_message(), elapsed_time);    
    emit_signal("job_ended", message);    
}


void GDBenchmarkWork::benchmark()
{
    print_line("Perde 250 msec. di tempo");
    OS::get_singleton()->delay_msec(250);
}

String GDBenchmarkWork::log_message()
{
    return "Processo sconosciuto. %d msec.";
}

Derived class on GDScript (not working, reporting value from original C++ code)

extends GDBenchmarkWork
class_name Benchmark1000Cubes

func _ready() -> void:
	print("Script Benchmark1000Cubes pronto!")

func benchmark() -> void:
	print("Non fa nulla")
	
func log_message() -> String:
	return "1000 Cubi su GDScript %d msec."

I’m not sure how, but I managed to find the solution. I’m attaching the code for the .cpp file, as it’s the only one that needs modification for everything to work. Let see last two methods…

#include "gdbenchmarkwork.h"
#include <godot_cpp/core/class_db.hpp>
#include <godot_cpp/classes/time.hpp>
#include <godot_cpp/classes/os.hpp>
#include <godot_cpp/classes/engine.hpp>
#include <godot_cpp/variant/variant.hpp>

using namespace godot;

// Protected
void GDBenchmarkWork::_bind_methods()
{
    ClassDB::bind_method(D_METHOD("run"), &GDBenchmarkWork::run);             
    
    GDVIRTUAL_BIND(benchmark);
    GDVIRTUAL_BIND(log_message);
        
    ADD_SIGNAL(MethodInfo("job_ended", PropertyInfo(Variant::STRING, "message")));
}

uint64_t GDBenchmarkWork::_benchmark_execute()
{
    uint64_t elapsed_time = Time::get_singleton()->get_ticks_msec();
        benchmark();
    elapsed_time = Time::get_singleton()->get_ticks_msec() - elapsed_time;    
    return elapsed_time;
}

// Public
GDBenchmarkWork::GDBenchmarkWork() {}
GDBenchmarkWork::~GDBenchmarkWork() {}

void GDBenchmarkWork::run()
{    
    uint64_t elapsed_time = _benchmark_execute();    
    String message = vformat(log_message(), elapsed_time);    
    emit_signal("job_ended", message);    
}

void GDBenchmarkWork::benchmark()
{
    if (!GDVIRTUAL_IS_OVERRIDDEN(benchmark)) {
        print_line("Perde 250 msec. di tempo");
        OS::get_singleton()->delay_msec(250);
    } else {    
        GDVIRTUAL_CALL(benchmark);    
    }
}

String GDBenchmarkWork::log_message()
{
	if (!GDVIRTUAL_IS_OVERRIDDEN(log_message)) {		
		return "Processo sconosciuto. %d msec.";
	} else {
        String message;
        GDVIRTUAL_CALL(log_message, message);
        return message;
	}
}