引言
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 是什么?何时需要它
- 2. ext.manifest 与目录结构
- 3. C/C++ 函数绑定到 Lua
- 4. 消息桥接:Lua ↔ C
- 5. 第三方库集成
- 6. 平台差异:iOS、Android、HTML5、Desktop
- 7. SDK 构建与发布流程
- 8. 常见问题与调试
- 9. 速查表
- 相关阅读
- 延伸阅读
1. Native Extension 是什么?何时需要它
Native Extension = 用 C/C++ 写的插件,通过 Lua C API 暴露给 Defold 脚本使用。
必须用到 NE 的场景:
| 场景 | 说明 |
|---|---|
| 平台原生 API | iOS Push、Android 振动、设备信息读取 |
| 第三方 SDK | Firebase、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 后缀) |
| frameworks | iOS/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 差异
| 差异项 | iOS | Android |
|---|---|---|
| UI 线程 | 主线程 + GCD | UI 线程 + Handler |
| 权限 | Info.plist 声明 | AndroidManifest.xml + |
| 运行时权限请求(API >= 23) | ||
| 推送 | APNs | Firebase Cloud Messaging |
| 内购 | StoreKit | Google 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 found | ext.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.manifest | name 与 require 一致 |
| 绑定函数 | luaL_register(L, name, methods) | 函数表 + init/final |
| 读取字符串 | luaL_checkstring(L, index) | index 从 1 开始 |
| 读取数字 | luaL_checknumber(L, index) | double 类型 |
| 读取 table | lua_getfield(L, index, key) | 字段逐一读取 |
| 压入返回 | lua_pushnumber/L_pushstring(L, val) | 返回值压栈 |
| 返回值数量 | return n | n = 栈上返回值个数 |
| 平台宏 | DM_PLATFORM_IOS/ANDROID/HTML5 | 条件编译 |
| 静态库集成 | 放 lib/ | 文件名不含 lib 前缀 |
| 异步回调 | dmScript::SetupCallback + PCall | 传 Lua function 到 C |
| 日志输出 | dmLogInfo/Warning/Error | Defold 统一日志 |
| 构建 | bob.jar –platform=xxx | CI/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
继续阅读
探索更多技术文章
浏览归档,发现更多关于系统设计、工具链和工程实践的内容。