Exposing Objects

Bob Chatman edited this page Apr 11, 2015 · 3 revisions

##Initial Steps V8 and UE4 live in different worlds, and there is a level of technical understanding you have to have about C++, function pointers and how to reference your object. To begin with, exposing an object is pretty straight forward:

Example 1

Object obj;
bool result = pCore->Expose("myObject", &obj);

The above example exposes an object called myObject to the Javascript Virtual Machine, and allows you to reference it & play with it on the JS side.

Example 2

Result res;
pCore->Execute("JSON.stringify(myObject)", &res)
UE_LOG(FlatheadExampleLog, Display, TEXT("%s"), ANSI_TO_TCHAR(res.StringValue));

Executing these two examples will result in a message like the following:

[2015.03.12-00.51.17:982][  0]FlatheadExampleLog:Display: {}

If your goal is to create an object that exists entirely in JS, you can continue to start setting properties up and do further amazing things.

obj.Set("Life", 42);
obj.Set("Universe", 42.0);
obj.Set("Everything", "42.0h");
obj.Set("Actual Question", false);

pCore->Execute("JSON.stringify(myObject.Dolphins)", &res) // undefined
pCore->Execute("JSON.stringify(myObject.Life)", &res) // 42.0
pCore->Execute("JSON.stringify(myObject.Universe)", &res) // 42.0
pCore->Execute("JSON.stringify(myObject.Everything)", &res) // 42.0h
pCore->Execute("JSON.stringify(myObject['Actual Question'])", &res) // false

##Tying In Functions level 2 of exposing objects is binding a function in. There is a level of complexity to be noted here, because while the solution is rather straight forward, the reasons for the solution are not. C++ compilation is an interesting process to look into, and specifically you should look into something called Name Mangling, which basically explains the steps taken to convert from the Namespaced class instance method into something that can be looked up easily. Instance methods are not as easily worked with and ISOCPP actually suggests that you don't work with C++ member functions.

In C++, member functions have an implicit parameter which points to the object (the this pointer inside the member function). Normal C functions can be thought of as having a different calling convention from member functions, so the types of their pointers (pointer-to-member-function vs pointer-to-function) are different and incompatible. C++ introduces a new type of pointer, called a pointer-to-member, which can be invoked only by providing an object.

The suggestion is to provide a wrapper, and that is nearly exactly what we are going to do.

Lets write the wrapper for accessing a method, TruncToInt, within the FMath class of UE4.

namespace MyGame
{
    class FMathWrapper 
    {
        static int TruncToInt(const Flathead::CallbackInfo &args)
        {
            return FMath::TruncToInt(args[0]);
        }
    }
}

The Wrapper for this static method is complete. Let's bind it to our object.

obj.Set("TruncToInt", FMathWrapper::TruncToInt);

I bet you thought it would be more complicated than that!

pCore->Execute("JSON.stringify(myObject.TruncToInt(42.4654654))", &res) // 42.0

##Tying into Instance Methods The only remaining thing to do, and likely the reason you are reading this, is to gain access to an instance object and be able to operate on it. This is achieved with the same exact process as the static method above, with two further steps. The issue with binding to an instance method is the hidden parameter for this, and we have to find a way to get that pointer to the object. The good news is, we have it - the address of our instance - &MyActor.

FVector location = FVector::ZeroVector;
ABadAssedVehicle *myVehicle = Cast<ABadAssedVehicle>(World->SpawnActor(ABadAssedVehicle::StaticClass(), &location));
obj.bind(myVehicle);

The only tricky part is instantiating your object.

Next, we need to write our static wrapper function, and reference our instance:

namespace MyGame
{
    class FBadAssedVehicleWrapper 
    {
        static void Accelerate(const Flathead::CallbackInfo &args)
        {
            ABadAssedVehicle *vehicle = Cast<ABadAssedVehicle>(args.Instance);
            
            // Do some amazing stuff with our vehicle
            vehicle.Accelerate(args[0], args[1], args[3]);
        }
    }
}

With two new additions to the API we are now able to pass values into our instance via an exposed public function. If you add the static accessors into your class directly you also gain the flexibility of working with internal values and methods.

Clone this wiki locally
You can’t perform that action at this time.
You signed in with another tab or window. Reload to refresh your session. You signed out in another tab or window. Reload to refresh your session.
Press h to open a hovercard with more details.