How to use a dynamic library with Defold?

Hi everyone,

I’m trying to integrate Rust into my Defold project. I’m using the mlua crate to expose custom Rust types to Lua. To avoid static linking conflicts (especially duplicated Lua symbols), I build my Rust code as a dynamic library (cdylib) and load it at runtime.

However, I’m having trouble finding a clear guide or example on how to properly integrate and load a dynamic library (DLL/.so/.dylib) in a Defold native extension. Most native extensions I’ve found are statically linked, and Defold itself doesn’t seem to document dynamic linking patterns in detail.

My question is:
Are there any known best practices or examples of using dynamic libraries in Defold extensions, especially for platforms like Windows/macOS/Linux?

My Rust test code

use mlua::{Lua, UserData, UserDataMethods};
use mlua::prelude::*;

// Define a custom type
struct MyType {
    value: i32,
}

impl MyType {
    fn new() -> Self {
        MyType { value: 42 }
    }

    fn some_method(&self) {
        // println!("Method called, value: {}", self.value);
    }
}

impl UserData for MyType {
    fn add_methods<M: UserDataMethods<Self>>(methods: &mut M) {
        methods.add_method("some_method", |_, this, ()| {
            this.some_method();
            Ok(())
        });

        methods.add_function("new", |_, ()| Ok(MyType::new()));
    }

    fn add_fields<F: mlua::UserDataFields<Self>>(fields: &mut F) {
        fields.add_field_method_get("value", |_, this| Ok(this.value));
    }
}

#[mlua::lua_module]
fn rust_module(lua: &Lua) -> LuaResult<LuaTable> {
    let exports = lua.create_table()?;
    exports.set("MyType", lua.create_proxy::<MyType>().unwrap()).unwrap();
    Ok(exports)
}

My C++ wrapper right now(how to link my dynamic lib?):

// myextension.cpp
// Extension lib defines
#define LIB_NAME "MyExtension"
#define MODULE_NAME "myextension"

// include the Defold SDK
#include <dmsdk/sdk.h>
#include "rust_defold_try.h" // Include the Rust library header
extern "C" {
    int luaopen_rust_module(lua_State* L);
}
#include "Assert/AssertUtils.h"

static dmExtension::Result AppInitializeMyExtension(dmExtension::AppParams *params)
{
    dmLogInfo("AppInitializeMyExtension");
    return dmExtension::RESULT_OK;
}

static dmExtension::Result InitializeMyExtension(dmExtension::Params *params)
{
    luaopen_rust_module(params->m_L);
    dmLogInfo("Registered %s Extension", MODULE_NAME);
    return dmExtension::RESULT_OK;
}

static dmExtension::Result AppFinalizeMyExtension(dmExtension::AppParams *params)
{
    dmLogInfo("AppFinalizeMyExtension");
    return dmExtension::RESULT_OK;
}

static dmExtension::Result FinalizeMyExtension(dmExtension::Params *params)
{
    dmLogInfo("FinalizeMyExtension");

    return dmExtension::RESULT_OK;
}

static dmExtension::Result OnUpdateMyExtension(dmExtension::Params *params)
{
    dmLogInfo("OnUpdateMyExtension");
    return dmExtension::RESULT_OK;
}

static void OnEventMyExtension(dmExtension::Params *params, const dmExtension::Event *event)
{
    switch (event->m_Event)
    {
    case dmExtension::EVENT_ID_ACTIVATEAPP:
        dmLogInfo("OnEventMyExtension - EVENT_ID_ACTIVATEAPP");
        break;
    case dmExtension::EVENT_ID_DEACTIVATEAPP:
        dmLogInfo("OnEventMyExtension - EVENT_ID_DEACTIVATEAPP");
        break;
    case dmExtension::EVENT_ID_ICONIFYAPP:
        dmLogInfo("OnEventMyExtension - EVENT_ID_ICONIFYAPP");
        break;
    case dmExtension::EVENT_ID_DEICONIFYAPP:
        dmLogInfo("OnEventMyExtension - EVENT_ID_DEICONIFYAPP");
        break;
    default:
        dmLogWarning("OnEventMyExtension - Unknown event id");
        break;
    }
}

// Defold SDK uses a macro for setting up extension entry points:
//
// DM_DECLARE_EXTENSION(symbol, name, app_init, app_final, init, update, on_event, final)

// MyExtension is the C++ symbol that holds all relevant extension data.
// It must match the name field in the `ext.manifest`
DM_DECLARE_EXTENSION(MyExtension, LIB_NAME, AppInitializeMyExtension, AppFinalizeMyExtension, InitializeMyExtension, OnUpdateMyExtension, OnEventMyExtension, FinalizeMyExtension)

My ext.manifest:

# C++ symbol in your extension
name: "MyExtension"

platforms:
  x86_64-win32:
    context:
      includes: ["include"]
      libs: ["rust_defold_try"]

My extension directory structure:

myextension
├── ext.manifest
├── include
│   └── rust_defold_try.h
├── lib
│   └── x86_64-win32
│       ├── rust_defold_try.dll
│       └── rust_defold_try.dll.lib
├── Sender.script
└── src
    ├── Assert
    │   ├── AssertUtils.h
    │   └── Private
    │       ├── AssertUtilsImplementationWithLogging.h
    │       └── AssertUtilsImplementationWithoutLogging.h
    └── myextension.cpp

Right now, when I run my project with the dynamic library, the application fails to launch completely, and I don’t see any logs or errors — it just silently fails. I suspect the issue is related to how the dynamic library is being loaded or initialized, but I have no clear direction to debug this.

I think Ive done something like this before (with a C dll lib). However, there are problems going this route.
The Defold native extension is compiled with specific build settings - make sure yours are matching and that you have the right import name mangling otherwise it wont work.

Other problems you will run into:

  • On windows, the security system may stop access to the dll. The search path is always the exe run path, and system paths. You can add your dll path to it, or just make sure it outputs to the exe path.
  • On other systems pathing is also important, and OSX, IOS, Android and Linux all have their own nuances when loading so’s. Make sure you have both security access and that its in the right place.
  • Dependencies - if your dll has non-system dependencies, make sure they are included in your path. On Windows this can especially be a pain.
  • Name mangling. Make sure you are using CDECL or STDCALL exports for your methods or they wont be accessible.
  • When building your rust dll make sure it is linked against the same runtime libs as the extension uses. You cant use vanilla Lua or Luajit because Defolds Lua is a modified Luajit/Lua runtime.

Another way around this, if you are not using html platforms (and Android?), you can use Luajit FFI directly (alot easier in this case). This is where you do something like:

ffi.load("mydll.dll")  -- note: change this dep on platform
ffi.cdef[[
       int luaopen_rust_module(lua_State* L);
]]

ffi.C.luaopen_rust_module(mystate)

Have a look at: FFI Library
Ask me if you have any specific q’s about it :slight_smile: Have done a little bit with luajit ffi.

However you cant really pass lua_State to this method from Luajit itself, since you would need a lua_State type that matches as a userdata. And I think this will be your main problem here. Note: you could make a getter that fetches it. Youd still need to build the dll against the Defold lua/luajit libs.

Whats the aim of getting the lua_State into rust? If you want to do that, it would be better to build a more direct compilation of the library (static) as you mention.

I should also note. Lua extensions method calling has performance penalties - the stack system for traditional Lua calling is very very slow. Any benefits of Rust perf wise will be limited by this. If you use the FFI route, there is no callthrough overhead, so you dont lose any perf, and in fact the LuaJit can often optimize the calling behavior to be even faster than normal C code :slight_smile:

The main drawbacks with FFI are you need to have per platform implementation of the interfaces, and there are some limitations on platform use (I think its html and Android - that use pure Lua).

Im guessing you want to make a Rust language interface (hence the dll). Probably best examine the Fennel and C# projects for more information about how they did their integrations?

Have a look in the forums, there are a couple of others too (that I cant remember from the top of my head).

1 Like

Thank you for such a detailed response. I’d like to clarify my goals for using Rust:

  1. Some degree of safety from memory leaks
  2. Easy integration of various libraries
  3. And honestly, I just really enjoy Rust and the functional programming features it offers

Lua is great, but to be honest, I often catch myself thinking how tricky it is to write certain algorithms or data structures in it — I’m never quite confident that I haven’t accidentally messed something up with references. That’s why I’d prefer to write part of the code in Rust — for example, I have a graph with both directed and undirected edges. This graph can hold data for both edges and vertices. I also have an algorithm that traverses the graph, collects some information, and returns a result. I think that gives you a better idea of what I’m trying to do.

In fact, I’ve already managed to compile a static Rust library and link it into Defold in a way that it builds and runs together. But in that version, I didn’t use Lua at all: if I needed to pass something from Rust to Lua/Defold, I would serialize my structures into a JSON string and send it to a C function, which then parsed the JSON and passed it into Lua. Yes, it sounds scary — and it works just as scarily.

After reading your comment about the incompatibility between vanilla Lua and Defold’s Lua, I realize I probably need to give up on using mlua in Rust. I’m not sure what the best approach is now: I’m thinking of calling Lua functions in Defold SDK from Rust to register my data types and other logic.

Alternatively, I could keep Rust entirely Lua-free, make all data structures C-compatible, and then wrap them in C.

Neither option seems simple or straightforward at this point.

Thanks again for your reply!

Hi. yeah. If you want to use the data structures and algorithms, I would probably recommend more direct function usage where the functions and data is mostly contained in Rust itself. This will minimize any oddities you have with marshaling any objects that might need to be transferred between each.

Probably stick to data buffer transfers too. Try using aligned memory too if you can - this can actually make copies very fast if they are cache friendly and it should work pretty well. A more complex alternative is to get Rust into the engine itself and provide a mechanism in C++ itself. That would provide great flexibility, but its a bigger process to go through.

All the best with the project. It can be a little tricky sorting out build and link processes between disparate systems.

1 Like

After reading your comment about the incompatibility between vanilla Lua and Defold’s Lua, I realize I probably need to give up on using mlua in Rust. I’m not sure what the best approach is now: I’m thinking of calling Lua functions in Defold SDK from Rust to register my data types and other logic.

If you haven’t seen the dmsdk crate, it might help you get where you need to go. It’s already got the Lua/C API and the Defold’s SDK wrapped for Rust - which might at least give you some ideas.

Right now, when I run my project with the dynamic library, the application fails to launch completely, and I don’t see any logs or errors — it just silently fails. I suspect the issue is related to how the dynamic library is being loaded or initialized, but I have no clear direction to debug this.

I can’t tell you exactly what’s wrong, but I have a few suggestions:

  1. Rename rust_defold_try.dll to librust_defold_try.dll. Defold expects the lib prefix and bob.jar would have renamed it for you when building.
  2. I also suggest renaming rust_defold_try.dll.lib to librust_defold_try.lib just to be sure that it isn’t matching the .dll first and thinking it’s also dynamic.
  3. The build/platform/ dir should have the dll - if it doesn’t that tells you the issue is probably the dll not being included. In which case trying moving librust_defold_try.dll to res/x86_64-win32/ (this was an old issue that may be fixed).
  4. Listing the dll in the libs: line of your ext.manifest is telling it to also look for the library in the system-wide places - which might be hiding an error if the editor doesn’t have access to STDERR. Running Defold from the shell or removing that line might show you the error.
  5. Extension-steam and defold-fmod both use dynamically linked libraries, so they might shed some light on what you need to do.
  6. And if none of those work, the PR that added dynamic lib support included tests which might give you an idea.
  7. And if all else fails, or you want to try a slightly different method: the Lua package library includes a way to load DLLs.
3 Likes