Defold Native Extensions:C/C++ 扩展引擎能力

系统讲解 Defold Native Extensions:ext.manifest 结构、C/C++ 绑定 Lua API 的方式、ext 目录组织规范、消息桥接 Lua↔C、第三方库集成、平台差异处理(iOS/Android/HTML5)与 SDK 构建流程。

引言

Defold 内置功能已经很丰富,但有些需求绕不过原生平台能力:推送通知、自定义加密、高性能计算、接入第三方 SDK(如 Firebase、广告、社交登录)。Native Extension(NE) 就是 Defold 的扩展机制——让你用 C/C++ 写代码,通过 Lua C API 暴露给脚本层使用。本文系统讲解 Defold Native Extensions:ext.manifest 配置文件、ext 目录结构、C/C++ 函数绑定到 Lua、消息桥接(Lua ↔ C)、第三方库集成方法、各平台(iOS/Android/HTML5/Desktop)差异处理,以及 SDK 构建与发布流程。

前置:/defold-script-system-lua/(Lua 运行时与消息系统)。跨平台发布见 /defold-cross-platform-publish/。


目录


1. Native Extension 是什么?何时需要它

Native Extension = 用 C/C++ 写的插件,通过 Lua C API 暴露给 Defold 脚本使用。

必须用到 NE 的场景:

场景说明
平台原生 APIiOS Push、Android 振动、设备信息读取
第三方 SDKFirebase、AdMob、Facebook 登录、Google Play
高性能计算大规模 A* 寻路、图像处理、加密算法
已有 C/C++ 库复用现有库(物理引擎、音频编码器)
自定义渲染底层 OpenGL/Vulkan 调用

不需要 NE 的场景:

- 纯游戏逻辑 → Lua 脚本足够
- HTTP 请求 → Defold 内置 http.request
- 文件读写 → sys.save / sys.load
- JSON/XML → json.decode / json.encode
- 基础 UI → GUI 组件

2. ext.manifest 与目录结构

2.1 创建 Extension

在项目中创建目录:
my_extension/
├── ext.manifest           ← 扩展配置(名称、版本、依赖)
├── include/               ← 公共头文件
├── src/
│   ├── my_extension.cpp   ← C++ 源文件
│   └── my_extension.h
├── lib/
│   ├── x86_64-osx/        ← macOS 静态库
│   ├── x86_64-win32/      ← Windows
│   ├── x86_64-linux/      ← Linux
│   ├── armv7-android/     ← Android armv7
│   ├── arm64-android/     ← Android arm64
│   ├── armv7-ios/         ← iOS armv7
│   ├── arm64-ios/         ← iOS arm64
│   └── js-web/            ← HTML5 (Emscripten)
└── res/
    └── ios/
        └── Info.plist     ← iOS 额外资源

2.2 ext.manifest 配置详解

# ext.manifest 示例
name: "MyExtension"
version: "1.0.0"

platforms:
  armv7-android:
    context:
      flags: ["-std=c++11"]
      libs: ["mylib_android"]   # 链接 libmylib_android.a
      frameworks: []            # Android 不需要 framework

  arm64-ios:
    context:
      flags: ["-std=c++11", "-fobjc-arc"]
      libs: []
      frameworks: ["Foundation", "UIKit"]   # iOS framework

  x86_64-osx:
    context:
      flags: ["-std=c++11"]
      libs: []
      frameworks: ["Foundation"]

  js-web:
    context:
      flags: ["-std=c++11"]
      libs: ["mylib_web"]       # Emscripten 编译的 .a

关键字段说明:

字段说明
name扩展名称,全局唯一
platforms按平台分配置
flags编译器选项
libs额外静态库文件名(不含 lib 前缀和 .a 后缀)
frameworksiOS/macOS 框架依赖
linkFlags链接器选项

3. C/C++ 函数绑定到 Lua

3.1 最小可运行绑定

// src/my_extension.cpp
#define LIB_NAME "MyExtension"

#include <dmsdk/sdk.h>   // Defold SDK 头文件
#include <stdlib.h>
#include <string.h>

// C 函数:给 Lua 暴露的方法
static int MyExtension_Hello(lua_State* L)
{
    // 从 Lua 栈读取参数
    const char* name = luaL_checkstring(L, 1);

    // 构造返回字符串
    char buf[256];
    snprintf(buf, sizeof(buf), "Hello, %s from C++!", name);

    // 压入返回值到 Lua 栈
    lua_pushstring(L, buf);
    return 1;  // 返回 1 个值
}

static int MyExtension_Add(lua_State* L)
{
    double a = luaL_checknumber(L, 1);
    double b = luaL_checknumber(L, 2);
    lua_pushnumber(L, a + b);
    return 1;
}

// 函数清单
static const luaL_reg MyExtension_methods[] =
{
    {"hello", MyExtension_Hello},
    {"add", MyExtension_Add},
    {0, 0}   // 结束标记
};

// 模块初始化函数
dmExtension::Result AppInitializeMyExtension(dmExtension::AppParams* params)
{
    dmLogInfo("MyExtension AppInitialize");
    return dmExtension::RESULT_OK;
}

dmExtension::Result InitializeMyExtension(dmExtension::Params* params)
{
    dmLogInfo("MyExtension Initialize");
    // 注册 Lua 模块
    luaL_register(params->m_L, LIB_NAME, MyExtension_methods);
    lua_pop(params->m_L, 1);
    return dmExtension::RESULT_OK;
}

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

dmExtension::Result FinalizeMyExtension(dmExtension::Params* params)
{
    return dmExtension::RESULT_OK;
}

// 声明扩展
DM_DECLARE_EXTENSION(MyExtension, LIB_NAME,
    AppInitializeMyExtension, AppFinalizeMyExtension,
    InitializeMyExtension, FinalizeMyExtension,
    0, 0)  // 最后两个是 Update/OnEvent(可选)

3.2 Lua 中使用

-- 使用扩展提供的 API
local myext = require("MyExtension")

function init(self)
    local greeting = myext.hello("Defold Developer")
    print(greeting)   -- 输出: Hello, Defold Developer from C++!

    local sum = myext.add(3.14, 2.86)
    print("Sum:", sum)   -- 输出: Sum: 6.0
end

注意:require(“MyExtension”) 中的名称必须与 ext.manifest 的 name 字段一致。

3.3 传递复杂数据:table ↔ struct

// 从 Lua table 读取数据到 C struct
static int MyExtension_SetConfig(lua_State* L)
{
    luaL_checktype(L, 1, LUA_TTABLE);

    lua_getfield(L, 1, "width");
    int width = luaL_checkint(L, -1);
    lua_pop(L, 1);

    lua_getfield(L, 1, "height");
    int height = luaL_checkint(L, -1);
    lua_pop(L, 1);

    lua_getfield(L, 1, "title");
    const char* title = luaL_checkstring(L, -1);
    lua_pop(L, 1);

    dmLogInfo("Config: %dx%d, title=%s", width, height, title);

    // 返回 boolean 表示成功
    lua_pushboolean(L, 1);
    return 1;
}

4. 消息桥接:Lua ↔ C

4.1 Lua → C 发消息(推荐方式)

Defold 推荐用消息而非直接函数调用做跨语言通信:

// C 端:注册消息监听
static int MyExtension_OnMessage(lua_State* L)
{
    // 收到来自 Lua 的 msg.post
    const char* msg_id = luaL_checkstring(L, 1);

    if (strcmp(msg_id, "request_device_info") == 0)
    {
        // 获取设备信息
        const char* platform = "unknown";
#if defined(DM_PLATFORM_IOS)
        platform = "iOS";
#elif defined(DM_PLATFORM_ANDROID)
        platform = "Android";
#elif defined(DM_PLATFORM_HTML5)
        platform = "HTML5";
#elif defined(DM_PLATFORM_OSX)
        platform = "macOS";
#elif defined(DM_PLATFORM_WINDOWS)
        platform = "Windows";
#endif

        lua_pushstring(L, platform);
        return 1;
    }

    return 0;
}

4.2 C → Lua 发消息

// C 端:通过 dmScript::PostCallback 回调 Lua
#include <dmsdk/script/script.h>

static void OnDeviceInfoReady(const char* info, void* user_data)
{
    dmScript::LuaCallbackInfo* cbk = (dmScript::LuaCallbackInfo*)user_data;

    lua_State* L = dmScript::GetCallbackLuaContext(cbk);
    int top = lua_gettop(L);

    if (dmScript::SetupCallback(cbk))
    {
        lua_pushstring(L, info);
        dmScript::PCall(L, 1, 0);   // 1 个参数,0 个返回值
        dmScript::TeardownCallback(cbk);
    }

    lua_settop(L, top);
}

4.3 Lua 端异步回调

-- Lua 端:异步请求设备信息
function request_device_info(callback)
    -- 发消息给 C 扩展
    msg.post("/my_ext", "get_device_info", { callback = callback })
end

function on_message(self, message_id, message, sender)
    if message_id == hash("device_info_ready") then
        print("Platform:", message.platform)
        if message.callback then
            message.callback(message.platform)
        end
    end
end

5. 第三方库集成

5.1 集成流程

1. 获取库的源码或预编译静态库
2. 按平台编译为 .a(iOS/macOS/Linux)或 .lib(Windows)
3. 放到 my_extension/lib/<platform>/
4. 在 ext.manifest → platforms → <platform> → libs 中声明
5. 在 C++ 中包含头文件并链接

5.2 示例:集成 zlib

# ext.manifest
name: "ZlibExtension"
version: "1.0.0"

platforms:
  x86_64-osx:
    context:
      libs: ["z"]           # 链接 libz.a
  arm64-ios:
    context:
      libs: ["z"]
  armv7-android:
    context:
      libs: ["z"]
// src/zlib_extension.cpp
#include <zlib.h>

static int ZlibExtension_Compress(lua_State* L)
{
    size_t len;
    const char* data = luaL_checklstring(L, 1, &len);

    uLong compLen = compressBound(len);
    char* compData = (char*)malloc(compLen);

    int ret = compress2((Bytef*)compData, &compLen,
                        (const Bytef*)data, len, Z_BEST_COMPRESSION);

    if (ret == Z_OK)
    {
        lua_pushlstring(L, compData, compLen);
        free(compData);
        return 1;
    }

    free(compData);
    lua_pushnil(L);
    lua_pushstring(L, "Compression failed");
    return 2;
}

6. 平台差异:iOS、Android、HTML5、Desktop

6.1 平台宏

// 平台判定宏(Defold SDK 提供)
#if defined(DM_PLATFORM_IOS)
    // iOS 专用代码
    #import <UIKit/UIKit.h>
#elif defined(DM_PLATFORM_ANDROID)
    // Android 专用代码
    #include <android/log.h>
    #define LOGI(...) __android_log_print(ANDROID_LOG_INFO, "MyExt", __VA_ARGS__)
#elif defined(DM_PLATFORM_HTML5)
    // HTML5 (Emscripten)
    #include <emscripten.h>
#elif defined(DM_PLATFORM_OSX)
    // macOS
#elif defined(DM_PLATFORM_WINDOWS)
    // Windows
#elif defined(DM_PLATFORM_LINUX)
    // Linux
#endif

6.2 iOS 与 Android 差异

差异项iOSAndroid
UI 线程主线程 + GCDUI 线程 + Handler
权限Info.plist 声明AndroidManifest.xml +
运行时权限请求(API >= 23)
推送APNsFirebase Cloud Messaging
内购StoreKitGoogle Play Billing
JNI不需要Java Native Interface

6.3 Android JNI 示例

// Android 专用:调用 Java 方法
#if defined(DM_PLATFORM_ANDROID)
#include <jni.h>

static int ShowToast(lua_State* L)
{
    const char* text = luaL_checkstring(L, 1);

    JNIEnv* env = 0;
    dmGraphics::GetNativeAndroidJavaVM()->AttachCurrentThread(&env, 0);

    jclass activity_class = env->FindClass("android/app/Activity");
    jmethodID method = env->GetMethodID(activity_class, "runOnUiThread",
        "(Ljava/lang/Runnable;)V");

    // 创建 Runnable 并执行 Toast
    // ... JNI 代码较复杂,这里仅示意

    dmGraphics::GetNativeAndroidJavaVM()->DetachCurrentThread();
    return 0;
}
#endif

6.4 HTML5 Emscripten 专用

#if defined(DM_PLATFORM_HTML5)
#include <emscripten.h>

static int GetBrowserInfo(lua_State* L)
{
    char buf[256];
    EM_ASM_({
        var ua = navigator.userAgent;
        stringToUTF8(ua, $0, 256);
    }, buf);

    lua_pushstring(L, buf);
    return 1;
}
#endif

7. SDK 构建与发布流程

7.1 本地构建测试

1. 开发 Extension,确保 ext.manifest 正确
2. Project → Build → 选目标平台
3. Defold 编辑器会自动:
   - 下载对应平台的 SDK/toolchain
   - 编译 C++ 源码
   - 链接静态库
   - 打包进游戏

7.2 命令行构建(CI/CD)

# 使用 bob.jar 命令行构建
java -jar bob.jar \
    --platform=armv7-android \
    --archive \
    --variant=release \
    resolve distclean build bundle

# 构建 iOS
java -jar bob.jar \
    --platform=arm64-ios \
    --identity="iPhone Distribution" \
    --mobileprovisioning=profile.mobileprovision \
    resolve distclean build bundle

7.3 发布到 Asset Portal

1. 将 Extension 上传到 GitHub 仓库
2. 包含 README、API 文档、示例项目
3. 在 Defold Asset Portal 提交
4. 用户可以 Project → Fetch Libraries → 输入 GitHub URL 使用

8. 常见问题与调试

问题原因解决
module 'MyExtension' not foundext.manifest name 不匹配检查 name 字段和 require 名称
链接错误 undefined symbol静态库平台不匹配确认 .a 编译目标与构建平台一致
iOS framework 缺失ext.manifest frameworks 未声明添加 Foundation/UIKit 等
Android 崩溃native 代码空指针用 dmLogInfo 打印日志排查
参数类型错误Lua 栈类型不匹配用 luaL_checkstring/checknumber
HTML5 构建失败Emscripten 版本不兼容更新到 Defold 推荐的 EMSDK 版本

日志输出:

// 在 C++ 中打印 Defold 日志
dmLogInfo("Info message: %s", str);    // 普通信息
dmLogWarning("Warning!");              // 警告
dmLogError("Error: %d", code);         // 错误

9. 速查表

需求方法说明
创建扩展新建 ext/ 目录 + ext.manifestname 与 require 一致
绑定函数luaL_register(L, name, methods)函数表 + init/final
读取字符串luaL_checkstring(L, index)index 从 1 开始
读取数字luaL_checknumber(L, index)double 类型
读取 tablelua_getfield(L, index, key)字段逐一读取
压入返回lua_pushnumber/L_pushstring(L, val)返回值压栈
返回值数量return nn = 栈上返回值个数
平台宏DM_PLATFORM_IOS/ANDROID/HTML5条件编译
静态库集成放 lib// + ext.manifest 声明文件名不含 lib 前缀
异步回调dmScript::SetupCallback + PCall传 Lua function 到 C
日志输出dmLogInfo/Warning/ErrorDefold 统一日志
构建bob.jar –platform=xxxCI/CD 用

一句话记忆:Defold Native Extension = ext.manifest 配置 + C++ 源码 + Lua C API 绑定(luaL_register/checkstring/pushstring);消息桥接用异步回调(dmScript::PCall);第三方库按平台放 .a + 声明 libs;平台差异用 DM_PLATFORM 宏 + 条件编译;调试靠 dmLogInfo + bob.jar 命令行构建。


相关阅读

  • /defold-script-system-lua/ — Lua 运行时与脚本系统
  • /defold-cross-platform-publish/ — 多平台打包与发布
  • /defold-performance-optimization/ — 性能优化(调用 C 的时机)

延伸阅读

  • /defold-iap-advertising/ — iOS/Android SDK 集成实战
  • /defold-input-system/ — 消息系统深度解析
  • /defold-save-serialization/ — 数据序列化
  • C++ 系统编程专题 — C++ 基础与 Lua C API
  • 游戏开发专题 — 游戏引擎扩展机制
-- ======================================
-- 完整可运行示例:Native Extension Lua 端调用
-- 假设扩展名 "MyExtension" 已编译
-- 放入 ext_demo.script
-- ======================================

-- 加载 C++ 扩展模块
local myext = require("MyExtension")

function init(self)
    -- 调用 C++ 绑定函数
    local greeting = myext.hello("Defold Developer")
    print("[init] C++ 返回:", greeting)

    local sum = myext.add(10, 32)
    print("[init] 10 + 32 =", sum)

    -- 传递 table 到 C
    local ok = myext.set_config({
        width = 1280,
        height = 720,
        title = "My Awesome Game"
    })
    print("[init] Config set:", ok)

    -- 请求设备信息(异步)
    myext.get_device_info(function(platform)
        print("[callback] 检测平台:", platform)
    end)
end

function update(self, dt)
    -- 帧更新中可以持续调用 C 扩展
    -- self.frame_count = (self.frame_count or 0) + 1
    -- if self.frame_count % 60 == 0 then
    --     print("FPS check from C++:", myext.get_fps())
    -- end
end

function on_message(self, message_id, message, sender)
    if message_id == hash("device_info_result") then
        print("[msg] 平台信息:", message.platform)
    end
end

function final(self)
    print("[final] Extension demo 结束")
end

继续阅读

探索更多技术文章

浏览归档,发现更多关于系统设计、工具链和工程实践的内容。

全部文章 返回首页

「defold」更多文章

  1. Defold 网络通信:HTTP、WebSocket 与 REST API
  2. Defold Render Scripts 与自定义渲染管线
  3. Defold Tilemap 碰撞与关卡设计