在 Cocos Creator 中的 Swig 工作流教程

    • 拷贝 swig-interface-template.i 文件中的内容到 new-engine-module.i

    • 添加必要的配置,可以参考 native/tools/swig-config 目录下现有的 .i 文件配置,或者参考。

    修改 swig-config.js 文件

    生成绑定代码

    1. cd engine/native/tools/swig-config
    2. node genbindings.js

    修改 engine/native/cocos/CMakeLists.txt

    1. ######## auto
    2. cocos_source_files(
    3. NO_WERROR NO_UBUILD cocos/bindings/auto/jsb_new_engine_module_auto.cpp # 添加此行
    4. cocos/bindings/auto/jsb_new_engine_module_auto.h # 添加此行
    5. NO_WERROR NO_UBUILD cocos/bindings/auto/jsb_cocos_auto.cpp
    6. cocos/bindings/auto/jsb_cocos_auto.h
    7. ......

    打开 jsb_module_register.cpp ,做如下修改

    1. ......
    2. #if CC_USE_PHYSICS_PHYSX
    3. #include "cocos/bindings/auto/jsb_physics_auto.h"
    4. #endif
    5. #include "cocos/bindings/auto/jsb_new_engine_module_auto.h" // 添加此行
    6. bool jsb_register_all_modules() {
    7. se::ScriptEngine *se = se::ScriptEngine::getInstance();
    8. ......
    9. se->addRegisterCallback(register_all_my_new_engine_module); // 添加此行
    10. se->addAfterCleanupHook([]() {
    11. cc::DeferredReleasePool::clear();
    12. JSBClassType::cleanup();
    13. });
    14. return true;
    15. }

    如何为开发者的项目绑定一个新模块

    假定我们已经有一个 Cocos Creator 的工程,其位于 /Users/james/NewProject 目录下。

    打开 Cocos Creator 的构建面板,构建出一个原生平台的工程,会生成 /Users/james/NewProject/native 目录。

    绑定一个简单的类

    创建一个简单类

    创建一个头文件,其位于 /Users/james/NewProject/native/engine/Classes/MyObject.h , 其内容为:

    1. // MyObject.h
    2. #pragma once
    3. #include "cocos/cocos.h"
    4. namespace my_ns {
    5. class MyObject {
    6. public:
    7. MyObject() = default;
    8. MyObject(int a, bool b) {}
    9. virtual ~MyObject() = default;
    10. void print() {
    11. CC_LOG_DEBUG("==> a: %d, b: %d\n", _a, (int)_b);
    12. }
    13. float publicFloatProperty{1.23F};
    14. private:
    15. int _a{100};
    16. bool _b{true};
    17. };
    18. } // namespace my_ns {

    编写一个 Swig 接口文件

    创建一个名称为 my-module.i 的接口文件,其位于 /Users/james/NewProject/tools/swig-config目录下。

    1. // my-module.i
    2. %module(target_namespace="my_ns") my_module
    3. // %insert(header_file) %{ ... }%} 代码块中的内容最终会被原封不动地插入到生成的头文件(.h)开头的地方
    4. %insert(header_file) %{
    5. #pragma once
    6. #include "bindings/jswrapper/SeApi.h"
    7. #include "bindings/manual/jsb_conversions.h"
    8. #include "MyObject.h" // 添加这行,%include 指令表示让 swig 解析此文件,并且为此文件中的类生成绑定代码。
    9. %}
    10. // %{ ... %} 代码块中的内容最终会被原封不动地插入到生成的源文件(.cpp)开头的地方
    11. %{
    12. #include "bindings/auto/jsb_my_module_auto.h"
    13. %}
    14. %include "MyObject.h"

    编写一个 Swig 配置文件(swig-config.js)

    创建一个名为 swig-config.js 的文件,例如: /Users/james/NewProject/tools/swig-config目录下。

    1. // swig-config.js
    2. 'use strict';
    3. const path = require('path');
    4. const configList = [
    5. [ 'my-module.i', 'jsb_my_module_auto.cpp' ],
    6. ];
    7. const projectRoot = path.resolve(path.join(__dirname, '..', '..'));
    8. const interfacesDir = path.join(projectRoot, 'tools', 'swig-config');
    9. const bindingsOutDir = path.join(projectRoot, 'native', 'engine', 'common', 'bindings', 'auto');
    10. // includeDirs 意思是 swig 执行时候使用的头文件搜索路径
    11. const includeDirs = [
    12. path.join(projectRoot, 'native', 'engine', 'common', 'Classes'),
    13. ];
    14. module.exports = {
    15. interfacesDir,
    16. bindingsOutDir,
    17. includeDirs,
    18. configList
    19. };

    为项目生成自动绑定文件

    1. cd /Users/james/NewProject/tools/swig-config
    2. node < 引擎根目录 >/native/tools/swig-config/genbindings.js

    如果成功,包含自动绑定代码的 jsb_my_module_auto.cpp/.h 两个文件将被创建到 /Users/james/NewProject/native/engine/bindings/auto 目录下。

    修改项目的 CMakeLists.txt 文件

    • 打开 /Users/james/NewProject/native/engine/common/CMakeLists.txt, 添加 MyObject.h 和自动绑定代码文件

      1. include(${COCOS_X_PATH}/CMakeLists.txt)
      2. list(APPEND CC_COMMON_SOURCES
      3. ${CMAKE_CURRENT_LIST_DIR}/Classes/Game.h
      4. ${CMAKE_CURRENT_LIST_DIR}/Classes/Game.cpp
      5. ############### 添加下面几行 ##############
      6. ${CMAKE_CURRENT_LIST_DIR}/Classes/MyObject.h
      7. ${CMAKE_CURRENT_LIST_DIR}/bindings/auto/jsb_my_module_auto.h
      8. ${CMAKE_CURRENT_LIST_DIR}/bindings/auto/jsb_my_module_auto.cpp
      9. ########################################################
      10. )
    • 修改 /Users/james/NewProject/native/engine/mac/CMakeLists.txt

      1. cmake_minimum_required(VERSION 3.8)
      2. # ......
      3. cc_mac_before_target(${EXECUTABLE_NAME})
      4. add_executable(${EXECUTABLE_NAME} ${CC_ALL_SOURCES})
      5. ############### 添加下面几行 ##############
      6. target_include_directories(${EXECUTABLE_NAME} PRIVATE
      7. ${CC_PROJECT_DIR}/../common
      8. )
      9. ########################################################
      10. cc_mac_after_target(${EXECUTABLE_NAME})

    打开项目工程

    macOS: /Users/james/NewProject/build/mac/proj/NewProject.xcodeproj

    Windows: < 一个存放项目的目录 >/NewProject/build/win64/proj/NewProject.sln

    为脚本引擎注册新的模块

    修改 Game.cpp

    1. #include "Game.h"
    2. #include "bindings/auto/jsb_my_module_auto.h" // 添加此行
    3. //......
    4. int Game::init() {
    5. // ......
    6. se::ScriptEngine::getInstance()->addRegisterCallback(register_all_my_module); // 添加此行
    7. BaseGame::init();
    8. return 0;
    9. }
    10. // ......

    测试绑定

    • 在项目的根目录下添加一个 my-module.d.ts 文件,使 TS 编译器识别我们的绑定类型

      1. // my-module.d.ts
      2. declare namespace my_ns {
      3. class MyObject {
      4. constructor();
      5. constructor(a: number, b: number);
      6. publicFloatProperty : number;
      7. print() : void;
      8. }
      9. }
    • 修改 /Users/james/NewProject/temp/tsconfig.cocos.json 文件

      1. {
      2. "$schema": "https://json.schemastore.org/tsconfig",
      3. "compilerOptions": {
      4. "target": "ES2015",
      5. "module": "ES2015",
      6. "strict": true,
      7. "types": [
      8. "./temp/declarations/cc.custom-macro",
      9. "./temp/declarations/jsb",
      10. "./temp/declarations/cc",
      11. "./temp/declarations/cc.env",
      12. "./my-module" // 添加这行
      13. ],
      14. // ......
      15. "forceConsistentCasingInFileNames": true
      16. }
      17. }
    • 在 Cocos Creator 中打开 NewProject 项目, 在场景中添加一个立方体,并且添加一个脚本组件到这个立方体上,脚本的内容是:

      1. import { _decorator, Component } from 'cc';
      2. const { ccclass } = _decorator;
      3. @ccclass('MyComponent')
      4. export class MyComponent extends Component {
      5. start() {
      6. const myObj = new my_ns.MyObject();
      7. myObj.print(); // 调用原生的 print 方法
      8. console.log(`==> myObj.publicFloatProperty: ${myObj.publicFloatProperty}`); // 获取原生中定义的属性值
      9. }
      10. }
    • 在 Xcode 或者 Visual Studio 中运行项目, 如果成功,可以看到如下日志输出

      1. 17:31:44 [DEBUG]: ==> a: 100, b: 1
      2. 17:31:44 [DEBUG]: D/ JS: ==> myObj.publicFloatProperty: 1.2300000190734863

    本节总结

    在此节中,我们学会了如何使用 Swig 工具绑定一个简单的 C++ 类,并把它的公有方法与属性导出到 JS 中。从下一节开始,我们将更多地关注如何使用 Swig 的一些特性来满足各式各样的 JS 绑定需求,例如:

    • 如何使用 %import 指令导入头文件依赖?
    • 如何忽略绑定某些特殊的类、方法或属性?
    • 如何重命名类、方法或属性?
    • 如何将 C++ 的 getter 和 setter 函数绑定为 JS 属性?
    • 如何配置 C++ 模块宏

    导入头文件依赖

    假定我们让 MyObject 类继承于 MyRef 类。但是我们并不想绑定 MyRef 类型。

    1. // MyRef.h
    2. #pragma once
    3. namespace my_ns {
    4. class MyRef {
    5. public:
    6. MyRef() = default;
    7. virtual ~MyRef() = default;
    8. void addRef() { _ref++; }
    9. void release() { --_ref; }
    10. private:
    11. unsigned int _ref{0};
    12. };
    13. } // namespace my_ns {
    1. // MyObject.h
    2. #pragma once
    3. #include "cocos/cocos.h"
    4. #include "MyRef.h"
    5. namespace my_ns {
    6. // MyObject 继承于 MyRef
    7. class MyObject : public MyRef {
    8. public:
    9. MyObject() = default;
    10. MyObject(int a, bool b) {}
    11. virtual ~MyObject() = default;
    12. void print() {
    13. CC_LOG_DEBUG("==> a: %d, b: %d\n", _a, (int)_b);
    14. }
    15. float publicFloatProperty{1.23F};
    16. private:
    17. int _a{100};
    18. bool _b{true};
    19. };
    20. } // namespace my_ns {

    当 Swig 解析 MyObject.h 的时候, 它并不知道 MyRef 是什么, 因此它会在终端输出一个警告信息。

    1. .../Classes/MyObject.h:7: Warning 401: Nothing known about base class 'MyRef'. Ignored.

    要解决此警告也容易,我们只需要使用 %import 指令让 Swig 知道 MyRef 类的存在即可。

    1. // ......
    2. // Insert code at the beginning of generated source file (.cpp)
    3. %{
    4. #include "bindings/auto/jsb_my_module_auto.h"
    5. %}
    6. %import "MyRef.h" // 添加此行
    7. %include "MyObject.h"

    尽管 Swig 不再报错了,但是生成的代码却无法编译通过,会出现如下报错:

    我们将在下一节中使用 %ignore 指令来修复此问题。

    忽略某些类、方法或属性

    忽略某些类

    1. // my-module.i
    2. // ......
    3. %ignore my_ns::MyRef; // 添加此行
    4. %import "MyRef.h"
    5. %include "MyObject.h"

    重新生成绑定,现在应该可以编译通过了。

    1. // jsb_my_module_auto.cpp
    2. auto* cls = se::Class::create("MyObject", obj, nullptr, _SE(js_new_MyObject)); // parentProto will be set to nullptr
    3. cls->defineProperty("publicFloatProperty", _SE(js_my_ns_MyObject_publicFloatProperty_get), _SE(js_my_ns_MyObject_publicFloatProperty_set));
    4. cls->defineFunction("print", _SE(js_my_ns_MyObject_print));
    5. // ......
    6. }

    忽略某些方法和属性

    我们为 MyObject 类添加一个名为 methodToBeIgnored 的方法,再添加一个名为 propertyToBeIgnored 的属性。

    1. // MyObject.h
    2. #pragma once
    3. #include "cocos/cocos.h"
    4. #include "MyRef.h"
    5. namespace my_ns {
    6. // MyObject 继承于 MyRef
    7. class MyObject : public MyRef {
    8. public:
    9. // .....
    10. void methodToBeIgnored() {} // 添加此行
    11. float propertyToBeIgnored{345.123F}; // 添加此行
    12. // ......
    13. float publicFloatProperty{1.23F};
    14. private:
    15. int _a{100};
    16. bool _b{true};
    17. };
    18. } // namespace my_ns {

    重新生成绑定, 我们可以发现 methodToBeIgnoredpropertyToBeIgnored 的绑定代码已经被自动生成。

    修改 my-module.i以忽略绑定这个方法与属性。

    1. // my-module.i
    2. // ......
    3. %ignore my_ns::MyObject::methodToBeIgnored; // 添加此行
    4. %ignore my_ns::MyObject::propertyToBeIgnored; // 添加此行
    5. %import "MyRef.h"
    6. %include "MyObject.h"

    重新生成绑定,它们将被忽略。

    1. // jsb_my_module_auto.cpp
    2. bool js_register_my_ns_MyObject(se::Object* obj) {
    3. auto* cls = se::Class::create("MyObject", obj, nullptr, _SE(js_new_MyObject));
    4. cls->defineProperty("publicFloatProperty", _SE(js_my_ns_MyObject_publicFloatProperty_get), _SE(js_my_ns_MyObject_publicFloatProperty_set));
    5. cls->defineFunction("print", _SE(js_my_ns_MyObject_print));
    6. // ......
    7. }

    Swig 定义了一个名为 %rename 指令用于重命名类、方法或者属性。我们继续使用 MyObject 类来展示。

    1. // MyObject.h
    2. #pragma once
    3. #include "cocos/cocos.h"
    4. #include "MyRef.h"
    5. namespace my_ns {
    6. // MyObject 继承于 MyRef
    7. class MyObject : public MyRef {
    8. public:
    9. // ......
    10. void methodToBeRenamed() { // 添加此方法
    11. CC_LOG_DEBUG("==> hello MyObject::methodToBeRenamed");
    12. }
    13. int propertyToBeRenamed{1234}; // 添加此属性
    14. float publicFloatProperty{1.23F};
    15. private:
    16. int _a{100};
    17. bool _b{true};
    18. };
    19. } // namespace my_ns {

    重新生成绑定,我们发现 methodToBeRenamedpropertyToBeRenamed 的绑定代码已经被生成:

    1. // jsb_my_module_auto.cpp
    2. bool js_register_my_ns_MyObject(se::Object* obj) {
    3. auto* cls = se::Class::create("MyObject", obj, nullptr, _SE(js_new_MyObject));
    4. cls->defineProperty("propertyToBeRenamed", _SE(js_my_ns_MyObject_propertyToBeRenamed_get), _SE(js_my_ns_MyObject_propertyToBeRenamed_set));
    5. cls->defineProperty("publicFloatProperty", _SE(js_my_ns_MyObject_publicFloatProperty_get), _SE(js_my_ns_MyObject_publicFloatProperty_set));
    6. cls->defineFunction("print", _SE(js_my_ns_MyObject_print));
    7. cls->defineFunction("methodToBeRenamed", _SE(js_my_ns_MyObject_methodToBeRenamed));

    如果我们要重命名 propertyToBeRenamedcoolProperty ,重命名 methodToBeRenamedcoolMethod,那么按照下面的方式修改 my-module.i

    1. // my-module.i
    2. // ......
    3. %ignore my_ns::MyRef;
    4. %ignore my_ns::MyObject::methodToBeIgnored;
    5. %ignore my_ns::MyObject::propertyToBeIgnored;
    6. %rename(coolProperty) my_ns::MyObject::propertyToBeRenamed; // 添加此行
    7. %rename(coolMethod) my_ns::MyObject::methodToBeRenamed; // 添加此行
    8. %import "MyRef.h"
    9. %include "MyObject.h"

    如果我们还想把 MyObject 类重命名为 MyCoolObject,我猜想你已经知道如何做了吧。没错,只要添加这行:

    1. %rename(MyCoolObject) my_ns::MyObject;

    重新生成绑定代码,所有需要被重命名的类、方法与属性都按照我们的意愿被重命名了。

    1. // jsb_my_module_auto.cpp
    2. // MyCoolObject, coolProperty, coolMethod are all what we want now.
    3. bool js_register_my_ns_MyObject(se::Object* obj) {
    4. auto* cls = se::Class::create("MyCoolObject", obj, nullptr, _SE(js_new_MyCoolObject));
    5. cls->defineProperty("coolProperty", _SE(js_my_ns_MyCoolObject_coolProperty_get), _SE(js_my_ns_MyCoolObject_coolProperty_set));
    6. cls->defineProperty("publicFloatProperty", _SE(js_my_ns_MyCoolObject_publicFloatProperty_get), _SE(js_my_ns_MyCoolObject_publicFloatProperty_set));
    7. cls->defineFunction("print", _SE(js_my_ns_MyCoolObject_print));
    8. cls->defineFunction("coolMethod", _SE(js_my_ns_MyCoolObject_coolMethod));
    9. // ......
    10. }

    现在测试一下吧,更新如下文件 my-module.d.ts and MyComponent.ts

    1. // my-module.d.ts
    2. declare namespace my_ns {
    3. class MyCoolObject {
    4. constructor();
    5. constructor(a: number, b: number);
    6. publicFloatProperty : number;
    7. print() : void;
    8. coolProperty: number;
    9. coolMethod() : void;
    10. }
    11. }
    1. // MyComponent.ts
    2. import { _decorator, Component } from 'cc';
    3. const { ccclass } = _decorator;
    4. @ccclass('MyComponent')
    5. export class MyComponent extends Component {
    6. start() {
    7. const myObj = new my_ns.MyCoolObject(); // 这里改为 MyCoolObject,因为我们在前面重命名了
    8. myObj.print();
    9. console.log(`==> myObj.publicFloatProperty: ${myObj.publicFloatProperty}`);
    10. // Add the follow lines
    11. console.log(`==> old: myObj.coolProperty: ${myObj.coolProperty}`);
    12. myObj.coolProperty = 666;
    13. console.log(`==> new: myObj.coolProperty: ${myObj.coolProperty}`);
    14. myObj.coolMethod();
    15. }
    16. }

    在 Xcode 或者 Visual Studio 中运行项目,将得到如下日志:

    1. 17:53:28 [DEBUG]: ==> a: 100, b: 1
    2. 17:53:28 [DEBUG]: D/ JS: ==> myObj.publicFloatProperty: 1.2300000190734863
    3. 17:53:28 [DEBUG]: D/ JS: ==> old: myObj.coolProperty: 1234
    4. 17:53:28 [DEBUG]: D/ JS: ==> new: myObj.coolProperty: 666
    5. 17:53:28 [DEBUG]: ==> hello MyObject::methodToBeRenamed

    定义一个 attribute

    %attribute 指令用于把 C++ 的 gettersetter 函数绑定为一个 JS 属性。

    用法

    1. 定义一个没有 setter 函数的 JS 属性,即只读的 JS 属性。

      1. %attribute(your_namespace::your_class_name, cpp_member_variable_type, js_property_name, cpp_getter_function_name)
    2. 定义一个有 gettersetter 函数的 JS 属性,即可读可写的 JS 属性。

      1. %attribute(your_namespace::your_class_name, cpp_member_variable_type, js_property_name, cpp_getter_function_name, cpp_setter_function_name)
    3. 定义一个没有 getter 的 JS 属性,即可写不可读的 JS 属性。

      1. %attribute_writeonly(your_namespace::your_class_name, cpp_member_variable_type, js_property_name, cpp_setter_function_name)

    示例

    为了方便演示,我们为 MyObject 添加两个新方法:setTypegetType

    1. // MyObject.h
    2. #pragma once
    3. #include "cocos/cocos.h"
    4. #include "MyRef.h"
    5. namespace my_ns {
    6. // MyObject 继承于 MyRef
    7. class MyObject : public MyRef {
    8. public:
    9. // ......
    10. void setType(int v) { _type = v; CC_LOG_DEBUG("==> setType: v: %d", v); } // 添加此行
    11. int getType() const { return _type; } // 添加此行
    12. float publicFloatProperty{1.23F};
    13. private:
    14. int _a{100};
    15. bool _b{true};
    16. int _type{333};
    17. };
    18. } // namespace my_ns {
    1. // my-module.i
    2. // ......
    3. %attribute(my_ns::MyObject, int, type, getType, setType); // 添加此行
    4. %import "MyRef.h"
    5. %include "MyObject.h"
    1. // jsb_my_module_auto.cpp
    2. bool js_register_my_ns_MyObject(se::Object* obj) {
    3. // ......
    4. cls->defineProperty("type", _SE(js_my_ns_MyCoolObject_type_get), _SE(js_my_ns_MyCoolObject_type_set));
    5. // ......
    6. }
    1. // MyComponent.ts
    2. import { _decorator, Component } from 'cc';
    3. const { ccclass } = _decorator;
    4. @ccclass('MyComponent')
    5. export class MyComponent extends Component {
    6. start() {
    7. const myObj = new my_ns.MyCoolObject();
    8. myObj.print();
    9. console.log(`==> myObj.publicFloatProperty: ${myObj.publicFloatProperty}`);
    10. console.log(`==> old: myObj.coolProperty: ${myObj.coolProperty}`);
    11. myObj.coolProperty = 666;
    12. console.log(`==> new: myObj.coolProperty: ${myObj.coolProperty}`);
    13. myObj.coolMethod();
    14. console.log(`==> old: myObj.type: ${myObj.type}`);
    15. myObj.type = 888;
    16. console.log(`==> new: myObj.type: ${myObj.type}`);
    17. }
    18. }

    在 Xcode 或者 Visual Studio 中运行项目:

    1. 18:09:53 [DEBUG]: ==> a: 100, b: 1
    2. 18:09:53 [DEBUG]: D/ JS: ==> myObj.publicFloatProperty: 1.2300000190734863
    3. 18:09:53 [DEBUG]: D/ JS: ==> old: myObj.coolProperty: 1234
    4. 18:09:53 [DEBUG]: D/ JS: ==> new: myObj.coolProperty: 666
    5. 18:09:53 [DEBUG]: ==> hello MyObject::methodToBeRenamed
    6. 18:09:53 [DEBUG]: D/ JS: ==> old: myObj.type: 333
    7. 18:09:53 [DEBUG]: ==> setType: v: 888 // Cool, C++ setType is invoked
    8. 18:09:53 [DEBUG]: D/ JS: ==> new: myObj.type: 888 // Cool, C++ getType is invoked, 888 is return from C++

    %attribute_writeonly 指令

    %attribute_writeonly 指令是我们为 swig Cocos 后端添加的一个扩展指令,它用于 C++ 只有 setter 函数没有 getter 函数的情况。

    例如在 native/tools/swig-config/cocos.i 中有如下定义:

    1. %attribute_writeonly(cc::ICanvasRenderingContext2D, float, width, setWidth);
    2. %attribute_writeonly(cc::ICanvasRenderingContext2D, float, height, setHeight);
    3. %attribute_writeonly(cc::ICanvasRenderingContext2D, float, lineWidth, setLineWidth);
    4. %attribute_writeonly(cc::ICanvasRenderingContext2D, ccstd::string&, fillStyle, setFillStyle);
    5. %attribute_writeonly(cc::ICanvasRenderingContext2D, ccstd::string&, font, setFont);

    它的作用类似于 JS 中如下代码:

    1. Object.defineProperty(MyNewClass.prototype, 'width', {
    2. configurable: true,
    3. enumerable: true,
    4. set(v) {
    5. this._width = v;
    6. },
    7. // No get() for property
    8. });

    关于引用类型

    如果 C++ 的 get 函数返回的是一个引用数据类型或者 set 函数接受一个引用数据类型,别忘记在 %attribute 或 %attribute_writeonly 指令的编写中添加 & 后缀。以下 ccstd::string& 是一个例子:

    1. %attribute_writeonly(cc::ICanvasRenderingContext2D, ccstd::string&, fillStyle, setFillStyle);

    如果 & 没有被添加,当绑定函数被调用的时候,一个临时的 ccstd::string 实例将被创建。这种临时对象的创建与销毁是不划算的且是可以完全避免的。

    %arg() 指令

    有时候 C++ 变量的类型是用模版的方式来修饰的,例如:

    1. %attribute(MyNewClass, std::map<std::string, std::string>&, config, getConfig, setConfig);

    但当你执行 node genbindings.js的时候,你将得到如下错误:

    1. Error: Macro '%attribute_custom' expects 7 arguments

    这是因为 swig 看到 std::map<std::string, std::string>& 的时候并不知道如何处理逗号 (,) ,它将其分割为两部分,即:

    1. std::map<std::string
    2. std::string>&

    因此, %attribute 指令这行将被解析为 6 个参数,而不是正确的 5 个参数。

    为了避免这种情况出现,我们需要使用 %arg 指令来告诉 swig std::map<std::string, std::string>& 是一个整体。

    1. %attribute(MyNewClass, %arg(std::map<std::string, std::string>&), config, getConfig, setConfig);

    重新执行 node genbindings.js,之前的错误即消失了。

    不要添加 const

    在上一示例中,我们在 %attribute 指令中使用 %arg(std::map<std::string, std::string>&)。你可能会考虑在 std::map 前面添加一个 const 前缀,比如:%arg(const std::map<std::string, std::string>&)。如果你这样做了,你将添加一个 只读的、只绑定 MyNewClass::getConfigconfig 属性。这明显不是我们所期望的。如果我们需要属性是只读的,只需要不配置 setter 函数即可。

    1. // 不配置 setConfig 意味着属性是只读的
    2. %attribute(MyNewClass, %arg(std::map<std::string, std::string>&), config, getConfig);

    因此,为了让事情简单化,我们只要记得,永远不要在定义 %attribute 的时候为 C++ 变量类型使用 const 前缀

    配置 C++ 模块宏(用于 C++ 模块裁剪)

    有时候是否需要让一个类参与编译依赖于某个宏是否启用。比如,我们在 MyObject.h 文件中添加一个 MyFeatureObject 类:

    1. // MyObject.h
    2. #pragma once
    3. #include "cocos/cocos.h"
    4. #include "MyRef.h"
    5. #ifndef USE_MY_FEATURE
    6. #define USE_MY_FEATURE 1 // Enable USE_MY_FEATURE
    7. #endif
    8. namespace my_ns {
    9. #if USE_MY_FEATURE
    10. class MyFeatureObject {
    11. public:
    12. void foo() {
    13. CC_LOG_DEBUG("==> MyFeatureObject::foo");
    14. }
    15. };
    16. #else
    17. class MyFeatureObject;
    18. #endif
    19. // MyObject 继承于 MyRef
    20. class MyObject : public MyRef {
    21. public:
    22. //......
    23. MyFeatureObject* getFeatureObject() {
    24. #if USE_MY_FEATURE // getFeatureObject 只在宏 USE_MY_FEATURE 启用的情况下返回有效值
    25. if (_featureObject == nullptr) {
    26. _featureObject = new MyFeatureObject();
    27. }
    28. #endif
    29. return _featureObject;
    30. }
    31. private:
    32. int _a{100};
    33. bool _b{true};
    34. int _type{333};
    35. };
    36. } // namespace my_ns {
    1. // my-module.i
    2. // ......
    3. %rename(MyCoolObject) my_ns::MyObject;
    4. %attribute(my_ns::MyObject, int, type, getType, setType);
    5. %module_macro(USE_MY_FEATURE) my_ns::MyFeatureObject; // 添加此行,用于让 Swig 知道生成出来的 MyFeatureObject 类的绑定代码需要被包在 USE_MY_FEATURE 下
    6. %module_macro(USE_MY_FEATURE) my_ns::MyObject::getFeatureObject; // 添加此行,用于让 Swig 知道生成出来的 MyObject::getFeatureObject 方法的绑定代码需要被包在 USE_MY_FEATURE 下
    7. #define USE_MY_FEATURE 1 // 这里定义为 1 是骗过 Swig,让它帮我们生成绑定代码。注意,这行必须在 %module_macro 之后
    8. %import "MyRef.h"
    9. %include "MyObject.h"
    1. // my-module.d.ts
    2. declare namespace my_ns {
    3. class MyFeatureObject {
    4. foo() : void;
    5. }
    6. class MyCoolObject {
    7. constructor();
    8. constructor(a: number, b: number);
    9. publicFloatProperty : number;
    10. print() : void;
    11. coolProperty: number;
    12. coolMethod() : void;
    13. type: number;
    14. getFeatureObject() : MyFeatureObject;
    15. }
    16. }
    1. // MyComponent.ts
    2. import { _decorator, Component } from 'cc';
    3. const { ccclass } = _decorator;
    4. @ccclass('MyComponent')
    5. export class MyComponent extends Component {
    6. start() {
    7. const myObj = new my_ns.MyCoolObject();
    8. myObj.print();
    9. console.log(`==> myObj.publicFloatProperty: ${myObj.publicFloatProperty}`);
    10. console.log(`==> old: myObj.coolProperty: ${myObj.coolProperty}`);
    11. myObj.coolProperty = 666;
    12. console.log(`==> new: myObj.coolProperty: ${myObj.coolProperty}`);
    13. myObj.coolMethod();
    14. console.log(`==> old: myObj.type: ${myObj.type}`);
    15. myObj.type = 888;
    16. console.log(`==> new: myObj.type: ${myObj.type}`);
    17. const featureObj = myObj.getFeatureObject();
    18. console.log(`==> featureObj: ${featureObj}`);
    19. if (featureObj) {
    20. featureObj.foo();
    21. }
    22. }
    23. }

    重新生成绑定代码,自动绑定代码如下:

    1. #if USE_MY_FEATURE // 注意,现在所有 MyFeatureObject 相关的绑定代码都被包在 USE_MY_FEATURE 宏下面了。
    2. se::Class* __jsb_my_ns_MyFeatureObject_class = nullptr;
    3. se::Object* __jsb_my_ns_MyFeatureObject_proto = nullptr;
    4. SE_DECLARE_FINALIZE_FUNC(js_delete_my_ns_MyFeatureObject)
    5. static bool js_my_ns_MyFeatureObject_foo(se::State& s)
    6. {
    7. // ......
    8. }
    9. // ......
    10. bool js_register_my_ns_MyFeatureObject(se::Object* obj) {
    11. auto* cls = se::Class::create("MyFeatureObject", obj, nullptr, _SE(js_new_my_ns_MyFeatureObject));
    12. // ......
    13. }
    14. #endif // USE_MY_FEATURE
    15. // ......
    16. static bool js_my_ns_MyCoolObject_getFeatureObject(se::State& s)
    17. {
    18. #if USE_MY_FEATURE // getFeatureObject 函数的绑定代码也被包在 USE_MY_FEATURE 宏下面了。
    19. // ......
    20. ok &= nativevalue_to_se(result, s.rval(), s.thisObject() /*ctx*/);
    21. SE_PRECONDITION2(ok, false, "MyCoolObject_getFeatureObject, Error processing arguments");
    22. SE_HOLD_RETURN_VALUE(result, s.thisObject(), s.rval());
    23. #endif // USE_MY_FEATURE
    24. return true;
    25. }
    26. SE_BIND_FUNC(js_my_ns_MyCoolObject_getFeatureObject)
    27. // ......
    28. bool register_all_my_module(se::Object* obj) {
    29. // Get the ns
    30. se::Value nsVal;
    31. if (!obj->getProperty("my_ns", &nsVal, true))
    32. {
    33. se::HandleObject jsobj(se::Object::createPlainObject());
    34. nsVal.setObject(jsobj);
    35. obj->setProperty("my_ns", nsVal);
    36. }
    37. se::Object* ns = nsVal.toObject();
    38. /* Register classes */
    39. #if USE_MY_FEATURE
    40. js_register_my_ns_MyFeatureObject(ns); // js_register_my_ns_MyFeatureObject 也被包在 USE_MY_FEATURE 宏下面了。
    41. #endif // USE_MY_FEATURE
    42. js_register_my_ns_MyObject(ns);
    43. return true;
    44. }

    在 Xcode 或者 Visual Studio 中运行项目,会得到如下输出:

    1. 18:32:20 [DEBUG]: D/ JS: ==> featureObj: [object Object] // 在 USE_MY_FEATURE 启用的情况下,featureObj 是个有效值
    2. 18:32:20 [DEBUG]: ==> MyFeatureObject::foo // 调用 C++ foo 方法

    当我们不需要 MyFeatureObject 类的时候,把宏设置为 0 即可,代码示例如下:

    1. // MyObject.h
    2. #pragma once
    3. #include "cocos/cocos.h"
    4. #include "MyRef.h"
    5. #ifndef USE_MY_FEATURE
    6. #define USE_MY_FEATURE 0 // Disable USE_MY_FEATURE
    7. #endif

    在 Xcode 或者 Visual Studio 中运行项目:

    1. 18:54:00 [DEBUG]: D/ JS: ==> featureObj: undefined // getFeatureObject returns undefined if USE_MY_FEATURE is disabled.

    多个 Swig 模块的配置

    我们创建另外一个头文件,名为 MyAnotherObject.h

    1. // MyAnotherObject.h
    2. #pragma once
    3. namespace my_another_ns {
    4. struct MyAnotherObject {
    5. float a{135.246};
    6. int b{999};
    7. };
    8. } // namespace my_another_ns {

    更新 MyObject.h

    1. // MyObject.h
    2. //......
    3. class MyObject : public MyRef {
    4. public:
    5. // ......
    6. void helloWithAnotherObject(const my_another_ns::MyAnotherObject &obj) {
    7. CC_LOG_DEBUG("==> helloWithAnotherObject, a: %f, b: %d", obj.a, obj.b);
    8. }
    9. // ......
    10. };
    11. } // namespace my_ns {

    创建 /Users/james/NewProject/tools/swig-config/another-module.i

    1. // another-module.i
    2. %module(target_namespace="another_ns") another_module
    3. // %insert(header_file) %{ ... }%} 代码块中的内容最终会被原封不动地插入到生成的头文件(.h)开头的地方
    4. %insert(header_file) %{
    5. #pragma once
    6. #include "bindings/jswrapper/SeApi.h"
    7. #include "bindings/manual/jsb_conversions.h"
    8. #include "MyAnotherObject.h" // 添加此行
    9. %}
    10. // %{ ... %} 代码块中的内容最终会被原封不动地插入到生成的源文件(.cpp)开头的地方
    11. %{
    12. #include "bindings/auto/jsb_another_module_auto.h"
    13. %}
    14. %include "MyAnotherObject.h"

    修改 /Users/james/NewProject/tools/swig-config/swig-config.js

    1. 'use strict';
    2. const path = require('path');
    3. const configList = [
    4. [ 'my-module.i', 'jsb_my_module_auto.cpp' ],
    5. [ 'another-module.i', 'jsb_another_module_auto.cpp' ], // 添加此行
    6. ];
    7. const projectRoot = path.resolve(path.join(__dirname, '..', '..'));
    8. const interfacesDir = path.join(projectRoot, 'tools', 'swig-config');
    9. const bindingsOutDir = path.join(projectRoot, 'native', 'engine', 'common', 'bindings', 'auto');
    10. const includeDirs = [
    11. path.join(projectRoot, 'native', 'engine', 'common', 'Classes'),
    12. ];
    13. module.exports = {
    14. interfacesDir,
    15. bindingsOutDir,
    16. includeDirs,
    17. configList
    18. };

    修改 /Users/james/NewProject/native/engine/common/CMakeLists.txt

    1. # /Users/james/NewProject/native/engine/common/CMakeLists.txt
    2. list(APPEND CC_COMMON_SOURCES
    3. ${CMAKE_CURRENT_LIST_DIR}/Classes/Game.h
    4. ${CMAKE_CURRENT_LIST_DIR}/Classes/Game.cpp
    5. ${CMAKE_CURRENT_LIST_DIR}/Classes/MyObject.h
    6. ${CMAKE_CURRENT_LIST_DIR}/Classes/MyAnotherObject.h # Add this line
    7. ${CMAKE_CURRENT_LIST_DIR}/bindings/auto/jsb_my_module_auto.h
    8. ${CMAKE_CURRENT_LIST_DIR}/bindings/auto/jsb_my_module_auto.cpp
    9. ${CMAKE_CURRENT_LIST_DIR}/bindings/auto/jsb_another_module_auto.h # Add this line
    10. ${CMAKE_CURRENT_LIST_DIR}/bindings/auto/jsb_another_module_auto.cpp # Add this line
    11. )

    重新生成绑定。

    更新 Game.cpp:

    1. #include "Game.h"
    2. #include "bindings/auto/jsb_my_module_auto.h"
    3. #include "bindings/auto/jsb_another_module_auto.h" // Add this line
    4. //......
    5. int Game::init() {
    6. //......
    7. se::ScriptEngine::getInstance()->addRegisterCallback(register_all_my_module);
    8. se::ScriptEngine::getInstance()->addRegisterCallback(register_all_another_module); // Add this line
    9. //
    10. BaseGame::init();
    11. return 0;
    12. }

    在 Xcode 或者 Visual Studio 中编译,但是得到如下错误:

    Swig 示例 - 图2

    因为 MyObject 类依赖了 MyAnotherObject 类,而 MyAnotherObject 类是被定义在另外一个模块中的。我们需要修改 my-module.i 并添加 #include "bindings/auto/jsb_another_module_auto.h"

    1. // my-module.i
    2. %module(target_namespace="my_ns") my_module
    3. // %insert(header_file) %{ ... }%} 代码块中的内容最终会被原封不动地插入到生成的头文件(.h)开头的地方
    4. %insert(header_file) %{
    5. #pragma once
    6. #include "bindings/jswrapper/SeApi.h"
    7. #include "bindings/manual/jsb_conversions.h"
    8. #include "MyObject.h"
    9. %}
    10. // %{ ... %} 代码块中的内容最终会被原封不动地插入到生成的源文件(.cpp)开头的地方
    11. %{
    12. #include "bindings/auto/jsb_my_module_auto.h"
    13. #include "bindings/auto/jsb_another_module_auto.h" // Add this line
    14. %}
    15. // ......

    在 Xcode 或者 Visual Studio 中编译项目,现在应该可以正常编译了。

    下一步,我们需要更新 .d.ts 文件:

    1. // my-module.d.ts
    2. declare namespace my_ns {
    3. class MyFeatureObject {
    4. foo() : void;
    5. }
    6. class MyCoolObject {
    7. constructor();
    8. constructor(a: number, b: number);
    9. publicFloatProperty : number;
    10. print() : void;
    11. coolProperty: number;
    12. coolMethod() : void;
    13. type: number;
    14. getFeatureObject() : MyFeatureObject;
    15. helloWithAnotherObject(obj: another_ns.MyAnotherObject) : void; // 添加这行
    16. }
    17. }
    18. // 添加以下行
    19. declare namespace another_ns {
    20. class MyAnotherObject {
    21. a: number;
    22. b: number;
    23. }
    24. }

    添加更多的用于读取 MyAnotherObject 类属性的测试代码:

    1. // MyComponent.ts
    2. import { _decorator, Component } from 'cc';
    3. const { ccclass } = _decorator;
    4. @ccclass('MyComponent')
    5. export class MyComponent extends Component {
    6. start() {
    7. const myObj = new my_ns.MyCoolObject();
    8. // ......
    9. const anotherObj = new another_ns.MyAnotherObject(); // 添加此行
    10. myObj.helloWithAnotherObject(anotherObj); // 添加此行
    11. }