Skip to content

CppBinding 简单使用说明

siney edited this page Sep 17, 2020 · 6 revisions

CppBinding 简单使用说明

slua提供基于模板展开的lua接口绑定方法,我们称其为cppbingding,通过使用cppbinding,你可以在尽量不修改目标类文件(无侵入)的情况,将c++类和方法导出给lua使用。使用cppbinding方法导出的lua接口不使用反射机制,相当于你手写lua接口导出,所以在效率和速度上都是最优的,不用担心效率损失,因为你自己手写lua接口导出代码也不会再简洁了。

如果你存在一个如下类定义:

class Base {
public:
    virtual ~Base() {
        Log::Log("Base object %p had been freed",this);
    }

    void baseFunc1() {
        Log::Log("baseFunc1 call");
    }
};

class Foo : public Base {
public:
    Foo(int v):value(v) {}
    virtual ~Foo() {
        Log::Log("Foo object %p had been freed",this);
    }

    // constructor function for lua
    // LuaOwnedPtr will hold ptr by lua and auto collect it;
    static LuaOwnedPtr<Foo> create(int v) {
        return new Foo(v);
    }

    // raw ptr not hold by lua, lua not collect it
    static Foo* getInstance() {
        static Foo s_inst(2048);
        return &s_inst;
    }

    void bar(const char* v) {
        Log::Log("get text %s and value is %d",v,value);
    }

    static FString getStr() { 
        return FString(UTF8_TO_TCHAR("some text"));
    }

    virtual int virtualFunc() const {
        Log::Log("virtual func from Foo");
        return 1;
    }

    int value;
};


class FooChild : public Foo {
public:
    FooChild(int v):Foo(v) {

    }

    void fooChildFunc1() {
        Log::Log("baseFunc1 call");
    }

    static LuaOwnedPtr<Foo> create(int v) {
        return new FooChild(v);
    }

    virtual int virtualFunc() const {
        Log::Log("virtual func from %s",typeNameFromPtr(this));
        return 2;
    }
};

我们可以使用如下定义将其导出:

DefLuaClass(Base) 
	DefLuaMethod(baseFunc1,&Base::baseFunc1)
EndDef(Base,nullptr)

DefLuaClass(Foo,Base)
    DefLuaMethod(bar,&Foo::bar)
    DefLuaMethod(getStr,&Foo::getStr)
    DefLuaMethod(getInstance,&Foo::getInstance)
    DefLuaMethod(virtualFunc,&Foo::virtualFunc)
EndDef(Foo,&Foo::create)
    
DefLuaClass(FooChild,Foo)
    DefLuaMethod(fooChildFunc1,&FooChild::fooChildFunc1)
    DefLuaMethod(virtualFunc,&FooChild::virtualFunc)
EndDef(FooChild,&FooChild::create)

使用DefLuaClass导出一个c++类,如果存在父类,可以在,后面填写,支持多继承,如果存在多个就写多个,例如:

DefLuaClass(Base)
DefLuaClass(Foo,Base) 
DefLuaClass(Foo,Base,Object) 

使用DefLuaMethod导出类方法,同时支持支持static方法和成员方法,格式为:

DefLuaMethod(方法名字,函数指针)

使用EndDef结束定义,同时可以指定类的构造函数,如果不能构造就填入nullptr,格式为:

EndDef(类,构造函数指针)

需要注意,构造函数必须为非成员方法。

如果在方法中返回了类指针,默认lua是不负责生命周期管理的,你需要自己管理类的生命周期,比如自己提供对应destroy方法去销毁对象。如果你希望lua管理生命周期,在lua没有引用后自行销毁,你需要返回LuaOwnedPtr封装的对象指针,这样则lua会管理对象生命周期,同时在c++中不应该有任何代码去销毁对应的对象,否则会造成不可知的错误,例如:

	// constructor function for lua
    // LuaOwnedPtr will hold ptr by lua and auto collect it;
    static LuaOwnedPtr<Foo> create(int v) {
        return new Foo(v);
    }

    // raw ptr not hold by lua, lua not collect it
    static Foo* getInstance() {
        static Foo s_inst(2048);
        return &s_inst;
    }

create函数返回的new Foo(v) 收到lua管理,而getInstance返回的Foo*不受lua管理,lua不会回收。

如果c++函数存在多个重载版本,可以使用 DefLuaMethod_With_Type 宏 明确到底使用哪个版本的函数,例如:

DefLuaMethod_With_Type(getFruit_1,&Foo::getFruit,Fruit (Foo::*) ())
DefLuaMethod_With_Type(getFruit_2,&Foo::getFruit,Fruit (Foo::*) (int))

如果你希望使用lambda表达式作为函数实现,导出给lua,可是使用 DefLuaMethod_With_Lambda 宏,例如:

DefLuaMethod_With_Lambda(helloWorld,false,[]()->void {
            slua::Log::Log("Hello World from slua");
        })

扩展blueprint的CppBinding

UObject的UFunction都会通过反射的方法自动导出给lua,但有的时候,某些UObject的方法并没有标记为UFunction,但是我们仍然希望导出给lua使用,例如:

UCLASS(Abstract, editinlinenew, BlueprintType, Blueprintable, meta=( DontUseGenericSpawnObject="True", DisableNativeTick) )
class UMG_API UUserWidget : public UWidget, public INamedSlotInterface
{
	GENERATED_BODY()

	friend class SObjectWidget;
public:
	UUserWidget(const FObjectInitializer& ObjectInitializer);
	...
		/** @returns The root UObject widget wrapper */
	UWidget* GetRootWidget() const;

	/** @returns The slate widget corresponding to a given name */
	TSharedPtr<SWidget> GetSlateWidgetFromName(const FName& Name) const;

	/** @returns The uobject widget corresponding to a given name */
	UWidget* GetWidgetFromName(const FName& Name) const;

	//~ Begin UObject Interface
	virtual bool IsAsset() const;
	virtual void PreSave(const class ITargetPlatform* TargetPlatform) override;

可以看到UUserWidget的GetSlateWidgetFromName方法等都没有标记为UFunction,这样的函数在反射过程中是无法找到的,但是我们很可能在lua编程中需要使用它们,这个时候,我们可以增加扩展方法,用于扩展uobject的方法,例如:

// 给UObjectWidget增加名为FindWidget函数,使用GetWidgetFromName的实现
REG_EXTENSION_METHOD(UUserWidget,"FindWidget",&UUserWidget::GetWidgetFromName);

// 给UObjectWidget增加名为RemoveWidget函数,自己实现
REG_EXTENSION_METHOD_IMP(UUserWidget,"RemoveWidget",{
                CheckUD(UUserWidget,L,1);
                auto widget = LuaObject::checkUD<UWidget>(L,2);
                bool ret = UD->WidgetTree->RemoveWidget(widget);
                return LuaObject::push(L,ret);
            });

// 给UObjectWidget增加名为SpawnActor函数,因为存在重载版本,我明确给出使用哪个重载版本的函数签名
REG_EXTENSION_METHOD_WITHTYPE(UWorld,"SpawnActor",&UWorld::SpawnActor,AActor* (UWorld::*)( UClass*, FVector const*,FRotator const*, const FActorSpawnParameters&));

同时扩展方法也支持使用lambda表达式,可以使用 REG_EXTENSION_METHOD_LAMBDA 宏,具体请参考宏定义

导出enum和enum class

如果需要导出enum,或者enum class(注意enum和enum class不是一回事,请参考c++标准),你需要使用 DefEnum 和 DefEnumClass 宏, 例如:

enum TestEnum {
	TE_OK,
	TE_BAD,
	TE_COUNT,
};
DefEnum(TestEnum, TE_OK, TE_BAD, TE_COUNT);

enum class TestEnum2 {
	OK,
	BAD,
	COUNT,
};
DefEnumClass(TestEnum2, TestEnum2::OK, TestEnum2::BAD, TestEnum2::COUNT);

这样便在lua中导出了 TestEnum 和 TestEnum2 enum对象,例如:

assert(TestEnum.TE_COUNT==2)
assert(TestEnum2.COUNT==2)

CppBinding的限制:

目前cppbinding导出的c++类暂时不支持down cast操作,即从父类cast到子类,未来版本会增加这个支持。