Skip to content
Cauê Waneck edited this page Jul 23, 2018 · 21 revisions

In order to use external code that is defined on UE4, you must provide the C++ definition to it. Unreal.hx covers already a good part of the API in externs - thanks to the Extern Generator project - which generates all UFUNCTION and UPROPERTY members for UCLASS, USTRUCT and UENUM definitions.

This doesn't cover all of it - specially if you need a specific API that is not available for blueprints. So you can add more definitions to it when needed

Extern Baker

The Haxe compilation happens in two steps: First, it bakes all the externs that are defined in YourProject/Haxe/Externs and YourProject/Plugins/UnrealHx/Haxe/Externs into actual Haxe wrappers, and then actually compiles your custom classes definitions (anything that is inside Haxe/Static).

This allows for extern code definitions to be very expressive and slim. Any .hx file put into the Haxe/Externs folder will be processed by the extern baker, which in turn will generate code into the Haxe/Generated folder.

Basics

Any extern class/enum/struct may contain the following metadatas:

  • @:uextern - Tells the Extern Baker that this class is an unreal extern. Required
  • @:glueCppIncludes("path/to/header.h"[,"path/to/another/header.h",...]) - Specifies which headers in UE-land define the type that is being extern. Required
  • @:umodule - Tells Haxe's build system which module this extern is defined. The build system will then pick this up when adding module dependencies. Optional
  • @:uname - Allows to extern a class with a different name than in Unreal. Useful for example for taking off Unreal's prefixes from the Haxe classes. Optional - the same name as the class (without the package) is assumed if no @:uname meta is present

Only public and protected functions and properties can be made extern.

UObject-derived externs

It's very simple to create externs for UObject-derived classes.

UObject example:

// this file is at Haxe/Externs/somePackage/AMyObject.hx
package somePackage;

@:glueCppIncludes("path/to/some/header.h")
@:uextern extern class AMyObject extends unreal.AActor // this could be any subclass of UObject
{
  // the property can be of any supported type (see types ahead)
  // you can declare any C++ member/static variable with the supported types here - be it uproperty or not
  public var someProperty : unreal.UObject;

  // the function can have its arguments / return type be of any supported type (see types ahead)
  // any function with the supported types can be defined here - be it ufunction or not
  public function someFunction(arg:Bool) : unreal.Int32;
}

For more information about uobject-derived externs, see garbage collection and pointer ownership

Other classes/structs that are not derived from UObjects

The syntax for externing a non-UObject-derived class is the same as UObject derived. The Extern Baker will know which kind of extern it is by looking at the superclass:

// this file is at Haxe/Externs/somePackage/FMyStruct.hx
package somePackage;

@:glueCppIncludes("path/to/some/header.h")
@:uextern extern class FMyStruct // this could actually extend other structs
{
  // definition goes here
}
// this file is at Haxe/Externs/somePackage/FMyStructExt.hx
package somePackage;

@:glueCppIncludes("Path/to/another/header.h")
@:uextern extern class FMyStructExt extends FMyStruct {
  // definition goes here
}

You can define basically any struct/class that is used by unreal - this is not limited only to USTRUCTs

For information on how to create your own USTRUCTs from Haxe code, see UStructs

Special constructs for non-uobject types

Non-UObject types can define operators, have constructors, etc. There are some special constructs for these types:

  • Constructors - You can use Haxe's new function for declaring constructors. But if instead you want to define a function that creates a types using the C++ new operator, you can define a static function that has a @:uname("new") metadata:
  @:uname("new") public static function createNew(arg1:SupportedType, arg2:SupportedType2 /* ... */):POwnedPtr<MyStruct>;

POwnedPtr is explained below on the pointers to Non-UObjects.

The @:uname("new") should only be used in cases where you need a pointer that was allocated with new - for example if we are creating a shared pointer from it - which will be destroyed using delete when no references are left. In other cases, you should use the @:uname(".ctor") metadata:

  @:uname(".ctor") public static function createStruct(arg1:SupportedType, arg2:SupportedType2 /* ... */):MyStruct;

Structs created using @:uname(".ctor") are managed by Haxe - so they will be destroyed when the hxcpp garbage collector collects it.

Please note that on the context of non-UObject extern definitions, public function new() in Haxe is equivalent to `@:uname(".ctor")

  • Operators - UE4Haxe supports some C++ operators. Whenever possible, it follows the CLI naming convention for naming them:
    • Array (bracketed) access: get_Item and set_Item - for getting and setting a new item
    • Dereference operator (*someInstance): op_Dereference
    • Equals operator: Added automatically. See Equality for more info
    • Copy operator: Added automatically - can be disabled by adding @:noCopy metadata. You can use it by calling CoreAPI.copy / CoreAPI.copyStruct
    • Increment (++) / Decrement (--) operators: op_Increment, op_Decrement
    • Not (!) operator: op_Not

You can define these names through a @:uname function or just use the actual function name.

UENUM externs

See Enums

Supported types

Not all Haxe types are supported when declaring externs. Only basic types, or other @:uextern classes/enums are supported. There are also some special types that are supported by UE4Haxe:

Basic types

In the unreal package (at PluginFolder/Haxe/Static/unreal), there are external definitions for all basic types:

Unreal type Haxe Type
int8 unreal.Int8
uint8 unreal.UInt8
int16 unreal.Int16
uint16 unreal.UInt16
int32 unreal.Int32 or Int
uint32 unreal.UInt32
int64 unreal.Int64
uint64 unreal.UInt64
float unreal.Float32
double unreal.Float64 or Float
bool Bool

UObject types

Any @:uextern uobject can be used as a type inside externs. They will always be generated as a pointer to the type - as UObject-derived types always need to be accessed as pointers

Non-UObject types

Any @:uextern non-uobject can also be used as a type inside externs. By default, they will be expected to be used by value (i.e. not as a pointer or reference). You can use the following ownership modifiers:

C++ type Haxe type
MyStruct (no modifier) MyStruct
MyStruct& unreal.PRef<MyStruct>
const MyStruct Const<MyStruct>
const MyStruct& unreal.PRef<Const<MyStruct>>
MyStruct * See below

Pointers to Non-UObjects

When dealing with pointers to non-uobjects, one can use either unreal.POwnedPtr<> or unreal.PPtr<> as the ownership modifier. POwnedPtr should be used when it is our responsibility to call delete on it - for example when the type was created through the new function; Otherwise PPtr should be used. You cannot use a POwnedPtr directly; Rather, you must convert to a shared pointer using one of its member methods. If no conversion happens, the memory will leak.

"Special" types

There are some types that are treated in a special way inside UE4Haxe:

TCHAR *

TCHAR * is unreal.TCharStar in Haxe. It's just a typedef to a String, and a special conversion is done to make this happen. The drawback is that we cannot modify any TCHAR * contents inside Haxe code, or let some native UE function modify a TCHAR * sent to Unreal

TSubclassOf

TSubclassOf<> is unreal.TSubclassOf<> in Haxe. It's just a typedef to a unreal.UClass, and performs the conversion automatically under-the-hood

TWeakObjectPtr

TWeakObjectPtr<> is unreal.TWeakObjectPtr<> in Haxe. It's just a typedef to its underlying type, and performs the conversion automatically under-the-hood. All UObject-derived objects are weak pointers in Unreal.hx already. You can check if the pointer is still valid by running the isValid() function.

Lambda types

If a Haxe function signature is found in any of the extern definitions, the type will be considered a C++ Lambda type. A wrapper to and from lambda types will be generated, and the Haxe function will be kept from being garbage collected while the lambda type is still alive

Method pointers

Sometimes an object pointer to a member function is required on Unreal code. For example, delegates' BindUObject variants take a pointer to a UObject member. In haxe terms, this is a unreal.MethodPointer<ThisObjectType, HaxeFunctionSignature>. For example, unreal.MethodPointer<UObject, Int->Void> is a method pointer of a UObject-derived type, which takes an Int and returns nothing.

Please note that you can use MethodPointer only for Haxe types that derive from UObject (uextension) and only in those cases, for functions that have @:ufunction (and are compiled as Static code) or @:uexpose metadata.

Shared and Weak pointers

All shared/weak pointer variants TSharedPtr, TWeakPtr, TSharedRef, TThreadSafeSharedPtr, etc. work on Haxe, and they are available on the unreal package. One can access them as if they were normal objects, and pass them around; In order to create one shared pointer, one must have a POwnedPtr type (see pointer ownership above). POwnedPtr types have the relevant toSharedPtr() and similar function calls.

In order to access the underlying object, you must call Get() on the shared pointer.

When using shared pointers and references in Haxe, each wrapper to it will increase one reference - which will be decreased once the wrapper is garbage collector or dispose is called. See Garbage Collection for more information.

Automatic externs

Most types that are present on the Haxe/Externs folder at the UE4Haxe repo were automatically generated by the UE4Haxe Extern Generator project. This project takes all definitions available from UHT and generates externs based on them. They are not complete - as only UCLASS, UENUM, USTRUCT and UDELEGATE types are visible by UHT, and only UPROPERTY and UFUNCTION members are available on these types. These definitions can be completed by adding a file with the suffix _Extra.hx which contains an extern class also with the _Extra suffix that has extra definitions. The extern baker will automatically look for these files and merge their definitions into the existing ones.

You can also replace @:glueCppIncludes metadata by adding it to the _Extra extern. Adding @:hasCopy and @:hasEquals will cancel previously added @:noCopy and @:noEquals. These metas are added to a struct when it's not exported to DLL - since we cannot know if these functions are inlined or not.

you can identify if the file was generated or not by looking at the header generated headers will contain the ue4hx ascii art and a warning stating that it was generated

Warning: do not confuse the pre-baked generated externs from UE4HaxeExternGenerator (which are contained within the Haxe/Externs folder) with the baked externs (which are generated into Haxe/Generated/Externs). The first ones are included in the UE4Haxe repo, while the latter should be ignored by version control

Circular references

If you want to extern a C++ class which in its turn references a Haxe-generated type, you will quickly realize that since extern baking is a completely different compilation step, it still has no knowledge about the Haxe class. In these cases, you can add a kind of "forward reference" to the Extern Baker, which can be an empty @:uextern extern class with just which type it extends and the include header for it. Such use should be considered experimental.

Example:

@:glueCppIncludes("MyClass.h") // it's always the class name without the unreal prefix
@:uextern extern class AMyClass extends unreal.AActor {
}

Be sure to make it use the same file name as your original file, as well as the original Haxe name (with its @:uname if needed)

Troubleshooting

Extern baker is not touching a file that has changed

The extern baker tries to be smart and only change the files with a timestamp that is newer than the generated one. If for some reason you want to force re-generate everything, you can set the BAKE_EXTERNS environment variable. This will however add time to your build.