godotengine / godotengine/godot-cpp
_init() is required with exact name in C++, but not documented or enforced by API
- Dominant language
- C++
- Stars
- 2.7k
- Forks
- 809
- Avg merge
- 1d 3h
- Merged PRs (30d)
- 8
Description
_Versions: https://github.com/godotengine/godot/commit/245c99175c242bdc60a212cc84986b1a9ad5aa08, godot-cpp 123d9f0e9264dcc7206888fc96419b32feef00c8_
Lately I tried to bind a simple C++ class to Godot, very similar to the one [in the official example](https://docs.godotengine.org/en/3.1/tutorials/plugins/gdnative/gdnative-cpp-example.html):
```cpp
class MyClass : public godot::Node2D
{
GODOT_CLASS(MyClass, godot::Node2D)
public:
static void _register_methods()
{
godot::register_method("_init", &MyClass::Init);
godot::register_method("_process", &MyClass::Process);
}
void Init()
{
printf("_init()\n");
}
void Process(float delta)
{
printf("_process(%f)\n", delta);
}
};
```
and register as follows:
```cpp
extern "C" void GDN_EXPORT godot_nativescript_init(void* handle)
{
godot::Godot::nativescript_init(handle);
godot::register_class();
}
```
Note that **unlike the official example**, I'm using a different naming convention, and my method is called `Init` instead of `_init`. Since I'm explicitly binding a member function reference via `&MyClass::Init`, and C++ generally has no concept of method names at runtime, a different naming convention should not be a problem.
Except the little problem that blows up the application when launching from Godot (expand):
```
CrashHandlerException: Program crashed
Dumping the backtrace. Please include this when reporting the bug on https://github.com/godotengine/godot/issues
[0] godot_method_bind_ptrcall (c:\godot\modules\gdnative\gdnative\gdnative.cpp:69)
[1] godot_method_bind_ptrcall (c:\godot\modules\gdnative\gdnative\gdnative.cpp:69)
[2] godot::___godot_icall_void (c:\godot-cpp\include\gen\__icalls.hpp:2503)
[3] godot::Object::_init (c:\godot-cpp\src\gen\object.cpp:84)
[4] godot::_godot_class_instance_func (c:\c++libs\include\godot\core\godot.hpp:104)
[5] NativeScript::instance_create (c:\godot\modules\gdnative\nativescript\nativescript.cpp:217)
[6] Object::set_script (c:\godot\core\object.cpp:998)
[7] Object::set (c:\godot\core\object.cpp:432)
[8] SceneState::instance (c:\godot\scene\resources\packed_scene.cpp:212)
[9] PackedScene::instance (c:\godot\scene\resources\packed_scene.cpp:1692)
[10] Main::start (c:\godot\main\main.cpp:1808)
[11] widechar_main (c:\godot\platform\windows\godot_windows.cpp:160)
[12] _main (c:\godot\platform\windows\godot_windows.cpp:184)
[13] main (c:\godot\platform\windows\godot_windows.cpp:196)
[14] __scrt_common_main_seh (f:\dd\vctools\crt\vcstartup\src\startup\exe_common.inl:253)
[15] BaseThreadInitThunk
-- END OF BACKTRACE --
```
I debugged for at least half an hour, always searching the error in the binding, I even inspected the exported DLL functions to make sure their signature is correct. Only when moving step-by-step from the official example, I could reduce the code, so that no other difference than the method names remained. Thinking "this sure can't be it", the naming _was_ it to my surprise. Later I found out, that even an empty `_register_methods()` would trigger the same crash.
With the attached VC++ debugger, I tracked down the issue to find this GDNative function template:
```cpp
template
void *_godot_class_instance_func(godot_object *p, void *method_data) {
T *d = new T();
d->_owner = p;
d->_type_tag = typeid(T).hash_code();
d->_init();
return d;
}
```
Without me defining my own `_init()` function, it would call the base class `Object::_init()`:
```cpp
void Object::_init() {
___godot_icall_void(___mb.mb__init, (const Object *) this);
}
```
And this crashes, because the function pointer `___mb.mb__init` is a null pointer.
---
So far so well, fixing this is easy by naming the function appropriately. However, some thoughts related to the current implementation:
1. I first thought I forgot to override a function in a base class, **however `Object::_init()` is not virtual**. Due to the static polymorphism at `d->_init()` (see above), GDNative _still_ calls the most derived function, but silently falls back to the base function. This design is rather unusual, if not very confusing. A pure virtual function would have had the same effect, but could enforce overriding through C++'s type system. I understand that dynamic polymorphism may have been avoided for performance reasons though.
1. In my opinion, public and internal APIs are ideally separated in a way that it's not that easy for a user to accidentally mess with the internals. There are various ways to achieve this; apart from the obvious `private/public/friend` access specifiers, it's also possible to add static checks to the resolving function template, to cause a compile error if no user-defined method is provided.
1. It is unclear to me, where the Godot-site `_init()` (the equivalent in GDScript) comes into play. Why do I even need to register the function pointer, if GDNative already uses a hardcoded call to that method? Or is it just coincidence that `Object::_init()` and my `_init()` have the same name?
1. More generally, is there a reason why the de-facto default implementation of `_init()` -- i.e. `Object::_init()` -- is broken or is this just a bug?
In case this behavior is by design, I would recommend to make it a bit harder to use wrong, and add a big warning to the documentation -- even for C++ programmers this is quite a pitfall 😉
Nevertheless, I've been amazed how easy it is to get simple things up and running with both Godot and GDScript, keep up the great work!
Contributor guide
No contributing guide indexed for this repository
Assessment
This issue has not been assessed yet.