2012年12月30日 星期日

在C++中存取UnrealScript函式

在C++中要存取UnrealScript函式時,一般的做法是宣告此類別和函式為原生類別和函式,這樣在編譯UnrealScript時會自動產生對應的 C++類別和函式定義,就可讓C++認得此函式了。不過如果是非原生類別,難道就沒有其他方法可以存取函式嗎?答案是當然可以,不然用C++實作的 UnrealScript虛擬機就寫不出來了。

由UnrealScript定義的函式必須放在一個繼承自Object的類別內,UnrealScript並沒有支援非成員函式。Object類別在C++中的對應型別為UObject。由於Object這個名稱容易混淆,所以接下來 皆以UObject稱之。每個UObject都會記得自己所屬類別以及相關成員,包括函式在內。可以用UObject::FindFunction()函式取得對應的UFunction物件,並且用ProcesEvent()函式執行UFunction。

UnrealScript類別的狀態(state)內也可定義函式,會遮蔽同名的非狀態函式。FindFunction()預設會先找目前作用中的狀態是否有同名函式,然後再找父狀態,都沒有才找同名的非狀態函式。如果不想要狀態函式的話可以讓第二個參數為TRUE。

使用ProcessEvent()函式時要注意如果要執行的UFunction有參數或傳回值,第二個參數必須傳入一塊代表參數和傳回值的區塊,用來傳遞給UFunction實際的參數並且傳回執行結果。例如若要在C++執行以下UnrealScript函式:
function int foo(int i);
就要準備相應的大小的參數區塊:
struct
{
    INT Parameter;
    INT Result;
}
Params={Arg, 0};

ProcessEvent( Function, &Params );
FindFunction()也會傳回delegate,可以檢查UFunction的FunctionFlags是否標記為FUNC_Delegate來判斷。delegate不能用ProcessEvent()來執行,而是必須找出delegate屬性,然後傳入ProcessDelegate()來執行。 delegate屬性會記錄實際指派的物件位址和函式名稱,因此可以找出指派的UFunction來執行。

範例 


以下程式碼展示如何在C++取得並執行指定的UnrealScript函式:
class FunctionAccess extends Object
    native;
    
native function InvokeVoidFunction(string Path);

native function int InvokeIntFunction(string Path, int Arg);

cpptext
{
    UFunction* GetFunction(const FString& Path, UBOOL GlobalOnly=FALSE);
    void InvokeFunc(UFunction* Function, void* Params);
}
實作碼:
UFunction* UFunctionAccess::GetFunction(const FString& Path, UBOOL GlobalOnly)
{
    UFunction* Function = NULL;
    INT DotPos = Path.InStr(TEXT("."));
    if( DotPos == INDEX_NONE )
    {
        Function = FindFunction( FName(*Path), GlobalOnly );
    }
    else if(! GlobalOnly)
    {
        FName StateName    = *Path.Left( DotPos );
        FName FunctionName = *Path.Mid( DotPos+1 );
        
        for( UState* State = FindState(StateName); State; State = State->GetSuperState() )
        {
            Function = State->FuncMap.FindRef( FunctionName );
            if( Function )
                break;
        }
    }
    
    return Function;
}

void UFunctionAccess::InvokeFunc(UFunction* Function, void* Params)
{
    if( Function->FunctionFlags & FUNC_Delegate )
    {
        UDelegateProperty* DelegateProperty = FindField<UDelegateProperty>( GetClass(), *FString::Printf(TEXT("__%s__Delegate"), *Function->GetName()) );
        if( DelegateProperty )
        {
            FScriptDelegate* ScriptDelegate = (FScriptDelegate*)((BYTE*)this + DelegateProperty->Offset);
            ProcessDelegate( FunctionGetFName(), ScriptDelegate, Parmas );
        }
        else
        {
            ProcessEvent( Function, Params );
        }
    }
}

void UFunctionAccess::InvokeVoidFunction(const FString& Path)
{
    UFunction* Function = GetFunction( Path );
    if( Function && Function->NumParms == 0 )
    {
        InvokeFunc( Function, NULL );
    }
}

Int UFunctionAccess::InvokeIntFunction(const FString& Path, INT Arg)
{
    struct
    {
        INT Parameter;
        INT Result;
    }
    Params={Arg, 0};

    UFunction* Function = GetFunction( Path );
    if( Function && Function->NumParms == 2 &&
        Function->PropertyLink && Function->PropertyLink->IsA(UIntProperty::StaticClass()) &&
        Function->GetReturnProperty() && Function->GetReturnProperty()->IsA(UIntProperty::StaticClass()) )
    {
        checkSlow( Function->ParmsSize == sizeof(Params) );
        
        InvokeFunc( Function, &Params );
    }
    
    return Params.Result;
}
以下類別包括幾個測試用的函式:
class Test extends FunctionAccess;

function VoidFunction()
{
    `log("global"@GetFuncName());
}

function int IntFunction(int i)
{
    `log("global"@GetFuncName());
    return i;
}

delegate VoidDelegate();

delegate int IntDelegate(int i);

state MyState
{
    function VoidFunction()
    {
        `log("MyState."$GetFuncName());
    }

    function int IntFunction(int i)
    {
        `log("MyState."$GetFuncName());
        return i;
    }
    
    function int StateOnlyFunction(int i)
    {
        `log("MyState."$GetFuncName());
        return i;
    }
}
把測試案例寫在CheatManager裡方便執行:
class MyCheatManager extends CheatManager;

exec function InvokeVoidFunction()
{
    local Test T;
    T = new class'Test';
    T.InvokeVoidFunction("VoidFunction");
    T.InvokeVoidFunction("MyState.VoidFunction");    
}

exec function InvokeIntFunction(int i)
{
    local Test T;
    local int r;
    T = new class'Test';
    r = T.InvokeIntFunction("IntFunction", i);
    `log("result="$r);
    r = T.InvokeIntFunction("MyState.IntFunction", i);    
    `log("result="$r);
}

exec function InvokeStateOnlyFunction(int i)
{
    local Test T;
    local int r;
    T = new class'Test';
    r = T.InvokeIntFunction("StateOnlyFunction", i);
    `log("result="$r);
    r = T.InvokeIntFunction("MyState.StateOnlyFunction", i);    
    `log("result="$r);
}

沒有留言:

張貼留言