Repository: godotengine/godot-cpp (C++)

Issue:
cast_to and virtual function confusing behavior
**Summary:** I ran into confusion implementing my own virtual function in a GDExtension class and overwritting it in GDScript.  There may be a fix by properly binding the virtual method.  For any solution an example could be added to the `test/src/example.h`

**Minimal Test Program:**  Only showing the relevant part of the class, not the boilerplate for GDExtension.
```
// test.h
class Test : public Node 
{
    GDCLASS(Test, Node);
protected:
    static void _bind_methods();
public:
    virtual void update();
    void update_all_children();
};

// test.cpp
void Test::_bind_methods()
{
    // Abstract Methods
    ClassDB::bind_method("update", &Test::update);
    ClassDB::bind_method("update_all_children", &Test::update_all_children);
}
void Test::update()
{
    UtilityFunctions::print("Default update from node=",get_name());
}
void Test::update_all_children()
{
    for(size_t i=0; i<get_child_count(); ++i)
    {
        (Object::cast_to<Test>(get_child(i)))->update();
    }
}
```

**Problem:** If I overwrite the `update` method in GDScript, then future calls directly to update in GDScript call the correct `update`.  The problem is when calling `update_all_children` in GDScript in which case all children will call the `update` you see defined in `test.cpp`.

**Temporary Fix:** Replace `->update()` with `->call("update").  This produces the correct output for all children.

**Solution:** I think the `->update()` syntax should call the overridden version in GDScript, not the cpp implementation.  Perhaps this is how it works if using `GDVIRTUAL0` or `bind_virtual_method` would.  There isn't currenty an example for this, and I couldn't get it to compile on my own.   Either way there isn't currently an example for defining and using your own virtual method.


can GDVIRTUAL_BIND and GDVIRTUAL_CALL be added to the lib?
Looks like these two macroses are used by godot to call gdscript methods.
Can these two be added into godot-cpp?
Virtual function binding
### Godot version

4.1 stable

### godot-cpp version

4.1 stable

### System information

Windows 10 and Linux Mint

### Issue description

When binding a virtual function in a custom class, things become rather *finicky*. For the purposes of explanation I will exemplify things using `Resource` as base class, but this behavior occurs with any other base.

Suppose I have this:

```
class CustomResource : public Resource {
....
   static void _bind_methods() {
      BIND_VIRTUAL_METHOD(CustomResource, perform_task);
   }
...
   virtual void perform_task() { some_default_behavior... }
};
```

When in GDScript, if I attempt to directly call that virtual function with `some_instance.perform_task()` I will get an error telling that the function does not exist. Yet, If I do `some_instance.call("perform_task")` then the function will be called and the default behavior will occur. I can then create a derived class in GDScript and override `perform_task()` without any problem. In that case either `.perform_task()` or `call("perform_task")` will work in GDScript.

Now, from the C++ code:

1 - If I directly call `->perform_task()`, then the default behavior will occur. If I create a derived class in C++ and override that virtual function, then I will get the overridden code. However if I override that function in a GDScript class then I will still get the C++ default behavior.
2 - If I call the function with `->call("perform_task")` I will be able to override that function from GDScript but not in a C++ class. 

The workaround that I'm currently using is something like this:

```
class CustomResource : public Resource {
...
   static void _bind_methods() {
      BIND_VIRTUAL_METHOD(CustomResource, _perform_task);
      ClassDB::bind_method(D_METHOD("perform_task"), &CustomResource::perform_task);
   }
...
   virtual void _perform_task() { some_default_behavior... }
   void perform_task() { call("_perform_task"); }
};
```

This does present the intuitive `.perform_task()` way of calling the function from GDScript and `->perform-task()` from C++. However there are problems here.

1 - Using `call()` does not work in a const function. Well, not without some *"hacky"* code (casting the `this` into a non cost version).

2 - In a C++ derived class I need to do this:

```
class SpecialCustomResource : public CustomResource {
...
   static void _bind_methods() {
      ClassDB::bind_method(D_METHOD("_perform_task"), &SpecialCustomResource::_perform_task);
   }
...
   virtual void _perform_task() override { some_other_behavior... }
};
```

If I don't bind the overridden function as a normal one then the `call()` will not find the desired function. Another thing that occurs when the function is not registered is that if you are expecting a return value `nullptr` will be the result.

Is the described intended behavior?

### Steps to reproduce

The entire explanation is, in a way, steps to reproduce the problem.

### Minimal reproduction project

I don't have one in hand. If really absolutely necessary I can work on one


Discussion: 

I see BIND_VIRTUAL_METHOD exists.
Can we somehow use it with custom classes creating custom virtual functions?
These infomations are added to ClassDB, but not be used in godot editor, I guess.
GDCLASS macross has inside it:

```
template <class T, class B>                                                                                                                                                        \
	static void register_virtuals() {                                                                                                                                                  \
		m_inherits::register_virtuals<T, B>();                                                                                                                                         \
	}      
```
so you can't add this function in your class to bind your `virtuals`.
Maybe we can create another macros without declared `register_virtuals` in it? 
> GDCLASS macross has inside it:
> 
> ```
> template <class T, class B>                                                                                                                                                        \
> 	static void register_virtuals() {                                                                                                                                                  \
> 		m_inherits::register_virtuals<T, B>();                                                                                                                                         \
> 	}      
> ```
> 
> so you can't add this function in your class to bind your `virtuals`. Maybe we can create another macros without declared `register_virtuals` in it?

I'm dont't not the function of  this template method.
It seem do nothing form parent class to child class recursively, and just be called after `_bind_methods`.

Ignoring the call order, for your custom virtual methods, you can bind them in `_bind_methods` directly.

`BIND_VIRTUAL_METHOD ` call `ClassDB::bind_virtual_method`
```
void ClassDB::bind_virtual_method(const StringName &p_class, const StringName &p_method, GDExtensionClassCallVirtual p_call) {
	std::unordered_map<StringName, ClassInfo>::iterator type_it = classes.find(p_class);
	ERR_FAIL_COND_MSG(type_it == classes.end(), String("Class '{0}' doesn't exist.").format(Array::make(p_class)));

	ClassInfo &type = type_it->second;

	ERR_FAIL_COND_MSG(type.method_map.find(p_method) != type.method_map.end(), String("Method '{0}::{1}()' already registered as non-virtual.").format(Array::make(p_class, p_method)));
	ERR_FAIL_COND_MSG(type.virtual_methods.find(p_method) != type.virtual_methods.end(), String("Virtual '{0}::{1}()' method already registered.").format(Array::make(p_class, p_method)));

	type.virtual_methods[p_method] = p_call;
}
```
As you can see. it is added to `virtual_methods`, so the infomation of custom virtual methods are existed in `ClassDB`. Just not be used in Document( to refer) and LSP( to code hint). 

Currently, you can use `Object::call()` instead of `GDVIRTUAL_CALL`.
I don't know there has a better way or not.
Thank you. It does call function in the script.
But it turns out than you cant call function ,declared this way, from GDScript. It cant find it. 
> But it turns out than you cant call function ,declared this way, from GDScript. It cant find it.

I think you should call `call("virtul_method_bind_in_cpp")` in GDScript, too.

Hi @Daylily-Zeleen @RonYanDaik  , 
I need more detail on the workaround you mentioned. I'm  want to create a plugin that allows users to override my methods in gdscript, but calling those function in gdextension.

```c++
// in gdextension
struct BaseFeature : Resource{ // registered with ClassDB::register_class<BaseFeature >(true); for virtual
GDCLASS(BaseFeature,Resource)
 virtual void my_function() {} // Will be overwrited in gdscript.

 static void _bind_methods(){
  ClassDB::bind_method(D_METHOD("my_function"), &BaseFeature ::my_function); 
}
};
``` 

my goal is to have an array of them, and call them like this in gdextension : 
```c++
      for(auto i = 0; i < array_of_features.size(); ++i )
      {            
          BaseFeature * f = Object::cast_to<BaseFeature >(array_of_features[i]);
          f->my_function(); // Call the overrided function in a gdscript.
       }
```

For now it only call the base version of the functions, but I created a new resource from a children class.
```gdscript
class_name MyFeature  extends BaseFeature
func my_function(): # I has the sign that it override a function
   print("hello world")
```

My code is very data intensive, but I would like to override a few functions in gdscript for configuration and let the bulk of the work be done in c++.

Thank you, any help is useful
Hi @Daylily-Zeleen @RonYanDaik  , 
I need more detail on the workaround you mentioned. I'm  want to create a plugin that allows users to override my methods in gdscript, but calling those function in gdextension.

```c++
// in gdextension
struct BaseFeature : Resource{ // registered with ClassDB::register_class<BaseFeature >(true); for virtual
GDCLASS(BaseFeature,Resource)
 virtual void my_function() {} // Will be overwrited in gdscript.

 static void _bind_methods(){
  ClassDB::bind_method(D_METHOD("my_function"), &BaseFeature ::my_function); 
}
};
``` 

my goal is to have an array of them, and call them like this in gdextension : 
```c++
      for(auto i = 0; i < array_of_features.size(); ++i )
      {            
          BaseFeature * f = Object::cast_to<BaseFeature >(array_of_features[i]);
          f->my_function(); // Call the overrided function in a gdscript.
       }
```

For now it only call the base version of the functions, but I created a new resource from a children class.
```gdscript
class_name MyFeature  extends BaseFeature
func my_function(): # I has the sign that it override a function
   print("hello world")
```

My code is very data intensive, but I would like to override a few functions in gdscript for configuration and let the bulk of the work be done in c++.

Thank you, any help is useful
Just use 'call()' both c++ and gds.

This seems similar to these earlier issues:

- https://github.com/godotengine/godot-cpp/issues/1072
- https://github.com/godotengine/godot-cpp/issues/910

I think the problem may be that virtual methods are meant to be implemented in godot-cpp the same way as in Godot modules, which means using the `GDVIRTUAL*()` and `GDVIRTUAL_CALL()` macros, but those haven't actually been implemented in godot-cpp yet.

I'll double check with some of the more senior members of the team.
Correct me if I'm wrong but I keep seeing confusion about what constitutes actual virtual method bindings in GodotCpp, because while there is `godot::ClassDB::bind_virtual_method` (which is wrapped by `BIND_VIRTUAL_METHOD`), it seems to have been thought solely for the *internal bindings of core classes* and not for creating actual custom virtual functions (which should rather need `GDVIRTUAL` and `GDVIRTUAL_BIND`). Therefore attempts to use it as if it was the latter results in mixed up behaviors.

> I think the problem may be that virtual methods are meant to be implemented in godot-cpp the same way as in Godot modules, which means using the `GDVIRTUAL*()` and `GDVIRTUAL_CALL()` macros, but those haven't actually been implemented in godot-cpp yet.
> 
> I'll double check with some of the more senior members of the team.

Thank you. I did stumble before into #1072 but not #910. The first issue seem like the solution, while the second issue matches more closely my description (so sorry for somewhat creating a repeated issue).

> Correct me if I'm wrong but I keep seeing confusion about what constitutes actual virtual method bindings in GodotCpp, because while there is `godot::ClassDB::bind_virtual_method` (which is wrapped by `BIND_VIRTUAL_METHOD`), it seems to have been thought solely for the _internal bindings of core classes_ and not for creating actual custom virtual functions (which should rather need `GDVIRTUAL` and `GDVIRTUAL_BIND`). Therefore attempts to use it as if it was the latter results in mixed up behaviors.

This basically confirms what @dsnopek said. But then, `ClassDB::bind_virtual_method` currently being the only way for a GDExtension to tell Godot "hey, this is a virtual function" I think it's only natural that people will indeed attempt to use (and be confused with) like in the second use case.



Fix (patch):
diff --git a/binding_generator.py b/binding_generator.py
index 7a6fe2481..5f9bf66b7 100644
--- a/binding_generator.py
+++ b/binding_generator.py
@@ -70,6 +70,136 @@ def generate_wrappers(target):
         f.write(txt)
 
 
+def generate_virtual_version(argcount, const=False, returns=False):
+    s = """#define GDVIRTUAL$VER($RET m_name $ARG)\\
+	StringName _gdvirtual_##m_name##_sn = #m_name;\\
+	template <bool required>\\
+	_FORCE_INLINE_ bool _gdvirtual_##m_name##_call($CALLARGS) $CONST {\\
+		if (::godot::internal::gdextension_interface_object_has_script_method(_owner, &_gdvirtual_##m_name##_sn)) { \\
+			GDExtensionCallError ce;\\
+			$CALLSIARGS\\
+			$CALLSIBEGIN::godot::internal::gdextension_interface_object_call_script_method(_owner, &_gdvirtual_##m_name##_sn, $CALLSIARGPASS, $CALLSIRETPASS, &ce);\\
+			if (ce.error == GDEXTENSION_CALL_OK) {\\
+				$CALLSIRET\\
+				return true;\\
+			}\\
+		}\\
+		if (required) {\\
+			ERR_PRINT_ONCE("Required virtual method " + get_class() + "::" + #m_name + " must be overridden before calling.");\\
+			$RVOID\\
+		}\\
+		return false;\\
+	}\\
+	_FORCE_INLINE_ bool _gdvirtual_##m_name##_overridden() const {\\
+		return godot::internal::gdextension_interface_object_has_script_method(_owner, &_gdvirtual_##m_name##_sn); \\
+	}\\
+	_FORCE_INLINE_ static MethodInfo _gdvirtual_##m_name##_get_method_info() {\\
+		MethodInfo method_info;\\
+		method_info.name = #m_name;\\
+		method_info.flags = $METHOD_FLAGS;\\
+		$FILL_METHOD_INFO\\
+		return method_info;\\
+	}
+
+"""
+
+    sproto = str(argcount)
+    method_info = ""
+    if returns:
+        sproto += "R"
+        s = s.replace("$RET", "m_ret,")
+        s = s.replace("$RVOID", "(void)r_ret;")  # If required, may lead to uninitialized errors
+        method_info += "method_info.return_val = GetTypeInfo<m_ret>::get_class_info();\\\n"
+        method_info += "\t\tmethod_info.return_val_metadata = GetTypeInfo<m_ret>::METADATA;"
+    else:
+        s = s.replace("$RET ", "")
+        s = s.replace("\t\t\t$RVOID\\\n", "")
+
+    if const:
+        sproto += "C"
+        s = s.replace("$CONST", "const")
+        s = s.replace("$METHOD_FLAGS", "METHOD_FLAG_VIRTUAL | METHOD_FLAG_CONST")
+    else:
+        s = s.replace("$CONST ", "")
+        s = s.replace("$METHOD_FLAGS", "METHOD_FLAG_VIRTUAL")
+
+    s = s.replace("$VER", sproto)
+    argtext = ""
+    callargtext = ""
+    callsiargs = ""
+    callsiargptrs = ""
+    if argcount > 0:
+        argtext += ", "
+        callsiargs = f"Variant vargs[{argcount}] = {{ "
+        callsiargptrs = f"\t\t\tconst Variant *vargptrs[{argcount}] = {{ "
+    for i in range(argcount):
+        if i > 0:
+            argtext += ", "
+            callargtext += ", "
+            callsiargs += ", "
+            callsiargptrs += ", "
+        argtext += f"m_type{i + 1}"
+        callargtext += f"m_type{i + 1} arg{i + 1}"
+        callsiargs += f"Variant(arg{i + 1})"
+        callsiargptrs += f"&vargs[{i}]"
+        if method_info:
+            method_info += "\\\n\t\t"
+        method_info += f"method_info.arguments.push_back(GetTypeInfo<m_type{i + 1}>::get_class_info());\\\n"
+        method_info += f"\t\tmethod_info.arguments_metadata.push_back(GetTypeInfo<m_type{i + 1}>::METADATA);"
+
+    if argcount:
+        callsiargs += " };\\\n"
+        callsiargptrs += " };"
+        s = s.replace("$CALLSIARGS", callsiargs + callsiargptrs)
+        s = s.replace("$CALLSIARGPASS", f"(const GDExtensionConstVariantPtr *)vargptrs, {argcount}")
+    else:
+        s = s.replace("\t\t\t$CALLSIARGS\\\n", "")
+        s = s.replace("$CALLSIARGPASS", "nullptr, 0")
+
+    if returns:
+        if argcount > 0:
+            callargtext += ", "
+        callargtext += "m_ret &r_ret"
+        s = s.replace("$CALLSIBEGIN", "Variant ret;\\\n\t\t\t")
+        s = s.replace("$CALLSIRETPASS", "&ret")
+        s = s.replace("$CALLSIRET", "r_ret = VariantCaster<m_ret>::cast(ret);")
+    else:
+        s = s.replace("$CALLSIBEGIN", "")
+        s = s.replace("$CALLSIRETPASS", "nullptr")
+        s = s.replace("\t\t\t\t$CALLSIRET\\\n", "")
+
+    s = s.replace(" $ARG", argtext)
+    s = s.replace("$CALLARGS", callargtext)
+    if method_info:
+        s = s.replace("$FILL_METHOD_INFO", method_info)
+    else:
+        s = s.replace("\t\t$FILL_METHOD_INFO\\\n", method_info)
+
+    return s
+
+
+def generate_virtuals(target):
+    max_versions = 12
+
+    txt = """/* THIS FILE IS GENERATED DO NOT EDIT */
+#ifndef GDEXTENSION_GDVIRTUAL_GEN_H
+#define GDEXTENSION_GDVIRTUAL_GEN_H
+
+"""
+
+    for i in range(max_versions + 1):
+        txt += f"/* {i} Arguments */\n\n"
+        txt += generate_virtual_version(i, False, False)
+        txt += generate_virtual_version(i, False, True)
+        txt += generate_virtual_version(i, True, False)
+        txt += generate_virtual_version(i, True, True)
+
+    txt += "#endif // GDEXTENSION_GDVIRTUAL_GEN_H\n"
+
+    with open(target, "w", encoding="utf-8") as f:
+        f.write(txt)
+
+
 def get_file_list(api_filepath, output_dir, headers=False, sources=False):
     api = {}
     files = []
@@ -81,6 +211,7 @@ def get_file_list(api_filepath, output_dir, headers=False, sources=False):
     source_gen_folder = Path(output_dir) / "gen" / "src"
 
     files.append(str((core_gen_folder / "ext_wrappers.gen.inc").as_posix()))
+    files.append(str((core_gen_folder / "gdvirtual.gen.inc").as_posix()))
 
     for builtin_class in api["builtin_classes"]:
         if is_pod_type(builtin_class["name"]):
@@ -204,6 +335,7 @@ def generate_builtin_bindings(api, output_dir, build_config):
     source_gen_folder.mkdir(parents=True, exist_ok=True)
 
     generate_wrappers(core_gen_folder / "ext_wrappers.gen.inc")
+    generate_virtuals(core_gen_folder / "gdvirtual.gen.inc")
 
     # Store types beforehand.
     for builtin_api in api["builtin_classes"]:
diff --git a/gdextension/gdextension_interface.h b/gdextension/gdextension_interface.h
index d58f0226d..765952cfe 100644
--- a/gdextension/gdextension_interface.h
+++ b/gdextension/gdextension_interface.h
@@ -364,13 +364,18 @@ typedef struct {
 	GDExtensionClassMethodPtrCall ptrcall_func;
 	uint32_t method_flags; // Bitfield of `GDExtensionClassMethodFlags`.
 
-	/* If `has_return_value` is false, `return_value_info` and `return_value_metadata` are ignored. */
+	/* If `has_return_value` is false, `return_value_info` and `return_value_metadata` are ignored.
+	 *
+	 * @todo Consider dropping `has_return_value` and making the other two properties match `GDExtensionMethodInfo` and `GDExtensionClassVirtualMethod` for consistency in future version of this struct.
+	 */
 	GDExtensionBool has_return_value;
 	GDExtensionPropertyInfo *return_value_info;
 	GDExtensionClassMethodArgumentMetadata return_value_metadata;
 
 	/* Arguments: `arguments_info` and `arguments_metadata` are array of size `argument_count`.
 	 * Name and hint information for the argument can be omitted in release builds. Class name should always be present if it applies.
+	 *
+	 * @todo Consider renaming `arguments_info` to `arguments` for consistency in future version of this struct.
 	 */
 	uint32_t argument_count;
 	GDExtensionPropertyInfo *arguments_info;
@@ -381,6 +386,18 @@ typedef struct {
 	GDExtensionVariantPtr *default_arguments;
 } GDExtensionClassMethodInfo;
 
+typedef struct {
+	GDExtensionStringNamePtr name;
+	uint32_t method_flags; // Bitfield of `GDExtensionClassMethodFlags`.
+
+	GDExtensionPropertyInfo return_value;
+	GDExtensionClassMethodArgumentMetadata return_value_metadata;
+
+	uint32_t argument_count;
+	GDExtensionPropertyInfo *arguments;
+	GDExtensionClassMethodArgumentMetadata *arguments_metadata;
+} GDExtensionClassVirtualMethodInfo;
+
 typedef void (*GDExtensionCallableCustomCall)(void *callable_userdata, const GDExtensionConstVariantPtr *p_args, GDExtensionInt p_argument_count, GDExtensionVariantPtr r_return, GDExtensionCallError *r_error);
 typedef GDExtensionBool (*GDExtensionCallableCustomIsValid)(void *callable_userdata);
 typedef void (*GDExtensionCallableCustomFree)(void *callable_userdata);
@@ -2268,6 +2285,34 @@ typedef GDExtensionObjectPtr (*GDExtensionInterfaceObjectGetInstanceFromId)(GDOb
  */
 typedef GDObjectInstanceID (*GDExtensionInterfaceObjectGetInstanceId)(GDExtensionConstObjectPtr p_object);
 
+/**
+ * @name object_has_script_method
+ * @since 4.3
+ *
+ * Checks if this object has a script with the given method.
+ *
+ * @param p_object A pointer to the Object.
+ * @param p_method A pointer to a StringName identifying the method.
+ *
+ * @returns true if the object has a script and that script has a method with the given name. Returns false if the object has no script.
+ */
+typedef GDExtensionBool (*GDExtensionInterfaceObjectHasScriptMethod)(GDExtensionConstObjectPtr p_object, GDExtensionConstStringNamePtr p_method);
+
+/**
+ * @name object_call_script_method
+ * @since 4.3
+ *
+ * Call the given script method on this object.
+ *
+ * @param p_object A pointer to the Object.
+ * @param p_method A pointer to a StringName identifying the method.
+ * @param p_args A pointer to a C array of Variant.
+ * @param p_argument_count The number of arguments.
+ * @param r_return A pointer a Variant which will be assigned the return value.
+ * @param r_error A pointer the structure which will hold error information.
+ */
+typedef void (*GDExtensionInterfaceObjectCallScriptMethod)(GDExtensionObjectPtr p_object, GDExtensionConstStringNamePtr p_method, const GDExtensionConstVariantPtr *p_args, GDExtensionInt p_argument_count, GDExtensionUninitializedVariantPtr r_return, GDExtensionCallError *r_error);
+
 /* INTERFACE: Reference */
 
 /**
@@ -2483,6 +2528,20 @@ typedef void (*GDExtensionInterfaceClassdbRegisterExtensionClass2)(GDExtensionCl
  */
 typedef void (*GDExtensionInterfaceClassdbRegisterExtensionClassMethod)(GDExtensionClassLibraryPtr p_library, GDExtensionConstStringNamePtr p_class_name, const GDExtensionClassMethodInfo *p_method_info);
 
+/**
+ * @name classdb_register_extension_class_virtual_method
+ * @since 4.3
+ *
+ * Registers a virtual method on an extension class in ClassDB, that can be implemented by scripts or other extensions.
+ *
+ * Provided struct can be safely freed once the function returns.
+ *
+ * @param p_library A pointer the library received by the GDExtension's entry point function.
+ * @param p_class_name A pointer to a StringName with the class name.
+ * @param p_method_info A pointer to a GDExtensionClassMethodInfo struct.
+ */
+typedef void (*GDExtensionInterfaceClassdbRegisterExtensionClassVirtualMethod)(GDExtensionClassLibraryPtr p_library, GDExtensionConstStringNamePtr p_class_name, const GDExtensionClassVirtualMethodInfo *p_method_info);
+
 /**
  * @name classdb_register_extension_class_integer_constant
  * @since 4.1
diff --git a/include/godot_cpp/classes/wrapped.hpp b/include/godot_cpp/classes/wrapped.hpp
index 32caf39b1..25a93dfc7 100644
--- a/include/godot_cpp/classes/wrapped.hpp
+++ b/include/godot_cpp/classes/wrapped.hpp
@@ -36,6 +36,7 @@
 #include <godot_cpp/core/property_info.hpp>
 
 #include <godot_cpp/templates/list.hpp>
+#include <godot_cpp/templates/vector.hpp>
 
 #include <godot_cpp/godot.hpp>
 
@@ -107,6 +108,26 @@ class Wrapped {
 	GodotObject *_owner = nullptr;
 };
 
+_FORCE_INLINE_ void snarray_add_str(Vector<StringName> &arr) {
+}
+
+_FORCE_INLINE_ void snarray_add_str(Vector<StringName> &arr, const StringName &p_str) {
+	arr.push_back(p_str);
+}
+
+template <class... P>
+_FORCE_INLINE_ void snarray_add_str(Vector<StringName> &arr, const StringName &p_str, P... p_args) {
+	arr.push_back(p_str);
+	snarray_add_str(arr, p_args...);
+}
+
+template <class... P>
+_FORCE_INLINE_ Vector<StringName> snarray(P... p_args) {
+	Vector<StringName> arr;
+	snarray_add_str(arr, p_args...);
+	return arr;
+}
+
 namespace internal {
 
 GDExtensionPropertyInfo *create_c_property_list(const ::godot::List<::godot::PropertyInfo> &plist_cpp, uint32_t *r_size);
@@ -445,4 +466,14 @@ public:
 // Don't use this for your classes, use GDCLASS() instead.
 #define GDEXTENSION_CLASS(m_class, m_inherits) GDEXTENSION_CLASS_ALIAS(m_class, m_class, m_inherits)
 
+#define GDVIRTUAL_CALL(m_name, ...) _gdvirtual_##m_name##_call<false>(__VA_ARGS__)
+#define GDVIRTUAL_CALL_PTR(m_obj, m_name, ...) m_obj->_gdvirtual_##m_name##_call<false>(__VA_ARGS__)
+
+#define GDVIRTUAL_REQUIRED_CALL(m_name, ...) _gdvirtual_##m_name##_call<true>(__VA_ARGS__)
+#define GDVIRTUAL_REQUIRED_CALL_PTR(m_obj, m_name, ...) m_obj->_gdvirtual_##m_name##_call<true>(__VA_ARGS__)
+
+#define GDVIRTUAL_BIND(m_name, ...) ::godot::ClassDB::add_virtual_method(get_class_static(), _gdvirtual_##m_name##_get_method_info(), ::godot::snarray(__VA_ARGS__));
+#define GDVIRTUAL_IS_OVERRIDDEN(m_name) _gdvirtual_##m_name##_overridden()
+#define GDVIRTUAL_IS_OVERRIDDEN_PTR(m_obj, m_name) m_obj->_gdvirtual_##m_name##_overridden()
+
 #endif // GODOT_WRAPPED_HPP
diff --git a/include/godot_cpp/core/class_db.hpp b/include/godot_cpp/core/class_db.hpp
index af394f041..e5bccf3d4 100644
--- a/include/godot_cpp/core/class_db.hpp
+++ b/include/godot_cpp/core/class_db.hpp
@@ -165,7 +165,10 @@ class ClassDB {
 	static void add_property(const StringName &p_class, const PropertyInfo &p_pinfo, const StringName &p_setter, const StringName &p_getter, int p_index = -1);
 	static void add_signal(const StringName &p_class, const MethodInfo &p_signal);
 	static void bind_integer_constant(const StringName &p_class_name, const StringName &p_enum_name, const StringName &p_constant_name, GDExtensionInt p_constant_value, bool p_is_bitfield = false);
+	// Binds an implementation of a virtual method defined in Godot.
 	static void bind_virtual_method(const StringName &p_class, const StringName &p_method, GDExtensionClassCallVirtual p_call);
+	// Add a new virtual method that can be implemented by scripts.
+	static void add_virtual_method(const StringName &p_class, const MethodInfo &p_method, const Vector<StringName> &p_arg_names = Vector<StringName>());
 
 	static MethodBind *get_method(const StringName &p_class, const StringName &p_method);
 
diff --git a/include/godot_cpp/core/object.hpp b/include/godot_cpp/core/object.hpp
index 79f8fbfeb..c1f106922 100644
--- a/include/godot_cpp/core/object.hpp
+++ b/include/godot_cpp/core/object.hpp
@@ -68,6 +68,8 @@ struct MethodInfo {
 	int id = 0;
 	std::vector<PropertyInfo> arguments;
 	std::vector<Variant> default_arguments;
+	GDExtensionClassMethodArgumentMetadata return_val_metadata;
+	std::vector<GDExtensionClassMethodArgumentMetadata> arguments_metadata;
 
 	inline bool operator==(const MethodInfo &p_method) const { return id == p_method.id; }
 	inline bool operator<(const MethodInfo &p_method) const { return id == p_method.id ? (name < p_method.name) : (id < p_method.id); }
diff --git a/include/godot_cpp/core/property_info.hpp b/include/godot_cpp/core/property_info.hpp
index 0ecfa322c..f610f3fd1 100644
--- a/include/godot_cpp/core/property_info.hpp
+++ b/include/godot_cpp/core/property_info.hpp
@@ -80,6 +80,17 @@ struct PropertyInfo {
 		p_info->usage = usage;
 		*(reinterpret_cast<StringName *>(p_info->class_name)) = class_name;
 	}
+
+	GDExtensionPropertyInfo _to_gdextension() const {
+		return {
+			(GDExtensionVariantType)type,
+			name._native_ptr(),
+			class_name._native_ptr(),
+			hint,
+			hint_string._native_ptr(),
+			usage,
+		};
+	}
 };
 
 } // namespace godot
diff --git a/include/godot_cpp/godot.hpp b/include/godot_cpp/godot.hpp
index c9e902260..1b420d38e 100644
--- a/include/godot_cpp/godot.hpp
+++ b/include/godot_cpp/godot.hpp
@@ -165,6 +165,8 @@ extern "C" GDExtensionInterfaceObjectGetClassName gdextension_interface_object_g
 extern "C" GDExtensionInterfaceObjectCastTo gdextension_interface_object_cast_to;
 extern "C" GDExtensionInterfaceObjectGetInstanceFromId gdextension_interface_object_get_instance_from_id;
 extern "C" GDExtensionInterfaceObjectGetInstanceId gdextension_interface_object_get_instance_id;
+extern "C" GDExtensionInterfaceObjectHasScriptMethod gdextension_interface_object_has_script_method;
+extern "C" GDExtensionInterfaceObjectCallScriptMethod gdextension_interface_object_call_script_method;
 extern "C" GDExtensionInterfaceCallableCustomCreate gdextension_interface_callable_custom_create;
 extern "C" GDExtensionInterfaceCallableCustomGetUserData gdextension_interface_callable_custom_get_userdata;
 extern "C" GDExtensionInterfaceRefGetObject gdextension_interface_ref_get_object;
@@ -177,6 +179,7 @@ extern "C" GDExtensionInterfaceClassdbGetMethodBind gdextension_interface_classd
 extern "C" GDExtensionInterfaceClassdbGetClassTag gdextension_interface_classdb_get_class_tag;
 extern "C" GDExtensionInterfaceClassdbRegisterExtensionClass2 gdextension_interface_classdb_register_extension_class2;
 extern "C" GDExtensionInterfaceClassdbRegisterExtensionClassMethod gdextension_interface_classdb_register_extension_class_method;
+extern "C" GDExtensionInterfaceClassdbRegisterExtensionClassVirtualMethod gdextension_interface_classdb_register_extension_class_virtual_method;
 extern "C" GDExtensionInterfaceClassdbRegisterExtensionClassIntegerConstant gdextension_interface_classdb_register_extension_class_integer_constant;
 extern "C" GDExtensionInterfaceClassdbRegisterExtensionClassProperty gdextension_interface_classdb_register_extension_class_property;
 extern "C" GDExtensionInterfaceClassdbRegisterExtensionClassPropertyIndexed gdextension_interface_classdb_register_extension_class_property_indexed;
diff --git a/src/core/class_db.cpp b/src/core/class_db.cpp
index 1f4b135d2..acead8bfa 100644
--- a/src/core/class_db.cpp
+++ b/src/core/class_db.cpp
@@ -32,6 +32,7 @@
 
 #include <godot_cpp/core/error_macros.hpp>
 #include <godot_cpp/godot.hpp>
+#include <godot_cpp/templates/vector.hpp>
 
 #include <godot_cpp/core/memory.hpp>
 
@@ -337,6 +338,46 @@ void ClassDB::bind_virtual_method(const StringName &p_class, const StringName &p
 	type.virtual_methods[p_method] = p_call;
 }
 
+void ClassDB::add_virtual_method(const StringName &p_class, const MethodInfo &p_method, const Vector<StringName> &p_arg_names) {
+	std::unordered_map<StringName, ClassInfo>::iterator type_it = classes.find(p_class);
+	ERR_FAIL_COND_MSG(type_it == classes.end(), String("Class '{0}' doesn't exist.").format(Array::make(p_class)));
+
+	GDExtensionClassVirtualMethodInfo mi;
+	mi.name = (GDExtensionStringNamePtr)&p_method.name;
+	mi.method_flags = p_method.flags;
+	mi.return_value = p_method.return_val._to_gdextension();
+	mi.return_value_metadata = p_method.return_val_metadata;
+	mi.argument_count = p_method.arguments.size();
+	if (mi.argument_count > 0) {
+		mi.arguments = (GDExtensionPropertyInfo *)memalloc(sizeof(GDExtensionPropertyInfo) * mi.argument_count);
+		mi.arguments_metadata = (GDExtensionClassMethodArgumentMetadata *)memalloc(sizeof(GDExtensionClassMethodArgumentMetadata) * mi.argument_count);
+		for (int i = 0; i < mi.argument_count; i++) {
+			mi.arguments[i] = p_method.arguments[i]._to_gdextension();
+			mi.arguments_metadata[i] = p_method.arguments_metadata[i];
+		}
+	} else {
+		mi.arguments = nullptr;
+		mi.arguments_metadata = nullptr;
+	}
+
+	if (p_arg_names.size() != mi.argument_count) {
+		WARN_PRINT("Mismatch argument name count for virtual method: " + String(p_class) + "::" + p_method.name);
+	} else {
+		for (int i = 0; i < p_arg_names.size(); i++) {
+			mi.arguments[i].name = (GDExtensionStringNamePtr)&p_arg_names[i];
+		}
+	}
+
+	internal::gdextension_interface_classdb_register_extension_class_virtual_method(internal::library, &p_class, &mi);
+
+	if (mi.arguments) {
+		memfree(mi.arguments);
+	}
+	if (mi.arguments_metadata) {
+		memfree(mi.arguments_metadata);
+	}
+}
+
 void ClassDB::initialize_class(const ClassInfo &p_cl) {
 }
 
diff --git a/src/godot.cpp b/src/godot.cpp
index 5c2aaa65d..a80ad90da 100644
--- a/src/godot.cpp
+++ b/src/godot.cpp
@@ -171,6 +171,8 @@ GDExtensionInterfaceObjectGetClassName gdextension_interface_object_get_class_na
 GDExtensionInterfaceObjectCastTo gdextension_interface_object_cast_to = nullptr;
 GDExtensionInterfaceObjectGetInstanceFromId gdextension_interface_object_get_instance_from_id = nullptr;
 GDExtensionInterfaceObjectGetInstanceId gdextension_interface_object_get_instance_id = nullptr;
+GDExtensionInterfaceObjectHasScriptMethod gdextension_interface_object_has_script_method = nullptr;
+GDExtensionInterfaceObjectCallScriptMethod gdextension_interface_object_call_script_method = nullptr;
 GDExtensionInterfaceCallableCustomCreate gdextension_interface_callable_custom_create = nullptr;
 GDExtensionInterfaceCallableCustomGetUserData gdextension_interface_callable_custom_get_userdata = nullptr;
 GDExtensionInterfaceRefGetObject gdextension_interface_ref_get_object = nullptr;
@@ -183,6 +185,7 @@ GDExtensionInterfaceClassdbGetMethodBind gdextension_interface_classdb_get_metho
 GDExtensionInterfaceClassdbGetClassTag gdextension_interface_classdb_get_class_tag = nullptr;
 GDExtensionInterfaceClassdbRegisterExtensionClass2 gdextension_interface_classdb_register_extension_class2 = nullptr;
 GDExtensionInterfaceClassdbRegisterExtensionClassMethod gdextension_interface_classdb_register_extension_class_method = nullptr;
+GDExtensionInterfaceClassdbRegisterExtensionClassVirtualMethod gdextension_interface_classdb_register_extension_class_virtual_method = nullptr;
 GDExtensionInterfaceClassdbRegisterExtensionClassIntegerConstant gdextension_interface_classdb_register_extension_class_integer_constant = nullptr;
 GDExtensionInterfaceClassdbRegisterExtensionClassProperty gdextension_interface_classdb_register_extension_class_property = nullptr;
 GDExtensionInterfaceClassdbRegisterExtensionClassPropertyIndexed gdextension_interface_classdb_register_extension_class_property_indexed = nullptr;
@@ -408,6 +411,8 @@ GDExtensionBool GDExtensionBinding::init(GDExtensionInterfaceGetProcAddress p_ge
 	LOAD_PROC_ADDRESS(object_cast_to, GDExtensionInterfaceObjectCastTo);
 	LOAD_PROC_ADDRESS(object_get_instance_from_id, GDExtensionInterfaceObjectGetInstanceFromId);
 	LOAD_PROC_ADDRESS(object_get_instance_id, GDExtensionInterfaceObjectGetInstanceId);
+	LOAD_PROC_ADDRESS(object_has_script_method, GDExtensionInterfaceObjectHasScriptMethod);
+	LOAD_PROC_ADDRESS(object_call_script_method, GDExtensionInterfaceObjectCallScriptMethod);
 	LOAD_PROC_ADDRESS(callable_custom_create, GDExtensionInterfaceCallableCustomCreate);
 	LOAD_PROC_ADDRESS(callable_custom_get_userdata, GDExtensionInterfaceCallableCustomGetUserData);
 	LOAD_PROC_ADDRESS(ref_get_object, GDExtensionInterfaceRefGetObject);
@@ -420,6 +425,7 @@ GDExtensionBool GDExtensionBinding::init(GDExtensionInterfaceGetProcAddress p_ge
 	LOAD_PROC_ADDRESS(classdb_get_class_tag, GDExtensionInterfaceClassdbGetClassTag);
 	LOAD_PROC_ADDRESS(classdb_register_extension_class2, GDExtensionInterfaceClassdbRegisterExtensionClass2);
 	LOAD_PROC_ADDRESS(classdb_register_extension_class_method, GDExtensionInterfaceClassdbRegisterExtensionClassMethod);
+	LOAD_PROC_ADDRESS(classdb_register_extension_class_virtual_method, GDExtensionInterfaceClassdbRegisterExtensionClassVirtualMethod);
 	LOAD_PROC_ADDRESS(classdb_register_extension_class_integer_constant, GDExtensionInterfaceClassdbRegisterExtensionClassIntegerConstant);
 	LOAD_PROC_ADDRESS(classdb_register_extension_class_property, GDExtensionInterfaceClassdbRegisterExtensionClassProperty);
 	LOAD_PROC_ADDRESS(classdb_register_extension_class_property_indexed, GDExtensionInterfaceClassdbRegisterExtensionClassPropertyIndexed);