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.