Integration Extension Example

What this is: A comprehensive example showing how to integrate third-party libraries with GDExtensions. This demonstrates wrapping external libraries like SQLite, cURL, and Dear ImGui to make them accessible from GDScript, along with platform-specific optimizations.

What it demonstrates: Third-party library integration, custom build system configuration, cross-platform compilation, memory management across library boundaries, and how to expose complex C/C++ APIs through clean GDScript interfaces. Shows practical patterns for networking, database access, and custom rendering.

When to use this pattern: Essential reference when your project needs to use existing C++ libraries, implement custom networking protocols, integrate with system APIs, or create platform-specific optimizations. Perfect for understanding how to bridge the gap between native libraries and Godot’s scripting environment.

Project Structure

integration_extension/
├── src/
   ├── register_types.cpp
   ├── network_manager.h/cpp
   ├── database_interface.h/cpp
   ├── custom_renderer.h/cpp
   └── platform_handler.h/cpp
├── thirdparty/
   ├── sqlite/
   ├── curl/
   └── imgui/
├── platform/
   ├── windows/
   ├── linux/
   └── macos/
├── demo/
└── SConstruct

Network Manager with HTTP/WebSocket

// src/network_manager.h
#ifndef NETWORK_MANAGER_H
#define NETWORK_MANAGER_H

#include <godot_cpp/classes/ref_counted.hpp>
#include <godot_cpp/classes/tcp_server.hpp>
#include <godot_cpp/classes/stream_peer_tcp.hpp>
#include <godot_cpp/templates/hash_map.hpp>
#include <thread>
#include <atomic>

namespace godot {

class NetworkManager : public RefCounted {
    GDCLASS(NetworkManager, RefCounted)

public:
    enum ConnectionState {
        STATE_DISCONNECTED,
        STATE_CONNECTING,
        STATE_CONNECTED,
        STATE_ERROR
    };

private:
    // HTTP client
    struct HTTPRequest {
        String url;
        String method;
        Dictionary headers;
        PackedByteArray body;
        Callable callback;
        int id;
    };

    // WebSocket connection
    class WebSocketPeer : public RefCounted {
    public:
        Ref<StreamPeerTCP> tcp_peer;
        String url;
        ConnectionState state = STATE_DISCONNECTED;
        PackedByteArray read_buffer;
        PackedByteArray write_buffer;
        bool is_binary_frame = false;

        Error connect_to_url(const String &p_url);
        Error send_text(const String &p_text);
        Error send_binary(const PackedByteArray &p_data);
        String get_message();
        void poll();
    };

    // Server management
    Ref<TCPServer> tcp_server;
    HashMap<int, Ref<StreamPeerTCP>> client_connections;
    HashMap<int, Ref<WebSocketPeer>> websocket_peers;

    std::thread network_thread;
    std::atomic<bool> should_stop{false};
    Mutex request_mutex;
    Vector<HTTPRequest> pending_requests;
    int next_request_id = 1;

    void _network_thread_func();
    void _process_http_request(const HTTPRequest &p_request);
    void _handle_websocket_frame(Ref<WebSocketPeer> p_peer);

protected:
    static void _bind_methods();

public:
    NetworkManager();
    ~NetworkManager();

    // HTTP methods
    int http_request(const String &p_url, const String &p_method = "GET",
                     const Dictionary &p_headers = Dictionary(),
                     const PackedByteArray &p_body = PackedByteArray());
    void cancel_http_request(int p_id);

    // WebSocket methods
    int websocket_connect(const String &p_url);
    void websocket_send(int p_peer_id, const Variant &p_data);
    void websocket_close(int p_peer_id, int p_code = 1000, const String &p_reason = "");

    // TCP Server
    Error start_server(int p_port, const String &p_bind_address = "*");
    void stop_server();
    bool is_server_running() const;
    PackedInt32Array get_client_ids() const;

    // Client management
    void send_to_client(int p_client_id, const PackedByteArray &p_data);
    void disconnect_client(int p_client_id);

    // Signals
    static void _bind_signals();
};

}

#endif

Database Interface with SQLite

// src/database_interface.h
#ifndef DATABASE_INTERFACE_H
#define DATABASE_INTERFACE_H

#include <godot_cpp/classes/ref_counted.hpp>
#include <godot_cpp/variant/dictionary.hpp>
#include <sqlite3.h>
#include <mutex>

namespace godot {

class DatabaseInterface : public RefCounted {
    GDCLASS(DatabaseInterface, RefCounted)

private:
    sqlite3 *db = nullptr;
    String db_path;
    std::mutex db_mutex;
    bool in_transaction = false;

    struct PreparedStatement {
        sqlite3_stmt *stmt;
        String query;
        int param_count;
    };

    HashMap<String, PreparedStatement> prepared_statements;

    Dictionary _fetch_row(sqlite3_stmt *stmt);
    Error _check_error(int result, const String &operation);

protected:
    static void _bind_methods();

public:
    DatabaseInterface();
    ~DatabaseInterface();

    // Connection management
    Error open(const String &p_path);
    void close();
    bool is_open() const;

    // Query execution
    Dictionary execute(const String &p_query, const Array &p_params = Array());
    Array execute_query(const String &p_query, const Array &p_params = Array());
    int64_t execute_update(const String &p_query, const Array &p_params = Array());

    // Prepared statements
    Error prepare(const String &p_name, const String &p_query);
    Dictionary execute_prepared(const String &p_name, const Array &p_params = Array());
    void close_prepared(const String &p_name);

    // Transactions
    Error begin_transaction();
    Error commit();
    Error rollback();
    bool is_in_transaction() const;

    // Schema management
    bool table_exists(const String &p_table);
    Array get_table_columns(const String &p_table);
    Error create_table(const String &p_table, const Dictionary &p_schema);
    Error drop_table(const String &p_table);

    // Backup and maintenance
    Error backup_to(const String &p_path);
    Error vacuum();
    int64_t get_last_insert_id();

    // Batch operations
    Error execute_batch(const PackedStringArray &p_queries);
    int64_t insert_many(const String &p_table, const Array &p_records);
};

}

#endif
// src/database_interface.cpp (partial implementation)
#include "database_interface.h"
#include <godot_cpp/core/error_macros.hpp>

using namespace godot;

void DatabaseInterface::_bind_methods() {
    ClassDB::bind_method(D_METHOD("open", "path"), &DatabaseInterface::open);
    ClassDB::bind_method(D_METHOD("close"), &DatabaseInterface::close);
    ClassDB::bind_method(D_METHOD("is_open"), &DatabaseInterface::is_open);

    ClassDB::bind_method(D_METHOD("execute", "query", "params"),
                        &DatabaseInterface::execute, DEFVAL(Array()));
    ClassDB::bind_method(D_METHOD("execute_query", "query", "params"),
                        &DatabaseInterface::execute_query, DEFVAL(Array()));
    ClassDB::bind_method(D_METHOD("execute_update", "query", "params"),
                        &DatabaseInterface::execute_update, DEFVAL(Array()));

    ClassDB::bind_method(D_METHOD("begin_transaction"), &DatabaseInterface::begin_transaction);
    ClassDB::bind_method(D_METHOD("commit"), &DatabaseInterface::commit);
    ClassDB::bind_method(D_METHOD("rollback"), &DatabaseInterface::rollback);

    ClassDB::bind_method(D_METHOD("table_exists", "table"), &DatabaseInterface::table_exists);
    ClassDB::bind_method(D_METHOD("create_table", "table", "schema"), &DatabaseInterface::create_table);

    ADD_SIGNAL(MethodInfo("query_executed", PropertyInfo(Variant::STRING, "query")));
    ADD_SIGNAL(MethodInfo("error_occurred", PropertyInfo(Variant::STRING, "message")));
}

Error DatabaseInterface::open(const String &p_path) {
    std::lock_guard<std::mutex> lock(db_mutex);

    if (db != nullptr) {
        close();
    }

    CharString path_utf8 = p_path.utf8();
    int result = sqlite3_open(path_utf8.get_data(), &db);

    if (result != SQLITE_OK) {
        String error = sqlite3_errmsg(db);
        sqlite3_close(db);
        db = nullptr;
        ERR_FAIL_V_MSG(FAILED, "Failed to open database: " + error);
    }

    // Enable foreign keys
    execute("PRAGMA foreign_keys = ON");

    // Set busy timeout
    sqlite3_busy_timeout(db, 5000);

    db_path = p_path;
    return OK;
}

Array DatabaseInterface::execute_query(const String &p_query, const Array &p_params) {
    std::lock_guard<std::mutex> lock(db_mutex);

    ERR_FAIL_NULL_V(db, Array());

    sqlite3_stmt *stmt;
    CharString query_utf8 = p_query.utf8();

    int result = sqlite3_prepare_v2(db, query_utf8.get_data(), -1, &stmt, nullptr);
    ERR_FAIL_COND_V(_check_error(result, "prepare") != OK, Array());

    // Bind parameters
    for (int i = 0; i < p_params.size(); i++) {
        Variant param = p_params[i];

        switch (param.get_type()) {
            case Variant::NIL:
                sqlite3_bind_null(stmt, i + 1);
                break;
            case Variant::BOOL:
            case Variant::INT:
                sqlite3_bind_int64(stmt, i + 1, (int64_t)param);
                break;
            case Variant::FLOAT:
                sqlite3_bind_double(stmt, i + 1, (double)param);
                break;
            case Variant::STRING:
                {
                    CharString str = String(param).utf8();
                    sqlite3_bind_text(stmt, i + 1, str.get_data(), -1, SQLITE_TRANSIENT);
                }
                break;
            case Variant::PACKED_BYTE_ARRAY:
                {
                    PackedByteArray bytes = param;
                    sqlite3_bind_blob(stmt, i + 1, bytes.ptr(), bytes.size(), SQLITE_TRANSIENT);
                }
                break;
            default:
                WARN_PRINT("Unsupported parameter type: " + Variant::get_type_name(param.get_type()));
        }
    }

    // Execute and fetch results
    Array results;
    while (sqlite3_step(stmt) == SQLITE_ROW) {
        results.append(_fetch_row(stmt));
    }

    sqlite3_finalize(stmt);
    emit_signal("query_executed", p_query);

    return results;
}

Custom Renderer with ImGui

// src/custom_renderer.h
#ifndef CUSTOM_RENDERER_H
#define CUSTOM_RENDERER_H

#include <godot_cpp/classes/node3d.hpp>
#include <godot_cpp/classes/rendering_server.hpp>
#include <godot_cpp/classes/viewport.hpp>
#include <imgui.h>

namespace godot {

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

private:
    RID viewport_texture;
    RID canvas;
    RID canvas_item;

    // ImGui context
    ImGuiContext *imgui_context = nullptr;
    PackedByteArray vertex_buffer;
    PackedByteArray index_buffer;

    // Render state
    bool show_demo_window = true;
    bool show_metrics = false;
    float clear_color[4] = {0.2f, 0.3f, 0.3f, 1.0f};

    void _setup_imgui();
    void _render_imgui();
    void _upload_font_texture();

protected:
    static void _bind_methods();
    void _notification(int p_what);

public:
    CustomRenderer();
    ~CustomRenderer();

    // ImGui interface
    void begin_frame();
    void end_frame();

    // UI building
    void add_window(const String &p_title, const Callable &p_callback);
    void add_menu_bar(const Callable &p_callback);

    // Widgets
    bool button(const String &p_label);
    bool checkbox(const String &p_label, bool p_value);
    float slider_float(const String &p_label, float p_value, float p_min, float p_max);
    String input_text(const String &p_label, const String &p_text);
    Color color_picker(const String &p_label, const Color &p_color);

    // Layout
    void begin_group();
    void end_group();
    void same_line();
    void separator();
    void spacing();

    // Render to texture
    Ref<ImageTexture> render_to_texture(int p_width, int p_height);
};

}

#endif

Platform Handler

// src/platform_handler.h
#ifndef PLATFORM_HANDLER_H
#define PLATFORM_HANDLER_H

#include <godot_cpp/classes/object.hpp>

namespace godot {

class PlatformHandler : public Object {
    GDCLASS(PlatformHandler, Object)

private:
    static PlatformHandler *singleton;

    // Platform-specific handles
#ifdef WINDOWS_ENABLED
    void *hwnd = nullptr;
#elif defined(X11_ENABLED)
    void *display = nullptr;
    unsigned long window = 0;
#elif defined(MACOS_ENABLED)
    void *ns_window = nullptr;
#endif

protected:
    static void _bind_methods();

public:
    static PlatformHandler *get_singleton();

    PlatformHandler();
    ~PlatformHandler();

    // Window management
    void *get_native_handle();
    void set_window_always_on_top(bool p_enabled);
    void set_window_transparency(float p_alpha);

    // System integration
    String get_system_username();
    String get_system_locale();
    PackedStringArray get_system_fonts();

    // File associations
    Error register_file_association(const String &p_extension, const String &p_description);
    bool is_file_association_registered(const String &p_extension);

    // Clipboard extended
    void set_clipboard_image(const Ref<Image> &p_image);
    Ref<Image> get_clipboard_image();

    // Native dialogs
    String show_native_file_dialog(const String &p_title, const String &p_filters, bool p_save = false);
    int show_native_message_box(const String &p_title, const String &p_message, int p_buttons);

    // Process management
    int launch_process(const String &p_path, const PackedStringArray &p_arguments);
    bool is_process_running(int p_pid);
    void terminate_process(int p_pid);

    // Hardware info
    Dictionary get_cpu_info();
    Dictionary get_gpu_info();
    Dictionary get_memory_info();
    Array get_display_info();

    // Power management
    int get_battery_level();
    bool is_on_battery_power();
    void prevent_sleep(bool p_prevent);
};

}

#endif

Integration Usage Example

# demo/test_integration.gd
extends Node

var network: NetworkManager
var database: DatabaseInterface
var renderer: CustomRenderer
var platform: PlatformHandler

func _ready():
    # Initialize components
    network = NetworkManager.new()
    database = DatabaseInterface.new()
    renderer = CustomRenderer.new()
    platform = PlatformHandler.get_singleton()

    add_child(renderer)

    # Setup database
    setup_database()

    # Start network server
    setup_network()

    # Configure platform
    setup_platform()

    # Test integrations
    test_http_request()
    test_database_operations()
    test_imgui_rendering()

func setup_database():
    database.open("user://game_data.db")

    # Create tables if not exist
    if not database.table_exists("players"):
        database.create_table("players", {
            "id": "INTEGER PRIMARY KEY AUTOINCREMENT",
            "name": "TEXT NOT NULL",
            "score": "INTEGER DEFAULT 0",
            "created_at": "TIMESTAMP DEFAULT CURRENT_TIMESTAMP"
        })

    if not database.table_exists("settings"):
        database.create_table("settings", {
            "key": "TEXT PRIMARY KEY",
            "value": "TEXT"
        })

func setup_network():
    # Start TCP server
    network.start_server(8080)

    # Connect signals
    network.connect("client_connected", _on_client_connected)
    network.connect("client_disconnected", _on_client_disconnected)
    network.connect("data_received", _on_data_received)
    network.connect("http_response", _on_http_response)

    print("Server started on port 8080")

func setup_platform():
    # Set window properties
    platform.set_window_always_on_top(false)
    platform.set_window_transparency(1.0)

    # Register file association
    platform.register_file_association(".save", "Game Save File")

    # Get system info
    var cpu_info = platform.get_cpu_info()
    var memory_info = platform.get_memory_info()
    print("CPU: ", cpu_info)
    print("Memory: ", memory_info)

    # Prevent sleep during gameplay
    platform.prevent_sleep(true)

func test_http_request():
    # Make HTTP request
    var request_id = network.http_request(
        "https://api.example.com/data",
        "GET",
        {"Authorization": "Bearer token123"}
    )
    print("HTTP request sent with ID: ", request_id)

func test_database_operations():
    database.begin_transaction()

    # Insert player
    var player_id = database.execute_update(
        "INSERT INTO players (name, score) VALUES (?, ?)",
        ["Player1", 100]
    )

    # Query players
    var players = database.execute_query(
        "SELECT * FROM players WHERE score > ?",
        [50]
    )

    for player in players:
        print("Player: ", player)

    database.commit()

    # Prepared statement
    database.prepare("get_top_players",
        "SELECT * FROM players ORDER BY score DESC LIMIT ?")

    var top_players = database.execute_prepared("get_top_players", [10])
    print("Top players: ", top_players)

func test_imgui_rendering():
    renderer.begin_frame()

    # Create ImGui window
    renderer.add_window("Game Settings", func():
        if renderer.button("Save Settings"):
            save_settings()

        renderer.separator()

        var music_enabled = database.execute_query(
            "SELECT value FROM settings WHERE key = 'music_enabled'"
        )

        var enabled = music_enabled.size() > 0 and music_enabled[0]["value"] == "true"
        enabled = renderer.checkbox("Enable Music", enabled)

        if enabled != (music_enabled.size() > 0 and music_enabled[0]["value"] == "true"):
            database.execute_update(
                "INSERT OR REPLACE INTO settings (key, value) VALUES ('music_enabled', ?)",
                [str(enabled)]
            )
    )

    renderer.end_frame()

func _on_client_connected(client_id: int):
    print("Client connected: ", client_id)

    # Send welcome message
    var welcome = {
        "type": "welcome",
        "message": "Connected to game server",
        "timestamp": Time.get_ticks_msec()
    }
    network.send_to_client(client_id, var_to_bytes(welcome))

func _on_data_received(client_id: int, data: PackedByteArray):
    var message = bytes_to_var(data)
    print("Received from ", client_id, ": ", message)

    # Process game messages
    match message.get("type", ""):
        "login":
            handle_login(client_id, message)
        "action":
            handle_action(client_id, message)
        _:
            print("Unknown message type")

func handle_login(client_id: int, message: Dictionary):
    var username = message.get("username", "")

    # Check database for player
    var result = database.execute_query(
        "SELECT * FROM players WHERE name = ?",
        [username]
    )

    if result.size() == 0:
        # Create new player
        database.execute_update(
            "INSERT INTO players (name) VALUES (?)",
            [username]
        )

    # Send response
    network.send_to_client(client_id, var_to_bytes({
        "type": "login_response",
        "success": true,
        "player_data": result[0] if result.size() > 0 else {}
    }))

Build Configuration with External Libraries

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

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

# Add third-party libraries
env.Append(CPPPATH=[
    "thirdparty/sqlite",
    "thirdparty/curl/include",
    "thirdparty/imgui"
])

# Platform-specific configuration
if env["platform"] == "windows":
    env.Append(LIBS=["ws2_32", "winmm", "advapi32"])
    env.Append(LIBPATH=["thirdparty/curl/lib/win64"])
    env.Append(LIBS=["libcurl"])

elif env["platform"] == "linux":
    # Use pkg-config for system libraries
    env.ParseConfig("pkg-config --cflags --libs libcurl")
    env.Append(LIBS=["pthread", "dl"])

elif env["platform"] == "macos":
    env.Append(FRAMEWORKS=["CoreFoundation", "Security"])
    env.Append(LIBPATH=["thirdparty/curl/lib/macos"])
    env.Append(LIBS=["curl"])

# Compile third-party sources
thirdparty_sources = [
    "thirdparty/sqlite/sqlite3.c",
    "thirdparty/imgui/imgui.cpp",
    "thirdparty/imgui/imgui_draw.cpp",
    "thirdparty/imgui/imgui_widgets.cpp",
    "thirdparty/imgui/imgui_tables.cpp"
]

# Add our sources
sources = Glob("src/*.cpp")
sources += Glob("platform/" + env["platform"] + "/*.cpp")
sources += thirdparty_sources

# Build configuration
if env["target"] == "template_release":
    env.Append(CPPDEFINES=["NDEBUG"])
    env.Append(CCFLAGS=["-O3"])

# Build the library
library = env.SharedLibrary(
    "demo/bin/integration_extension",
    source=sources
)

Default(library)

Platform-Specific Implementation Example

// platform/windows/platform_handler_windows.cpp
#ifdef WINDOWS_ENABLED

#include "../../src/platform_handler.h"
#include <windows.h>
#include <shlobj.h>

void *PlatformHandler::get_native_handle() {
    if (!hwnd) {
        // Get window handle from Godot
        hwnd = GetActiveWindow();
    }
    return hwnd;
}

void PlatformHandler::set_window_always_on_top(bool p_enabled) {
    HWND handle = (HWND)get_native_handle();
    SetWindowPos(handle,
                 p_enabled ? HWND_TOPMOST : HWND_NOTOPMOST,
                 0, 0, 0, 0,
                 SWP_NOMOVE | SWP_NOSIZE);
}

String PlatformHandler::get_system_username() {
    char username[256];
    DWORD size = sizeof(username);
    if (GetUserNameA(username, &size)) {
        return String(username);
    }
    return "";
}

Dictionary PlatformHandler::get_cpu_info() {
    SYSTEM_INFO sysinfo;
    GetSystemInfo(&sysinfo);

    Dictionary info;
    info["processor_count"] = (int)sysinfo.dwNumberOfProcessors;
    info["page_size"] = (int)sysinfo.dwPageSize;
    info["processor_architecture"] = (int)sysinfo.wProcessorArchitecture;

    // Get CPU name from registry
    HKEY hKey;
    char cpuName[256] = {0};
    DWORD size = sizeof(cpuName);

    if (RegOpenKeyExA(HKEY_LOCAL_MACHINE,
                      "HARDWARE\\DESCRIPTION\\System\\CentralProcessor\\0",
                      0, KEY_READ, &hKey) == ERROR_SUCCESS) {
        RegQueryValueExA(hKey, "ProcessorNameString", NULL, NULL,
                        (LPBYTE)cpuName, &size);
        RegCloseKey(hKey);
        info["processor_name"] = String(cpuName);
    }

    return info;
}

#endif // WINDOWS_ENABLED

Summary

This integration extension demonstrates:

  1. External Library Integration: SQLite, libcurl, ImGui
  2. Network Programming: HTTP client, WebSocket, TCP server
  3. Database Operations: Full SQL interface with transactions
  4. Custom Rendering: ImGui integration for tools/debugging
  5. Platform-Specific Features: Native window handling, system info
  6. Thread Safety: Proper synchronization for concurrent operations
  7. Error Handling: Comprehensive error checking and recovery

Key features:

This example provides a complete template for building GDExtensions that integrate with external systems and libraries.