-
Notifications
You must be signed in to change notification settings - Fork 428
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");
})
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不是一回事,请参考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导出的c++类暂时不支持down cast操作,即从父类cast到子类,未来版本会增加这个支持。