C++ - How to inherit _bind_methods() from parent classes contained in external libraries?

Godot Version

4.1.4

Issue

I made an GDExtension-library containing a base class (CharacterComponent). When trying to implement another extension (DefaultGravity) that builds on the first one I encountered two issues.

  • I could not include the header file from the base class, so I had to copy header and implementation into my DefaultGravity directory
  • My Child class of the second extension does not show the added property from the parents _bind_methods().

Questions

How do you link remote custom headers and how to properly inherit added properties of a parent class?

Code - Short Version

void godot::DefaultGravity::_bind_methods(){
    ClassDB::bind_method(D_METHOD("get_gravity_factor"), &DefaultGravity::get_GravityFactor);
	ClassDB::bind_method(D_METHOD("set_gravity_factor", "gravity_factor"), &DefaultGravity::set_GravityFactor);
	ClassDB::add_property("DefaultGravity",PropertyInfo(Variant::FLOAT, "Gravity Factor"), "set_gravity_factor", "get_gravity_factor");
}

I tried these three approaches to get my child node to adopt its parent nodes added property. The extension compiled but in the editor only the factor property is shown.

CharacterComponent::_bind_methods();
ClassDB::bind_method(D_METHOD("get_target"), &DefaultGravity::get_Target);
ClassDB::bind_method(D_METHOD("set_target", "target"), &DefaultGravity::set_Target);
ClassDB::add_property("DefaultGravity",PropertyInfo(Variant::OBJECT, "Target Character", PROPERTY_HINT_NODE_TYPE, "CharacterBody3D", PROPERTY_USAGE_DEFAULT), "set_target", "get_target");
}
ClassDB::bind_method(D_METHOD("get_target"), &CharacterComponent::get_Target);
ClassDB::bind_method(D_METHOD("set_target", "target"), &CharacterComponent::set_Target);
ClassDB::add_property("CharacterComponent",PropertyInfo(Variant::OBJECT, "Target Character", PROPERTY_HINT_NODE_TYPE, "CharacterBody3D", PROPERTY_USAGE_DEFAULT), "set_target", "get_target");
}

Complete Code

CharacterComponent.h

#ifndef CHARACTER_COMPONENT
#define CHARACTER_COMPONENT

#include <godot_cpp/classes/node.hpp>
#include <godot_cpp/classes/character_body3d.hpp>

namespace godot{
    class CharacterComponent : public Node{
        GDCLASS(CharacterComponent, Node)

    private:
        CharacterBody3D* target;

    protected:
        static void _bind_methods();

    public:
        CharacterComponent();
        ~CharacterComponent();

        CharacterBody3D* get_Target() const;
        void set_Target(CharacterBody3D* target_cb);
    };
}
#endif

CharacterComponent.cpp

#include "CharacterComponent.h"
#include <godot_cpp/classes/character_body3d.hpp>

using namespace godot;

void CharacterComponent::_bind_methods(){
    ClassDB::bind_method(D_METHOD("get_target"), &CharacterComponent::get_Target);
	ClassDB::bind_method(D_METHOD("set_target", "target"), &CharacterComponent::set_Target);
	ClassDB::add_property("CharacterComponent",PropertyInfo(Variant::OBJECT, "Target Character", PROPERTY_HINT_NODE_TYPE, "CharacterBody3D", PROPERTY_USAGE_DEFAULT), "set_target", "get_target");
}

CharacterComponent::CharacterComponent(){
}

CharacterComponent::~CharacterComponent(){
}

CharacterBody3D *godot::CharacterComponent::get_Target() const{
    return target;
}

void CharacterComponent::set_Target(CharacterBody3D *target_cb){
    this->target = target_cb;
}

DefaultGravity.h

#ifndef DEFAULT_GRAVITY
#define DEFAULT_GRAVITY

#include <CharacterComponent.h>

namespace godot{
    class DefaultGravity : public CharacterComponent{
        GDCLASS(DefaultGravity, CharacterComponent)

        private:
            float custom_gravity_factor;

        protected:
            static void _bind_methods();

        public:
            DefaultGravity();
            ~DefaultGravity();

            float get_GravityFactor() const;
            void set_GravityFactor(float gravity_f);   
    };
}
#endif

DefaultGravity.cpp

#include "DefaultGravity.h"

using namespace godot;

void DefaultGravity::_bind_methods(){
    //CharacterComponent::_bind_methods();
    ClassDB::bind_method(D_METHOD("get_gravity_factor"), &DefaultGravity::get_GravityFactor);
	ClassDB::bind_method(D_METHOD("set_gravity_factor", "gravity_factor"), &DefaultGravity::set_GravityFactor);
	ClassDB::add_property("DefaultGravity",PropertyInfo(Variant::FLOAT, "Gravity Factor"), "set_gravity_factor", "get_gravity_factor");

    ClassDB::bind_method(D_METHOD("get_target"), &DefaultGravity::get_Target);
	ClassDB::bind_method(D_METHOD("set_target", "target"), &DefaultGravity::set_Target);
	ClassDB::add_property("DefaultGravity",PropertyInfo(Variant::OBJECT, "Target Character", PROPERTY_HINT_NODE_TYPE, "CharacterBody3D", PROPERTY_USAGE_DEFAULT), "set_target", "get_target");
}

DefaultGravity::DefaultGravity(){
    CharacterComponent();
}

DefaultGravity::~DefaultGravity(){
}

float DefaultGravity::get_GravityFactor() const{
    return custom_gravity_factor;
}

void DefaultGravity::set_GravityFactor(float gravity_f){
    this->custom_gravity_factor = gravity_f;
}

You want to build two different extentions, but use class CharacterComponent from one extention inside another extention DefaultGravity?
In this case you need link DefaultGravity with CharacterComponent.lib and include “CharacterComponent.h”

If they both in one extention just include #include "CharacterComponent.h". Look, there are quotes, not <...>

1 Like

Yes, my current goal with GDExtensions is to get familiar with how they work in practice. It was possible to inherite those bindings when putting both classes in one extension. While this works for some use cases I would like to know how to split up extensions in order to gain more flexibility when designing extensions. Currently I don’t know how to properly include and link created extensions in other extensions of mine.

I tried to link the .CharacterComponent.lib and include “CharacterComponent.h”. Thank you for this hint. Unfortunately I couldn’t succeed yet. This is where I am currently at. For some reasons I had to link the binding .lib again as I got a seemingly endless list of unresolved external symbols, which I assume where the bindings classes.

#!/usr/bin/env python
import os
import sys

env = SConscript("../../godot-cpp/SConstruct")

library_title = "HelloWorld"
bindings_dir = "../../"

lib_path_relative = [
    f'{bindings_dir}demoproject/bin/CharacterComponents',
    f'{bindings_dir}/godot-cpp/bin'
]

lib_include_path_relative = f'{bindings_dir}library-projects/CharacterComponents/src'


env.Append(CPPPATH=[lib_include_path_relative])
env.Append(CPPPATH=["src/"])
sources = Glob("src/*.cpp")

sources.append(File(f"{bindings_dir}demoproject/bin/CharacterComponents/CharacterComponents.windows.template_debug.x86_64.lib"))

if env["platform"] == "macos":
    library = env.SharedLibrary(
        "../../demoproject/bin/HelloWorld.{}.{}.framework/{}/{}.{}.{}".format(library_title, library_title,
            env["platform"], env["target"], env["platform"], env["target"]
        ),
        source=sources,
    )
else:
    library = env.SharedLibrary(
        "../../demoproject/bin/{}/{}{}{}".format(library_title, library_title, env["suffix"], env["SHLIBSUFFIX"]),
        source=sources, 
        LIBS=[
            'CharacterComponents.windows.template_debug.x86_64',
            'libgodot-cpp.windows.template_debug.x86_64.lib'
            ], 
        LIBPATH=lib_path_relative
    )

Default(library)

This currently creates this output:

scons: Reading SConscript files ...
Auto-detected 16 CPU cores available for build parallelism. Using 15 cores by default. You can override it with the -j argument.
Building for architecture x86_64 on platform windows
scons: done reading SConscript files.
scons: Building targets ...
scons: `C:\godot-cpp\bin\libgodot-cpp.windows.template_debug.x86_64.lib' is up to date.
Linking Shared Library C:\demoproject\bin\HelloWorld\HelloWorld.windows.template_debug.x86_64.dll ...
StringContainer.windows.template_debug.x86_64.obj : error LNK2019: Unresolved external symbol "public: __cdecl godot::CharacterComponent::CharacterComponent(void)" (??0CharacterComponent@godot@@QEAA@XZ) referenced in function "public: __cdecl StringContainer::StringContainer(void)" (??0StringContainer@@QEAA@XZ).
StringContainer.windows.template_debug.x86_64.obj : error LNK2019: Unresolved external symbol "public: virtual __cdecl godot::CharacterComponent::~CharacterComponent(void)" (??1CharacterComponent@godot@@UEAA@XZ) referenced in function "int `public: __cdecl StringContainer::StringContainer(void)'::`1'::dtor$0" (?dtor$0@?0???0StringContainer@@QEAA@XZ@4HA).
C:\demoproject\bin\HelloWorld\HelloWorld.windows.template_debug.x86_64.dll : fatal error LNK1120: 2 unresolved externals
scons: *** [C:\demoproject\bin\HelloWorld\HelloWorld.windows.template_debug.x86_64.dll] Error 1120
scons: building terminated because of errors.

I think I was wrong and there is no easy answer.
C++ must know class realization to determine offsets, sizes etc.
If you think “how godot-cpp work”, it fakes classes over C functions, mean it uses C ABI anyway.
There is exists exported classes in dll with full and only public methods but it is only microsoft way.

1 Like

Doesn’t C++ have all required information when provided with the .lib and header file of the CharacterComponent class?

I think - not, because class can be

template <typename T>
class MyClass ...

And there are no real code here
If you use MyCalss<int> it generate one class with own vtable, for MyClass it will another, but you cant put MyClass in .lib file
Any way cpp files must be also included in both projects…