Skip to content
Search

GDNative

lastmod 2023-04-02created 2020-07-2830 edits

Finally, the most shining addition to the 3.0 release: GDNative lets you script in C++ without recompiling Godot (or even restarting it).

Any C++ version can be used, and mixing compiler brands and versions for the generated shared libraries works perfectly, thanks to the internal C API Bridge.

This language is the best choice for performance and does not need to be used throughout an entire game, as other parts can be written in GDScript or Visual Script. However, the API is clear and easy to use as it resembles, mostly, Godot's actual C++ API.

More languages can be used through the GDNative interface, but there is no official support for them.

Let's build one

Introduction

This guide introduces the bare minimum needed to create a GDNative module. It should be your starting point into the world of GDNative. Understanding the contents of this tutorial will help you understand everything that comes after.

Before we begin, you can download the source code of the example object described below from the GDNative-demos repository.

This example project also contains a SConstruct file that makes compiling a little easier, but in this tutorial we will do the work by hand to understand the process.

GDNative can be used to create several types of additions to Godot, using interfaces such as PluginScript or ARVRInterfaceGDNative. In this tutorial we will look at how to create a NativeScript module. NativeScript lets you write logic in C or C++ in a way similar to writing a GDScript file. We will create the C equivalent of this GDScript:

extends Reference

var data

func _ready():
    data = "World from GDScript!"

func get_data():
    return data

Future guides will focus on other types of GDNative modules and explain when and how to use each of them.

Prerequisites

You need a few things before you start.

  • A Godot executable for your target version.
  • A C compiler. On Linux, install gcc or clang from your package manager. On macOS, you can install Xcode from the Mac App Store. On Windows, you can use Visual Studio 2015 or later, or MinGW-w64.
  • A Git clone of the godot_headers repository: these are the C headers for Godot's public API exposed to GDNative.

We recommend creating a dedicated folder for the GDNative example project, opening a terminal in that folder, and running:

git clone -b godot-3.2.3-stable --depth 1 https://github.com/godotengine/godot_headers

WARNING

You must checkout the tag that matches your version (godot-3.2.3-stable).

Our C source

Create directories with the following structure.

+ <your development folder>
  + godot_headers
    - <lots of files here>
  + simple
    + bin
      - libsimple.dll/so/dylib
      - libsimple.gdnlib
      - simple.gdns
    main.tscn
    project.godot
  + src
    - simple.c

Open Godot and create a new project called simple alongside the godot_headers Git clone. This creates the simple folder and the project.godot file. Then manually create a src folder next to the simple folder, and a bin subfolder in the simple folder.

Let's start by looking at what the simple.c file contains. For the example here, we make a single C source file without a header to keep things simple. Once you start writing bigger projects, it is a good idea to split the project into multiple files. However, that is outside the scope of this tutorial.

We will look at the source code bit by bit, so all the parts below must be put together into one big file. Each section is explained as it is added.

#include <gdnative_api_struct.gen.h>

#include <string.h>

const godot_gdnative_core_api_struct *api = NULL;
const godot_gdnative_ext_nativescript_api_struct *nativescript_api = NULL;

The code above includes the GDNative API struct header and a standard header that we will use for string operations. It then defines two pointers to two different structs. GDNative supports a large collection of functions for calling back into the main Godot executable. So that your module can access these functions, GDNative provides your application with a struct containing pointers to all of them.

To keep this implementation modular and easily extendable, the core functions are available directly through the "core" API struct, while additional functions have their own "GDNative structs" that are accessible through extensions.

In this example, we access one of these extensions to get at the functions needed specifically for NativeScript.

A NativeScript behaves like any other script in Godot. Because the NativeScript API is rather low level, the library has to specify many things in more detail than other scripting systems such as GDScript. When a NativeScript instance is created, the constructor provided by the library is called. When that instance is destroyed, the given destructor is run.

void *simple_constructor(godot_object *p_instance, void *p_method_data);
void simple_destructor(godot_object *p_instance, void *p_method_data, void *p_user_data);
godot_variant simple_get_data(godot_object *p_instance, void *p_method_data,
        void *p_user_data, int p_num_args, godot_variant **p_args);

These are forward declarations for the functions we will implement for our object. A constructor and a destructor are needed. Also, the object has get_data.

Next is the first entry point that Godot will call when the dynamic library is loaded. These methods are all named with the prefix godot_ (which you can change later) followed by their name. gdnative_init is the function that initializes the dynamic library. Godot gives it a pointer to a structure containing various information we may find useful, including pointers to the API structures.

For any additional API structures, you need to loop through the extensions array and check the type of each extension.

void GDN_EXPORT godot_gdnative_init(godot_gdnative_init_options *p_options) {
    api = p_options->api_struct;

    // Now find our extensions.
    for (int i = 0; i < api->num_extensions; i++) {
        switch (api->extensions[i]->type) {
            case GDNATIVE_EXT_NATIVESCRIPT: {
                nativescript_api = (godot_gdnative_ext_nativescript_api_struct *)api->extensions[i];
            }; break;
            default: break;
        }
    }
}

Next is gdnative_terminate, which is called before the library is unloaded. Godot unloads the library when no object uses it anymore. Here you can do any cleanup you need. In this example, we simply clear the API pointers.

void GDN_EXPORT godot_gdnative_terminate(godot_gdnative_terminate_options *p_options) {
    api = NULL;
    nativescript_api = NULL;
}

Finally, there is nativescript_init, the most important function we need today. This function is called by Godot as part of loading the GDNative library and tells the engine which objects are available.

void GDN_EXPORT godot_nativescript_init(void *p_handle) {
    godot_instance_create_func create = { NULL, NULL, NULL };
    create.create_func = &simple_constructor;

    godot_instance_destroy_func destroy = { NULL, NULL, NULL };
    destroy.destroy_func = &simple_destructor;

    nativescript_api->godot_nativescript_register_class(p_handle, "Simple", "Reference", create, destroy);

    godot_instance_method get_data = { NULL, NULL, NULL };
    get_data.method = &simple_get_data;

    godot_method_attributes attributes = { GODOT_METHOD_RPC_MODE_DISABLED };

    nativescript_api->godot_nativescript_register_method(p_handle, "Simple", "get_data",
            attributes, get_data);
}

First, we tell the engine which classes are implemented by calling nativescript_register_class.

  • The first parameter here is the handle pointer given to us.
  • The second is the name of our object class.
  • The third is the type of object in Godot that we 'inherit' from. This is not true inheritance, but it's close enough.
  • Finally, the fourth and fifth parameters are descriptions of the constructor and destructor.

Then we tell Godot about our methods (one method in this case) by calling nativescript_register_method for each method of the class. In our case, that is get_data.

  • The first parameter is again the handle pointer.
  • The second is again the name of the object class we are registering.
  • The third is the name of the function as it will be known to GDScript.
  • The fourth is the attributes setting (see the godot_method_rpc_mode enum in godot_headers/nativescript/godot_nativescript.h for possible values).
  • The fifth and final parameter is a description of the function to call when the method is called.

The description struct instance_method

  • contains the function pointer to the function itself as its first field. The other two fields of this struct are for specifying per-method user data.
  • The second is the method_data field, which is passed as the p_method_data argument on every function call. This is useful for reusing one function for different methods across several different script classes. If the method_data value is a pointer to memory that needs to be freed, the third free_func field can contain a pointer to a function that frees that memory. This free function is called when the script itself (not the instance) is unloaded (usually at library unload time).

Now it's time to start working on the functions of our object. First, we define the structure we use to store the member data of a GDNative class instance.

typedef struct user_data_struct {
    char data[256];
} user_data_struct;

Then we define the constructor. All we do in the constructor is allocate memory for the structure and fill it with data. We use Godot's memory functions so the memory is tracked, then return a pointer to the new structure. This pointer serves as the instance identifier in case multiple objects are instantiated.

This pointer is passed to the functions related to our object as a parameter called p_user_data, and can be used to identify the instance and access its member data.

void *simple_constructor(godot_object *p_instance, void *p_method_data) {
    user_data_struct *user_data = api->godot_alloc(sizeof(user_data_struct));
    strcpy(user_data->data, "World from GDNative!");

    return user_data;
}

Our destructor is called when Godot is done with our object, and we free the instance's member data.

void simple_destructor(godot_object *p_instance, void *p_method_data, void *p_user_data) {
    api->godot_free(p_user_data);
}

Finally, we implement the get_data function. Data is always sent and returned as variants, so to return our data (a string), we first need to convert the C string to a Godot string object, then copy that string object into the variant we return.

godot_variant simple_get_data(godot_object *p_instance, void *p_method_data,
        void *p_user_data, int p_num_args, godot_variant **p_args) {
    godot_string data;
    godot_variant ret;
    user_data_struct *user_data = (user_data_struct *)p_user_data;

    api->godot_string_new(&data);
    api->godot_string_parse_utf8(&data, user_data->data);
    api->godot_variant_new_string(&ret, &data);
    api->godot_string_destroy(&data);

    return ret;
}

Strings are heap-allocated in Godot, so they have a destructor that frees the memory. Destructors are named godot_TYPENAME_destroy. When a Variant is created with a String, it references the String. That means the original String can be "destroyed" to decrease the reference count. If that does not happen, the ref-count never reaches zero and the memory is never deallocated, so the String memory leaks. The returned variant is destroyed automatically by Godot.

INFORMATION

In more complex operations, it can be confusing to keep track of which values need to be deallocated and which do not. As a general rule, call godot_TYPENAME_destroy where a C++ destructor would be called instead. The String destructor would be called in C++ after the Variant is created, so the same is needed in C.

Compiling

Now we need to compile the source code. As mentioned, the example project on GitHub contains a SCons configuration that does all the work, but in this tutorial we will call the compiler directly.

Assuming you stick to the folder structure suggested above, it is best to open a terminal session in the src folder and run the commands from there. Make sure to create the bin folder before continuing.

For Linux:

gcc -std=c11 -fPIC -c -I../godot_headers simple.c -o simple.o
gcc -rdynamic -shared simple.o -o ../simple/bin/libsimple.so

macOS:

clang -std=c11 -fPIC -c -I../godot_headers simple.c -o simple.os
clang -dynamiclib simple.os -o ../simple/bin/libsimple.dylib

For Windows:

cl /Fosimple.obj /c simple.c /nologo -EHsc -DNDEBUG /MD /I. /I..\godot_headers
link /nologo /dll /out:..\simple\bin\libsimple.dll /implib:..\simple\bin\libsimple.lib simple.obj

Creating the GDNativeLibrary (.gdnlib) file

Now that the module is compiled, we need to create a corresponding GDNativeLibrary resource with the .gdnlib extension, to be placed alongside the dynamic libraries. This file tells Godot which dynamic libraries are part of our module and need to be loaded per platform.

We can use Godot to generate this file, so open the "simple" project in the editor.

Click the create resource button in the Inspector and select GDNativeLibrary. A contextual editor should appear in the bottom panel.

General properties

The Inspector has various properties that ​​control library loading.

Load Once
Our library is loaded only once, and each individual script that uses our library uses the same data. Any variable you define globally is accessible from every instance of the objects you create. If Load Once is disabled, a new copy of the library is loaded into memory each time a script accesses the library.
Singleton
Our library is loaded automatically, and a function called godot_gdnative_singleton is called. We'll leave that for another tutorial.
Symbol Prefix
The prefix for core functions, such as godot_ in godot_nativescript_init seen earlier. If you use multiple GDNative libraries that you want to link statically, you need to use different prefixes. This again is a topic to look at in more detail in a separate tutorial. It is currently only needed when deploying to iOS, since that platform does not like dynamic libraries.
Reloadable
Defines whether the library should be reloaded when the editor loses and regains focus, typically to pick up new or modified symbols from changes made to the library externally.

Platform libraries

The GDNativeLibrary editor plugin lets you configure two things for each platform and architecture you want to support.

The dynamic library column (the entry section of the saved file) tells us, for each platform and feature combination, which dynamic library to load. It also tells the exporter which files need to be exported when exporting to a specific platform.

The dependencies column (also the dependencies section) tells Godot which other files need to be exported for each platform for our library to work. Suppose your GDNative module uses another DLL to implement functionality from a third-party library; this is where you list that DLL.

In this example, we built libraries only for Linux, macOS and/or Windows, so you can link them in the relevant fields by clicking the folder button. If you built all three libraries, you should have something like this:

Saving the resource

Then you can save the GDNativeLibrary resource as bin/libsimple.gdnlib with the Save button in the Inspector.

The file is saved in a text-based format and should have contents similar to this:

[general]

singleton=false
load_once=true
symbol_prefix="godot_"
reloadable=true

[entry]

OSX.64="res://bin/libsimple.dylib"
OSX.32="res://bin/libsimple.dylib"
Windows.64="res://bin/libsimple.dll"
X11.64="res://bin/libsimple.so"

[dependencies]

OSX.64=[  ]
OSX.32=[  ]
Windows.64=[  ]
X11.64=[  ]

Creating the NativeScript (.gdns) file

Now that the .gdnlib file tells Godot how to load the library, we need to tell it about the "Simple" object class. Create a resource with the .gdns extension for NativeScript.

As with the GDNativeLibrary resource, click the button to create a new resource in the Inspector and select NativeScript.

The inspector shows a few properties we need to fill in.

Class Name
The object class name < declared in our C source. Enter "Simple", as specified in code>godot_nativescript_register_class</code>.
Library
Select the .gdnlib file.

Final connection

Now we can implement the main.gd code.

extends Control

# load the Simple library
onready var data = preload("res://bin/simple.gdns").new()

func _on_Button_pressed():
    $Label.text = "Data = " + data.get_data()

I've gotten GDNative to work on iOS and Android but there isn't a single guide to the process anywhere. I mostly grabbed bits and pieces from different places and made my own pipeline.

Here is a brief description:

  • Code is just C no C++ since it's easier to compile and I don't have to worry about the STD.
  • The build system is based on CMake. Even though SCons is the Godot preferred build system, CMake is simple enough for me to set up and I'm most comfortable with it.
  • I'm using the android NDK CMake toolchain file located in $ANDROID\_SDK\_ROOT/ndk-bundle/build/cmake/android.toolchain.cmake to compile the android binaries (be sure to download the ndk package from the android skd manager)
  • I'm using https://github.com/leetal/ios-cmake as a toolchain file to compile the ios library
  • The android version has to be compiled as a shared library (.so)
  • The iOS version has to be compiled as a static library (.a).
  • Finally follow the gdnative configuration in godot as the official guide says https://docs.godotengine.org/en/3.2/tutorials/plugins/gdnative/gdnative-c-example.html#creating-the-gdnativelibrary-gdnlib-file

The proper gdnlib setup was more of a trial and error thing so here you go:

[general]

singleton=false
load_once=true
symbol_prefix="godot_"
reloadable=true

[entry]

Android.armeabi-v7a="res://Minmax/build/android-armeabi-v7a/libminmax.so"
Android.arm64-v8a="res://Minmax/build/android-arm64-v8a/libminmax.so"
Android.x86="res://Minmax/build/android-x86/libminmax.so"
Android.x86_64="res://Minmax/build/android-x86_64/libminmax.so"
OSX.64="res://Minmax/build/Darwin/libminmax.dylib"
Windows.64="res://Minmax/build/Debug/minmax.dll"
X11.64="res://Minmax/build/Linux/libminmax.so"
X11.32="res://Minmax/build/Linux/libminmax.so"
iOS.armv7="res://Minmax/build/ios/libminmax.a"
iOS.arm64="res://Minmax/build/ios/libminmax.a"

[dependencies]

Android.armeabi-v7a=[  ]
Android.arm64-v8a=[  ]
Android.x86=[  ]
Android.x86_64=[  ]
OSX.64=[  ]
Windows.64=[  ]
X11.64=[  ]
X11.32=[  ]
iOS.armv7=[  ]
iOS.arm64=[  ]

Minmax is the name of the folder that contains my GDNative code

Here are a couple of scripts I made that really helped simplify the process: https://gist.github.com/jemsz95/80d4c0de6918989e9b1f503480efa871

There are a lot of assumptions made in those scripts about the project structure and other things. For example the native library is called Minmax, the GDNative headers are in the parent directory. The scripts expect a couple of environment variables.

Edit them to your hearts content and message me if you have any questions.

I'm planning to make a write-up of all of these things with more detail in a bit but I've been busy with work and stuff.

Anyways hope it helps!

Comments

As I understand it, supporting mobile compilation of GDNative C scripts is a little...non-trivial because:

  • On Android it likely requires the use of "Java Native Interface"; and,
  • On iOS it probably requires use of...something else. :D (Actually, I think it's "doesn't support dynamic libraries".)

Although, having said that, I just did a search for GDNative & Java and found this seemingly complete example of making GDNative C code to run on Android... (so you might be in luck! :) ) :

Also, these links/issues have various background/suggestion/PRs/examples related to the topic:

Semi-related:

Calling GDScript functions from GDNative

Create a Callback Method inside GDScript

...
func gdscript_callback(msg):
    print ("Called back from native C++ code: " + msg)
...

You can place this method inside of the same script file where you load() your NativeScript .gdns file as described in previous post.

Pass Class Instance that Contains the Callback from GDScript to Native Code

Your custom native C/C++ function registered with GDNative can take parameters from GDScript which are passed to native code as godot_variant. To be able to call a method on a GDScript object, you can pass an instance of this object as parameter.

extends Node2D

onready var agdn = preload("res://lib/android_gdnative.gdns").new()

...
func _ready():
    agdn.test(self)   
...   

Here I've passed the self variable to my test() function that I've created in the previous post. The method gdscript_callback() shown in the previous step is also defined inside of this same script, therefore I'm passing self variable and not an instance of some other object. How I changed the test()/android_gdnative_test() implementation from previous post is shown in the following step.

Cache the Instance in Native C/C++ code

I've used the GDNative api function godot_variant_new_copy() to create a copy of the self variable passed from GDScript and stored it inside a godot_variant struct(also named self). The self variable was passed from GDScript as the first argument - p_args[0]

...
godot_variant self;

godot_variant android_gdnative_test(godot_object *p_instance, void *p_method_data
    , void *p_user_data, int p_num_args, godot_variant **p_args)
{
    // Cache object that can be used to call our method in GDScript
    api->godot_variant_new_copy(&self, p_args[0]);
    ...

Call the GDScript Method from C/C++ code

Once you have an instance of the class that contains the callback code, you can use following snippet to call it back

...
    // Prepare args to pass from native C++ to GDScript
    godot_variant arg;
    auto msg = api->godot_string_chars_to_utf8("Message from native C++ code");
    api->godot_variant_new_string(&arg, &msg);
    std::vector<godot_variant*> args{ &arg };

    // Call our method in GDScript
    auto cb_name = api->godot_string_chars_to_utf8("gdscript_callback");
    godot_variant_call_error err;
    api->godot_variant_call(&self, &cb_name, (const godot_variant**)args.data(), 
        args.size(), &err);
...

You can pass arguments from native C/C++ code to GDScript easily as a variant type. GDNative api provides various functions to convert from different types into a godot_variant. In this case I just passed a string as parameter.

Projects

GDNative C++ Example
https://github.com/BastiaanOlij/gdnative_cpp_example
GDNative C++ example for tutorial
Using Godot's GDNative on Android
https://github.com/sixo/gdnative_example
https://www.sisik.eu/blog/godot/gd-native-on-android
Using_Godots_GDNative_on_Android_-_sisik.pdf
GDNative demos
https://github.com/godotengine/gdnative-demos
Demo projects for GDNative
godot-headers
https://github.com/godotengine/godot-headers
Headers for the Godot API supplied by the GDNative module.

See also

Favorite site

Discussion

Projects

author Zer0 sitemap