`UFunction
UFunction对象描述了一个Object的成员函数,使之能够被脚本系统调用。UFunction可描述如下2种类型函数:
- Native Function C++函数导出给脚本系统
- Blueprint Function 蓝图生成的函数
一般地,函数有如下几个部分组成:
- 函数名称
- 参数
- 返回值
- 局部变量
- 代码块
UFunction源码如下:
//
// Reflection data for a replicated or Kismet callable function.
//
class COREUOBJECT_API UFunction : public UStruct
{
DECLARE_CASTED_CLASS_INTRINSIC(UFunction, UStruct, 0, TEXT("/Script/CoreUObject"), CASTCLASS_UFunction)
DECLARE_WITHIN(UClass)
public:
// Persistent variables.
uint32 FunctionFlags;
uint16 RepOffset;
// Variables in memory only.
uint8 NumParms; // 参数个数
uint16 ParmsSize; // 所有参数占用的内存块大小
uint16 ReturnValueOffset; // 返回值的在参数内存块中的偏移量
/** Id of this RPC function call (must be FUNC_Net & (FUNC_NetService|FUNC_NetResponse)) */
uint16 RPCId;
/** Id of the corresponding response call (must be FUNC_Net & FUNC_NetService) */
uint16 RPCResponseId;
/** pointer to first local struct property in this UFunction that contains defaults */
UProperty* FirstPropertyToInit;
#if UE_BLUEPRINT_EVENTGRAPH_FASTCALLS
// The event graph this function calls in to (persistent)
UFunction* EventGraphFunction;
// The state offset inside of the event graph (persistent)
int32 EventGraphCallOffset;
#endif
private:
Native Func; // 如果该函数是C++ Native型,那么为Native函数的指针; 否则null
public:
/**
* Returns the native func pointer.
*
* @return The native function pointer.
*/
FORCEINLINE Native GetNativeFunc() const
{
return Func;
}
/**
* Sets the native func pointer.
*
* @param InFunc - The new function pointer.
*/
FORCEINLINE void SetNativeFunc(Native InFunc)
{
Func = InFunc;
}
/**
* Invokes the UFunction on a UObject.
*
* @param Obj - The object to invoke the function on.
* @param Stack - The parameter stack for the function call.
* @param Result - The result of the function.
*/
void Invoke(UObject* Obj, FFrame& Stack, RESULT_DECL);
// Constructors.
explicit UFunction(const FObjectInitializer& ObjectInitializer, UFunction* InSuperFunction, uint32 InFunctionFlags = 0, uint16 InRepOffset = 0, SIZE_T ParamsSize = 0 );
explicit UFunction(UFunction* InSuperFunction, uint32 InFunctionFlags = 0, uint16 InRepOffset = 0, SIZE_T ParamsSize = 0);
void InitializeDerivedMembers();
// UObject interface.
virtual void Serialize( FArchive& Ar ) override;
// UField interface.
virtual void Bind() override;
// UStruct interface.
virtual UStruct* GetInheritanceSuper() const override { return NULL;}
virtual void Link(FArchive& Ar, bool bRelinkExistingProperties) override;
// UFunction interface.
UFunction* GetSuperFunction() const;
UProperty* GetReturnProperty() const;
/**
* Used to safely check whether the passed in flag is set.
*
* @param FlagToCheck Class flag to check for
*
* @return true if the passed in flag is set, false otherwise
* (including no flag passed in, unless the FlagsToCheck is CLASS_AllFlags)
*/
FORCEINLINE bool HasAnyFunctionFlags( uint32 FlagsToCheck ) const
{
return (FunctionFlags&FlagsToCheck) != 0 || FlagsToCheck == FUNC_AllFlags;
}
/**
* Used to safely check whether all of the passed in flags are set.
*
* @param FlagsToCheck Function flags to check for
* @return true if all of the passed in flags are set (including no flags passed in), false otherwise
*/
FORCEINLINE bool HasAllFunctionFlags( uint32 FlagsToCheck ) const
{
return ((FunctionFlags & FlagsToCheck) == FlagsToCheck);
}
/**
* Returns the flags that are ignored by default when comparing function signatures.
*/
FORCEINLINE static uint64 GetDefaultIgnoredSignatureCompatibilityFlags()
{
//@TODO: UCREMOVAL: CPF_ConstParm added as a hack to get blueprints compiling with a const DamageType parameter.
const uint64 IgnoreFlags = CPF_PersistentInstance | CPF_ExportObject | CPF_InstancedReference
| CPF_ContainsInstancedReference | CPF_ComputedFlags | CPF_ConstParm | CPF_UObjectWrapper
| CPF_NativeAccessSpecifiers | CPF_AdvancedDisplay;
return IgnoreFlags;
}
/**
* Determines if two functions have an identical signature (note: currently doesn't allow
* matches with class parameters that differ only in how derived they are; there is no
* directionality to the call)
*
* @param OtherFunction Function to compare this function against.
*
* @return true if function signatures are compatible.
*/
bool IsSignatureCompatibleWith(const UFunction* OtherFunction) const;
/**
* Determines if two functions have an identical signature (note: currently doesn't allow
* matches with class parameters that differ only in how derived they are; there is no
* directionality to the call)
*
* @param OtherFunction Function to compare this function against.
* @param IgnoreFlags Custom flags to ignore when comparing parameters between the functions.
*
* @return true if function signatures are compatible.
*/
bool IsSignatureCompatibleWith(const UFunction* OtherFunction, uint64 IgnoreFlags) const;
};
实例分析,依然在FPS Example项目中, 给AFirstPersonCharacter添加一个函数
UFUNCTION(BlueprintCallable)
bool CheckNameValid(const FString &InStr);
在FirstPerson.generated.cpp中会生成如下代码
UFunction的调用
一般地在设计脚本系统时,需要考虑函数的调用情形有:
- Native函数调用Native函数
- Native函数调用Script函数
- Script函数调用Native函数
- Script函数调用Script函数
在UE4中,抽象出UFunction用于表示Native和BP函数。脚本系统的字节码指令对应着每个C++小函数(UObject中的execXXXX函数)。所以这里只需要考虑C++代码执行UFunction对象的情形。
// UnrealScript intrinsics.
// Undefined native handler
DECLARE_FUNCTION(execUndefined);
// @todo document below declared functions
// Variables
DECLARE_FUNCTION(execLocalVariable);
DECLARE_FUNCTION(execInstanceVariable);
DECLARE_FUNCTION(execDefaultVariable);
DECLARE_FUNCTION(execLocalOutVariable);
DECLARE_FUNCTION(execInterfaceVariable);
DECLARE_FUNCTION(execInterfaceContext);
DECLARE_FUNCTION(execArrayElement);
DECLARE_FUNCTION(execBoolVariable);
DECLARE_FUNCTION(execClassDefaultVariable);
DECLARE_FUNCTION(execEndFunctionParms);
// Do Nothing
DECLARE_FUNCTION(execNothing);
DECLARE_FUNCTION(execNothingOp4a);
/** Breakpoint; only observed in the editor; executing it at any other time is a NOP */
DECLARE_FUNCTION(execBreakpoint);
/** Tracepoint; only observed in the editor; executing it at any other time is a NOP */
DECLARE_FUNCTION(execTracepoint);
DECLARE_FUNCTION(execWireTracepoint);
/** Instrumentation event for profiling; only observed in the builds with blueprint instrumentation */
DECLARE_FUNCTION(execInstrumentation);
DECLARE_FUNCTION(execEndOfScript);
/** failsafe for functions that return a value - returns the zero value for a property and logs that control reached the end of a non-void function */
DECLARE_FUNCTION(execReturnNothing);
DECLARE_FUNCTION(execEmptyParmValue);
// Commands
DECLARE_FUNCTION(execJump);
DECLARE_FUNCTION(execJumpIfNot);
DECLARE_FUNCTION(execAssert);
/**
* Push a code offset onto the execution flow stack for future execution.
* Current execution continues to the next instruction after the push one.
*/
DECLARE_FUNCTION(execPushExecutionFlow);
/**
* Pops a code offset from the execution flow stack and starts execution there.
* If there are no stack entries left, it is treated as an execution error.
*/
DECLARE_FUNCTION(execPopExecutionFlow);
DECLARE_FUNCTION(execComputedJump);
/**
* Pops a code offset from the execution flow stack and starts execution there, if a condition is not true.
* If there are no stack entries left, it is treated as an execution error.
*/
DECLARE_FUNCTION(execPopExecutionFlowIfNot);
// Assignment
DECLARE_FUNCTION(execLet);
DECLARE_FUNCTION(execLetObj);
DECLARE_FUNCTION(execLetWeakObjPtr);
DECLARE_FUNCTION(execLetBool);
DECLARE_FUNCTION(execLetDelegate);
DECLARE_FUNCTION(execLetMulticastDelegate);
// Delegates
DECLARE_FUNCTION(execAddMulticastDelegate);
DECLARE_FUNCTION(execClearMulticastDelegate);
DECLARE_FUNCTION(execEatReturnValue);
DECLARE_FUNCTION(execRemoveMulticastDelegate);
// Context expressions
DECLARE_FUNCTION(execSelf);
DECLARE_FUNCTION(execContext);
DECLARE_FUNCTION(execContext_FailSilent);
DECLARE_FUNCTION(execStructMemberContext);
// Function calls
DECLARE_FUNCTION(execVirtualFunction);
DECLARE_FUNCTION(execFinalFunction);
// Struct comparison
DECLARE_FUNCTION(execStructCmpEq);
DECLARE_FUNCTION(execStructCmpNe);
DECLARE_FUNCTION(execStructMember);
// @todo delegate: Delegate comparison is not supported for multi-cast delegates
DECLARE_FUNCTION(execEqualEqual_DelegateDelegate);
DECLARE_FUNCTION(execNotEqual_DelegateDelegate);
DECLARE_FUNCTION(execEqualEqual_DelegateFunction);
DECLARE_FUNCTION(execNotEqual_DelegateFunction);
// Constants
DECLARE_FUNCTION(execIntConst);
DECLARE_FUNCTION(execInt64Const);
DECLARE_FUNCTION(execUInt64Const);
DECLARE_FUNCTION(execSkipOffsetConst);
DECLARE_FUNCTION(execFloatConst);
DECLARE_FUNCTION(execStringConst);
DECLARE_FUNCTION(execUnicodeStringConst);
DECLARE_FUNCTION(execTextConst);
DECLARE_FUNCTION(execObjectConst);
DECLARE_FUNCTION(execAssetConst);
// @todo delegate: Multi-cast versions needed for script execution! (Need Add, Remove, Clear/Empty)
DECLARE_FUNCTION(execInstanceDelegate);
DECLARE_FUNCTION(execNameConst);
DECLARE_FUNCTION(execByteConst);
DECLARE_FUNCTION(execIntZero);
DECLARE_FUNCTION(execIntOne);
DECLARE_FUNCTION(execTrue);
DECLARE_FUNCTION(execFalse);
DECLARE_FUNCTION(execNoObject);
DECLARE_FUNCTION(execNullInterface);
DECLARE_FUNCTION(execIntConstByte);
DECLARE_FUNCTION(execRotationConst);
DECLARE_FUNCTION(execVectorConst);
DECLARE_FUNCTION(execTransformConst);
DECLARE_FUNCTION(execStructConst);
DECLARE_FUNCTION(execSetArray);
DECLARE_FUNCTION(execSetSet);
DECLARE_FUNCTION(execSetMap);
DECLARE_FUNCTION(execArrayConst);
// Object construction
DECLARE_FUNCTION(execNew);
DECLARE_FUNCTION(execClassContext);
DECLARE_FUNCTION(execNativeParm);
// Conversions
DECLARE_FUNCTION(execDynamicCast);
DECLARE_FUNCTION(execMetaCast);
DECLARE_FUNCTION(execPrimitiveCast);
DECLARE_FUNCTION(execInterfaceCast);
// Cast functions
DECLARE_FUNCTION(execObjectToBool);
DECLARE_FUNCTION(execInterfaceToBool);
DECLARE_FUNCTION(execObjectToInterface);
DECLARE_FUNCTION(execInterfaceToInterface);
DECLARE_FUNCTION(execInterfaceToObject);
// Dynamic array functions
// Array support
DECLARE_FUNCTION(execGetDynArrayElement);
DECLARE_FUNCTION(execSetDynArrayElement);
DECLARE_FUNCTION(execGetDynArrayLength);
DECLARE_FUNCTION(execSetDynArrayLength);
DECLARE_FUNCTION(execDynArrayInsert);
DECLARE_FUNCTION(execDynArrayRemove);
DECLARE_FUNCTION(execDynArrayFind);
DECLARE_FUNCTION(execDynArrayFindStruct);
DECLARE_FUNCTION(execDynArrayAdd);
DECLARE_FUNCTION(execDynArrayAddItem);
DECLARE_FUNCTION(execDynArrayInsertItem);
DECLARE_FUNCTION(execDynArrayRemoveItem);
DECLARE_FUNCTION(execDynArraySort);
DECLARE_FUNCTION(execBindDelegate);
DECLARE_FUNCTION(execCallMulticastDelegate);
DECLARE_FUNCTION(execLetValueOnPersistentFrame);
DECLARE_FUNCTION(execCallMathFunction);
DECLARE_FUNCTION(execSwitchValue);
DECLARE_FUNCTION(execArrayGetByRef);
实例分析, 在FPS Example中, AFirstPersonProjectile::OnHit调用时堆栈如下:
FirstPersonProjectile.generated.h里做了些调用包装,
#define FirstPerson_Source_FirstPerson_FirstPersonProjectile_h_9_RPC_WRAPPERS \
\
DECLARE_FUNCTION(execOnHit) \
{ \
P_GET_OBJECT(UPrimitiveComponent,Z_Param_HitComp); \
P_GET_OBJECT(AActor,Z_Param_OtherActor); \
P_GET_OBJECT(UPrimitiveComponent,Z_Param_OtherComp); \
P_GET_STRUCT(FVector,Z_Param_NormalImpulse); \
P_GET_STRUCT_REF(FHitResult,Z_Param_Out_Hit); \
P_FINISH; \
P_NATIVE_BEGIN; \
this->OnHit(Z_Param_HitComp,Z_Param_OtherActor,Z_Param_OtherComp,Z_Param_NormalImpulse,Z_Param_Out_Hit); \
P_NATIVE_END; \
}
#define FirstPerson_Source_FirstPerson_FirstPersonProjectile_h_9_RPC_WRAPPERS_NO_PURE_DECLS \
\
DECLARE_FUNCTION(execOnHit) \
{ \
P_GET_OBJECT(UPrimitiveComponent,Z_Param_HitComp); \
P_GET_OBJECT(AActor,Z_Param_OtherActor); \
P_GET_OBJECT(UPrimitiveComponent,Z_Param_OtherComp); \
P_GET_STRUCT(FVector,Z_Param_NormalImpulse); \
P_GET_STRUCT_REF(FHitResult,Z_Param_Out_Hit); \
P_FINISH; \
P_NATIVE_BEGIN; \
this->OnHit(Z_Param_HitComp,Z_Param_OtherActor,Z_Param_OtherComp,Z_Param_NormalImpulse,Z_Param_Out_Hit); \
P_NATIVE_END; \
}
FirstPerson.generated.cpp中加入了execXXX的注册
void AFirstPersonProjectile::StaticRegisterNativesAFirstPersonProjectile()
{
FNativeFunctionRegistrar::RegisterFunction(AFirstPersonProjectile::StaticClass(), "OnHit",(Native)&AFirstPersonProjectile::execOnHit);
}
下面分析一下UObject::ProcessEvent(UFunction* Function, void* Parms), 略有删减:
void UObject::ProcessEvent( UFunction* Function, void* Parms )
{
// Reject.
if (IsPendingKill())
{
return;
}
if ((Function->FunctionFlags & FUNC_Native) != 0)
{ // Native函数
int32 FunctionCallspace = GetFunctionCallspace(Function, Parms, NULL);
if (FunctionCallspace & FunctionCallspace::Remote)
{
CallRemoteFunction(Function, Parms, NULL, NULL);
}
if ((FunctionCallspace & FunctionCallspace::Local) == 0)
{
return;
}
}
else if (Function->Script.Num() == 0)
{
// 脚本函数, 却无字节码(空体)
return;
}
checkSlow((Function->ParmsSize == 0) || (Parms != NULL));
// Scope required for scoped script stats.
{
uint8* Frame = NULL;
#if USE_UBER_GRAPH_PERSISTENT_FRAME
Frame = GetClass()->GetPersistentUberGraphFrame(this, Function);
#endif
const bool bUsePersistentFrame = (NULL != Frame);
if (!bUsePersistentFrame)
{
// 分配函数执行需要的内存(参数 + 返回值 + 局部变量)
Frame = (uint8*)FMemory_Alloca(Function->PropertiesSize);
// zero the local property memory
FMemory::Memzero(Frame + Function->ParmsSize, Function->PropertiesSize - Function->ParmsSize);
}
// initialize the parameter properties 拷贝参数, 参数可以是基本类型,或者引用类型;如果参数是结构体的话, 结构体肯定是没有显式的构造函数的
FMemory::Memcpy(Frame, Parms, Function->ParmsSize);
// Create a new local execution stack.
// 创建执行栈帧
FFrame NewStack(this, Function, Frame, NULL, Function->Children);
checkSlow(NewStack.Locals || Function->ParmsSize == 0);
// if the function has out parameters, fill the stack frame's out parameter info with the info for those params
if ( Function->HasAnyFunctionFlags(FUNC_HasOutParms) )
{
FOutParmRec** LastOut = &NewStack.OutParms;
for ( UProperty* Property = (UProperty*)Function->Children; Property && (Property->PropertyFlags&(CPF_Parm)) == CPF_Parm; Property = (UProperty*)Property->Next )
{
// this is used for optional parameters - the destination address for out parameter values is the address of the calling function
// so we'll need to know which address to use if we need to evaluate the default parm value expression located in the new function's
// bytecode
if ( Property->HasAnyPropertyFlags(CPF_OutParm) )
{
CA_SUPPRESS(6263)
FOutParmRec* Out = (FOutParmRec*)FMemory_Alloca(sizeof(FOutParmRec));
// set the address and property in the out param info
// note that since C++ doesn't support "optional out" we can ignore that here
Out->PropAddr = Property->ContainerPtrToValuePtr<uint8>(Parms);
Out->Property = Property;
// add the new out param info to the stack frame's linked list
if (*LastOut)
{
(*LastOut)->NextOutParm = Out;
LastOut = &(*LastOut)->NextOutParm;
}
else
{
*LastOut = Out;
}
}
}
}
if (!bUsePersistentFrame)
{
for (UProperty* LocalProp = Function->FirstPropertyToInit; LocalProp != NULL; LocalProp = (UProperty*)LocalProp->Next)
{
LocalProp->InitializeValue_InContainer(NewStack.Locals);
}
}
// Call native function or UObject::ProcessInternal.
const bool bHasReturnParam = Function->ReturnValueOffset != MAX_uint16;
uint8* ReturnValueAddress = bHasReturnParam ? ((uint8*)Parms + Function->ReturnValueOffset) : nullptr;
Function->Invoke(this, NewStack, ReturnValueAddress);
if (!bUsePersistentFrame)
{
// 销毁临时变量
// Destroy local variables except function parameters.!! see also UObject::CallFunctionByNameWithArguments
// also copy back constructed value parms here so the correct copy is destroyed when the event function returns
for (UProperty* P = Function->DestructorLink; P; P = P->DestructorLinkNext)
{
if (!P->IsInContainer(Function->ParmsSize))
{
P->DestroyValue_InContainer(NewStack.Locals);
}
else if (!(P->PropertyFlags & CPF_OutParm))
{
FMemory::Memcpy(P->ContainerPtrToValuePtr<uint8>(Parms), P->ContainerPtrToValuePtr<uint8>(NewStack.Locals), P->ArrayDim * P->ElementSize);
}
}
}
}
}
FFrame
函数运行的时候都有一个上下文, 这个称为栈帧(frame); 这些栈帧串起来就组成了调用栈(Stack)。
源码路径Engine\Source\Runtime\CoreUObject\Public\UObject\Stack.h
//
// Information about script execution at one stack level.
//
// UFunction的执行上下文
struct FFrame : public FOutputDevice
{
public:
// Variables.
UFunction* Node; // 对应的UFunction对象
UObject* Object; // this Object
uint8* Code; // 当前字节码指针(类似cpu eip)
uint8* Locals; // 局部变量内存块
UProperty* MostRecentProperty;
uint8* MostRecentPropertyAddress;
/** The execution flow stack for compiled Kismet code */
FlowStackType FlowStack;
/** Previous frame on the stack */
FFrame* PreviousFrame; // 前一个帧
/** contains information on any out parameters */
FOutParmRec* OutParms;
/** If a class is compiled in then this is set to the property chain for compiled-in functions. In that case, we follow the links to setup the args instead of executing by code. */
UField* PropertyChainForCompiledIn;
/** Currently executed native function */
UFunction* CurrentNativeFunction;
bool bArrayContextFailed;
public:
// Constructors.
FFrame( UObject* InObject, UFunction* InNode, void* InLocals, FFrame* InPreviousFrame = NULL, UField* InPropertyChainForCompiledIn = NULL );
virtual ~FFrame()
{
#if DO_BLUEPRINT_GUARD
FBlueprintExceptionTracker& BlueprintExceptionTracker = FBlueprintExceptionTracker::Get();
if (BlueprintExceptionTracker.ScriptStack.Num())
{
BlueprintExceptionTracker.ScriptStack.Pop(false);
}
#endif
}
// Functions.
COREUOBJECT_API void Step( UObject* Context, RESULT_DECL );
/** Replacement for Step that uses an explicitly specified property to unpack arguments **/
COREUOBJECT_API void StepExplicitProperty(void*const Result, UProperty* Property);
/** Replacement for Step that checks the for byte code, and if none exists, then PropertyChainForCompiledIn is used. Also, makes an effort to verify that the params are in the correct order and the types are compatible. **/
template<class TProperty>
FORCEINLINE_DEBUGGABLE void StepCompiledIn(void*const Result);
/** Replacement for Step that checks the for byte code, and if none exists, then PropertyChainForCompiledIn is used. Also, makes an effort to verify that the params are in the correct order and the types are compatible. **/
template<class TProperty, typename TNativeType>
FORCEINLINE_DEBUGGABLE TNativeType& StepCompiledInRef(void*const TemporaryBuffer);
COREUOBJECT_API virtual void Serialize( const TCHAR* V, ELogVerbosity::Type Verbosity, const class FName& Category ) override;
COREUOBJECT_API static void KismetExecutionMessage(const TCHAR* Message, ELogVerbosity::Type Verbosity, FName WarningId = FName());
/** Returns the current script op code */
const uint8 PeekCode() const { return *Code; }
/** Skips over the number of op codes specified by NumOps */
void SkipCode(const int32 NumOps) { Code += NumOps; }
template<typename TNumericType>
TNumericType ReadInt();
float ReadFloat();
FName ReadName();
UObject* ReadObject();
int32 ReadWord();
UProperty* ReadProperty();
/** May return null */
UProperty* ReadPropertyUnchecked();
/**
* Reads a value from the bytestream, which represents the number of bytes to advance
* the code pointer for certain expressions.
*
* @param ExpressionField receives a pointer to the field representing the expression; used by various execs
* to drive VM logic
*/
CodeSkipSizeType ReadCodeSkipCount();
/**
* Reads a value from the bytestream which represents the number of bytes that should be zero'd out if a NULL context
* is encountered
*
* @param ExpressionField receives a pointer to the field representing the expression; used by various execs
* to drive VM logic
*/
VariableSizeType ReadVariableSize(UProperty** ExpressionField);
/**
* This will return the StackTrace of the current callstack from the last native entry point
**/
COREUOBJECT_API FString GetStackTrace() const;
/**
* This will return the StackTrace of the all script frames currently active
**/
COREUOBJECT_API static FString GetScriptCallstack();
};
总结
经过上述的粗略分析,明白了对象系统中函数的描述方法和调用思路。但是还有些问题尚未解决:
- 函数中字节码的组织和布局,指令有哪些,操作数如何和指令混在一起
- 在脚本中调用函数时, 参数是放在何处的
这些问题待到研究VM指令这块再做分析。