-
Notifications
You must be signed in to change notification settings - Fork 43
Extern definitions
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
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.
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.
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
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
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
andset_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 callingCoreAPI.copy
/CoreAPI.copyStruct
- Increment (
++
) / Decrement (--
) operators:op_Increment
,op_Decrement
- Not (
!
) operator:op_Not
- Array (bracketed) access:
You can define these names through a @:uname
function or just use the actual function name.
See Enums
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:
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 |
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
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 |
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.
There are some types that are treated in a special way inside UE4Haxe:
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<>
is unreal.TSubclassOf<>
in Haxe. It's just a typedef to a unreal.UClass
, and performs the conversion automatically under-the-hood
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.
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
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 fromUObject
(uextension) and only in those cases, for functions that have@:ufunction
(and are compiled as Static code) or@:uexpose
metadata.
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.
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 intoHaxe/Generated/Externs
). The first ones are included in the UE4Haxe repo, while the latter should be ignored by version control
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)
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.