If you want to help us maintaining this wiki, check out our discord server: https://discord.gg/3u69jMa 

Difference between revisions of "Writing Native Code"

From SWRC Wiki
Jump to navigation Jump to search
 
(26 intermediate revisions by 2 users not shown)
Line 2: Line 2:


==Introduction==
==Introduction==
Native coding in the Unreal Engine refers to code that is written in C++ instead of UnrealScript. This C++ code can be called from a script to execute tasks that are either too slow in UnrealScript (about 20 times slower than C++) or are not possible at all due to the limited features of the language. Native coding is only officially supported by Unreal Tournament from 1999 (I think Deus Ex as well...) since it is the only game that has its native header files publicly available for download. Epic never released the headers for newer versions of the engine due to licensing issues with third party code. However, after countless hours of work it was possible to modify the UT99 headers in such a way that they're usable with Republic Commando. This process is not finished yet but the most important classes like Object, Actor, Pawn and some others already work.
Native coding in the Unreal Engine refers to code that is written in C++ instead of UnrealScript. This C++ code can be called from a script to execute tasks that are either too slow in UnrealScript (about 20 times slower than C++) or are not possible at all due to the limited features of the language. Native coding is only officially supported by Unreal Tournament from 1999 since it is the only game that has its native header files publicly available for download. Epic never released the headers for newer versions of the engine due to licensing issues with third party code. However, after countless hours of work it was possible to modify the UT99 headers in such a way that they're usable with Republic Commando. This offers a lot of new opportunities to modders or people who just like to experiment with old games like me.


The way native coding works in Unreal is that you compile your scripts into a .u package and then when you write your C++ code you compile it as a dll with the same name as your package. When the game is loading a UnrealScript class and detects that it was declared as native, it also looks for a dll which contains the native code for this UnrealScript class.
The way native coding works in Unreal is that you compile your scripts into a .u package and then when you write your C++ code you compile it as a dll with the same name as your package. When the game is loading an UnrealScript class and detects that it was declared as native, it also looks for a dll which contains the native code for this UnrealScript class.


==Getting Started==
==Getting Started==
Before you can write any C++ code you first need to create a UnrealScript class that you declare as native. This tutorial assumes that you already know how to compile UnrealScript code with ucc. If you don't, then the download for the [[Republic Commando UCC]] contains a brief explanation in its readme.
Before you can write any C++ code you first need to create a UnrealScript class that you declare as native. This tutorial assumes that you already know how to compile UnrealScript code with ucc. If you don't, then the download for the [[Republic Commando UCC]] contains a brief explanation in its readme.


For the sake of this tutorial I'm going create a really simple and quite useless class that has one native function which writes a string to the log file.
For the sake of this tutorial I'm going create a really simple and quite useless class that has only one native function which writes a string to the log file.


<source lang="cpp" line">
<syntaxhighlight lang="C++" line>
class TestNativeClass extends Actor native
class TestNativeClass extends Actor native
                                     placeable;
                                     placeable;
Line 17: Line 17:
var int TestInt;
var int TestInt;


native(1024) final function TestNativeFunc(string param);
native final function TestNativeFunc(string param);


function PostBeginPlay(){
function PostBeginPlay(){
Line 23: Line 23:


TestNativeFunc("Hello World!");
TestNativeFunc("Hello World!");
Log(TestInt);
Log(TestInt); //Printing out TestInt which has been modified in C++ code
}
}
</source>
</syntaxhighlight>
The class is going to be implemented in C++ and thus must be declared as 'native'. I added an integer variable called 'TestInt' that we're going to modify in C++ and then use in UnrealScript just to show that both, the C++ and Unrealscript classes refer to the exact same object in memory.
The class is going to be implemented in C++ and thus must be declared as 'native'. I added an integer variable called 'TestInt' that we're going to modify in C++ and then print from UnrealScript just to show that both, the C++ and Unrealscript classes refer to the exact same object in memory.
 
Furthermore, I declared a native function called 'TestNativeFunc' that takes in a string parameter. Notice the number in parentheses right after the 'native' keyword. This is the unique identifier Unreal uses to associate this UnrealScript function declaration with the actual C++ function. Some of these IDs are already used by the engine so try not to overwrite them. You can check which ones are available by entering the 'DumpNatives' console command while ingame. It will write all available IDs to the log file. However, I always start at 1024. All following numbers are definitely not in use by the game so you can really use anything up to 4095 which is the maximum amount of native functions.


Furthermore, I declared a native function called 'TestNativeFunc' that takes in a string parameter. I also declared it as final since I don't want it to be overridden by a subclass but you don't have to do this.
In my 'PostBeginPlay' function I simply call the native function with "Hello World!" as the string parameter and I also write 'TestInt' which at this point has been changed in C++ code to the log file.
In my 'PostBeginPlay' function I simply call the native function with "Hello World!" as the string parameter and I also write 'TestInt' which at this point has been changed in C++ code to the log file.


 
When compiling a native class the ucc also generates a header and a source file with the C++ class definition and some other setup code. But these won't be created by default because the folder they're supposed to be created in doesn't exist so we'll have to do that before we compile our class. In the folder that contains the UnrealScript source code you have a folder with the name of your package which in turn contains a folder called 'Classes' that holds all the .uc files for this package. Navigate to your package's folder and create two new folders called 'Inc' and 'Src' in the same folder as 'Classes'.
Before you go now and compile the script with the ucc you want to do something else. When compiling a native class the ucc also generates a header and a source file with the C++ class definition and some other setup code. But these won't be created by default because the folder they're supposed to be created in doesn't exist so we'll do that before we compile our class. In the folder that contains the UnrealScript source code you have a folder with the name of your package which in turn contains a folder called 'Classes' that holds all the .uc files for this package. Navigate to your package's folder and create two new folders called 'Inc' and 'Src'.


[[File:FolderLayout.PNG]]
[[File:FolderLayout.PNG]]


Now compile your package with ucc make. When the compilation is successful the ucc will prompt you whether you want to overwrite the files "MyPackageClasses.h" and "MyPackageClasses.cpp". Just click on yes for both. They are created in the 'Inc' and 'Src' folders.
Now compile your package with ucc make. When the compilation is successful the ucc will prompt you whether you want to overwrite the files "MyPackageClasses.h" and "MyPackageClasses.cpp". Just enter yes for both. They are created in the 'Inc' and 'Src' folders respectively.


==Setting Up Visual Studio .NET 2003==
==Setting Up Visual Studio .NET 2003==
In order to write native code for Republic Commando you first need [[MS Visual Studio 2003]]. Newer versions could work but probably won't so just use this one. Since it was also used to compile RC it gives you maximum compatibility. However,  due to it being rather old, it has some issues with newer versions of Windows but there are workarounds. You can ignore any errors that might occur during the installation as it should work regardless.
In order to write native code for Republic Commando you first need [[MS Visual Studio 2003]]. While it is possible to user a newer version of Visual Studio, it is not recommended due to C++ not having a standardized [https://en.wikipedia.org/wiki/Application_binary_interface ABI]. This means that there is no guarantee for binary compatibility between modules compiled with different compilers or even different versions of the same compiler. Compilation will work fine on newer compilers but there will most likely be random crashes at startup. Also mixing different versions of the MSVC++ runtime libraries will only lead to more problems.


Once you have installed Visual Studio open it up and create a new solution that is going to contain all your native coding projects. Go to "File->New->Blank Solution...", enter a name and click Ok. Then go ahead, right click the newly created solution in the solution explorer to the left and go to "Add->New Project...". Create an "Empty Project (.NET)" with the same name of your UnrealScript package. We are actually not going to use .NET but most of the other project default configurations don't work because the buttons in the Wizard are broken on modern Windows for some reason. When you have successfully created a new project you have to adjust some settings. But before you do anything go to the top toolbar and where it says 'Debug' select 'Release' from the drop down menu. The right click the project (not the Solution!) in the solution explorer and select "Properties".
VS .NET 2003 is rather old and thus, it has some issues with newer versions of Windows but there are workarounds. You can ignore any errors that might occur during the installation as it should work regardless.
You can also use a newer better editor and only use VS to compile everything from the command line.
 
Once you have installed Visual Studio open it up and download or clone this solution that is going to contain all your native coding projects: [https://github.com/Leon280698/CT Github]. This repository always contains the most up-to-date version of the headers. Place the content of the 'CT' folder in the Code folder you just created. Open the solution up in visual studio and you will see that it already contains three native coding projects that you can use as an example.
You can use these projects and add your code there but you probably want to create a new project with a different name. In order to do that go to the explorer on the left, right-click the solution ('CT') and go to "Add->New Project...". Create an "Empty Project (.NET)" with the same name as your UnrealScript package. We are actually not going to use .NET but most of the other project default configurations don't work because the buttons in the Wizard are broken on modern Windows for some reason. When you have successfully created a new project you have to adjust some settings. Right click the project (not the Solution!) in the solution explorer and select Properties".


You should see the following:
You should see the following:
Line 48: Line 50:
[[File:ProjectProperties.PNG]]
[[File:ProjectProperties.PNG]]


There's an issue that I've seen (on Windows 7 but not on Windows 10 strangely) where the properties on the right are not visible and there's just a big grey area. In that case, close Visual Studio, download this pre-configured project and place it in the "\Documents\Visual Studio Projects\MySolution\MyProject\" folder (but delete the one you created first): [[Media:VisualStudioProjectTemplate.zip]]. Rename it so that it has name of your project.
Change the following properties:


If you didn't have any problems and everything is displaying fine you have to adjust the following properties:
<syntaxhighlight lang="C++" line>
 
<source>
General:
General:
OutputDirectory = $(SolutionDir)/../GameData/System
Configuration Type = Dynamic Library (.dll)
Configuration Type = Dynamic Library (.dll)
Use Managed Extensions = No
Use Managed Extensions = No
Line 59: Line 60:
C/C++:
C/C++:
Code Generation:
Code Generation:
Runtime Library = Multi-threaded DLL (/MD) //Not necessary but recommended...
Runtime Library = Multi-threaded DLL (/MD)
Struct Member Alignment = 4 Bytes (/Zp4)
Struct Member Alignment = 4 Bytes (/Zp4)
Advanced:
Advanced:
Calling Convention = __fastcall (/Gr)
Calling Convention = __fastcall (/Gr)
</source>
Linker:
Advanced:
ImportLibrary =  $(ProjectDir)/lib/$(TargetName).lib
</syntaxhighlight>
 
It could be that there is an issue where the properties on the right are not visible and instead there is just a big grey area. The solution to this problem is installing the .NET framework 1.0 which can be found [https://www.microsoft.com/en-us/download/details.aspx?id=96 here]. Follow [https://superuser.com/questions/120218/how-do-i-install-net-framework-1-1-for-windows-7 these] instructions to install it on newer versions of windows.


When that is done place the source files (.h and .cpp) generated by the ucc in the "\Documents\Visual Studio Projects\MySolution\MyProject\" folder. Open the folder in windows explorer and simply drag and drop both files onto the project they belong to in the solution explorer. You also need the header files for RC: [[Media:Headers.zip]]. Place all folders from this archive in "\Documents\Visual Studio Projects\MySolution\MyProject\".
When you've successfully created and configured your Project, you have to add the files generated by the ucc so that you can compile them. Just open the folders in Windows explorer and drag them onto your project in the solution explorer.


==Writing Actual Code==
==Writing Actual Code==
Line 71: Line 77:


MyPackageClasses.h:
MyPackageClasses.h:
<source lang="cpp" line">
<syntaxhighlight lang="C++" line>
/*===========================================================================
/*===========================================================================
     C++ class definitions exported from UnrealScript.
     C++ class definitions exported from UnrealScript.
Line 98: Line 104:
     DECLARE_CLASS(ATestNativeClass,AActor,0,MyPackage)
     DECLARE_CLASS(ATestNativeClass,AActor,0,MyPackage)
     NO_DEFAULT_CONSTRUCTOR(ATestNativeClass)
     NO_DEFAULT_CONSTRUCTOR(ATestNativeClass)
     DECLARE_NATIVES(ATestNativeClass)           //REMOVE
     DECLARE_NATIVES(ATestNativeClass)
};
};


Line 107: Line 113:
#endif
#endif


#if __STATIC_LINK                           //REMOVE
#if __STATIC_LINK


#define AUTO_INITIALIZE_REGISTRANTS_TEST \   //REMOVE
#define AUTO_INITIALIZE_REGISTRANTS_TEST \
ATestNativeClass::StaticClass(); \   //REMOVE
ATestNativeClass::StaticClass(); \


#endif // __STATIC_LINK                     //REMOVE
#endif // __STATIC_LINK


#endif // CORE_NATIVE_DEFS
#endif // CORE_NATIVE_DEFS
</source>
</syntaxhighlight>


MyPackageClasses.cpp:
MyPackageClasses.cpp:
<source lang="cpp" line">
<syntaxhighlight lang="C++" line>
/*===========================================================================
/*===========================================================================
     C++ class definitions exported from UnrealScript.
     C++ class definitions exported from UnrealScript.
Line 132: Line 138:


IMPLEMENT_CLASS(ATestNativeClass);
IMPLEMENT_CLASS(ATestNativeClass);
FNativeEntry<ATestNativeClass> ATestNativeClass::StaticNativeMap[] = { //REMOVE
FNativeEntry<ATestNativeClass> ATestNativeClass::StaticNativeMap[] = {
MAP_NATIVE(TestNativeFunc,1024)                                 //REMOVE
MAP_NATIVE(TestNativeFunc,0)
{NULL,NULL}                                                     //REMOVE
{NULL,NULL}
};                                                                     //REMOVE
};
LINK_NATIVES(ATestNativeClass);                                         //REMOVE
LINK_NATIVES(ATestNativeClass);
</source>
</syntaxhighlight>


This won't compile. You first have to modify the code a bit. Just remove every line that is marked in the code above. It is not used by RC. My guess is, that it was necessary when creating the XBox version where everything is linked statically.
You should generally follow the advice in the comment at the top of the generated files and don't modify them. Place your function definitions and other code in their own files.
You might want to make some changes to the UnrealScript code later down the line, like adding more classes or native functions which means that you have to re run the ucc which in turn would mean all your changes to these files are lost.
For simplicity this tutorial does everything in the generated files but you really shouldn't.
 
 
But what if you wanted to add some member functions to your class declaration? You can't do that without modifying the generated code but luckily there's a functionality in the UnrealScript compiler that does this for you: At the end of your uc file add a block called 'cpptext'. Everything contained within is added to the C++ class declaration. For example the cpptext block from one of my projects looks like this:
<syntaxhighlight lang="C++" line>
cpptext
{
//Overrides
virtual UBOOL Exec(const char* Cmd, FOutputDevice& Ar);
virtual void Tick(float DeltaTime);
virtual float GetMaxTickRate();
}
Just make sure you don't add any non-static member variables there!!!
</syntaxhighlight>


Where it says this:
Where it says this:
<source lang="cpp" line">
<syntaxhighlight lang="C++" line>
#ifndef MYPACKAGE_API
#ifndef MYPACKAGE_API
#define MYPACKAGE_API DLL_IMPORT
#define MYPACKAGE_API DLL_IMPORT
#endif
#endif
</source>
</syntaxhighlight>
replace "DLL_IMPORT" with "DLL_EXPORT" since we want to export symbols from our new dll. Also in "MyPackageClasses.h" you need to include the necessary headers. Which ones depends on what classes you're using. I created a subclass of "AActor" (C++ classes have the prefix 'A' for subclasses of Actor and the prefix 'U' for subclasses of Object) which means that I have to include the headers of "Engine" since that's where Actor is defined. So add this line above your class definition:
replace "DLL_IMPORT" with "DLL_EXPORT" since we want to export symbols from our new dll. Alternatively you can also go to the project properties and under "C/C++ -> Preprocessor -> Preprocessor definitions" add "MYPACKAGE_API=DLL_EXPORT" that's a better and cleaner solution since you don't have to change it every time the files are generated.


<source lang="cpp" line">
Also in "MyPackageClasses.h" you need to include the necessary headers. Which ones depends on what classes you're using. I created a subclass of "AActor" (C++ classes have the prefix 'A' for subclasses of Actor, 'U' for subclasses of Object and 'F' for everything else) which means that I have to include the headers of "Engine" since that's where Actor is defined. So add this line above your class definition:
#include "../Engine/Inc/Engine.h"    //This also includes "Core"
 
</source>
<syntaxhighlight lang="C++" line>
#include "../../Engine/Inc/Engine.h"    //This also includes "Core"
</syntaxhighlight>


Now create the body of your native function in the .cpp file. The signature looks like this:
Now create the body of your native function in the .cpp file. The signature looks like this:
<source lang="cpp" line">
<syntaxhighlight lang="C++" line>
void execTestNativeFunc(FFrame& Stack, void* Result);
void execTestNativeFunc(FFrame& Stack, void* Result);
</source>
</syntaxhighlight>


Notice how Unreal automatically generated the prefix "exec". That is only to differentiate between functions that are callable from UnrealScript and other ones. Also look at the parameters. Although we declared it to take in a string in UnrealScript it now has different types as parameters. The function signatures for all native functions are exactly the same. The parameters that are passed in UnrealScript are contained in the "Stack" which is of the type "FFrame".
Notice how Unreal automatically generated the prefix "exec". This is only to differentiate between functions that are callable from UnrealScript and others. Also look at the parameters. Although we declared it to take in a string in UnrealScript it now has different types as parameters. The function signatures for all native functions are exactly the same. The parameters that are passed in UnrealScript are contained in the "Stack" which is of the type "FFrame".


The implementation for my function looks like this:
The implementation for my function looks like this:
<source lang="cpp" line">
<syntaxhighlight lang="C++" line>
IMPLEMENT_FUNCTION(ATestNativeClass, 1024, execTestNativeFunc);
 
void ATestNativeClass::execTestNativeFunc(FFrame& Stack, void* Result){
void ATestNativeClass::execTestNativeFunc(FFrame& Stack, void* Result){
P_GET_STR(StringVar);
P_GET_STR(StringVar);
Line 172: Line 193:
TestInt = 12345;
TestInt = 12345;
}
}
</source>
</syntaxhighlight>
 
Look at the use of the "IMPLEMENT_FUNCTION" macro. This is really important as it registers your native function so that it can be called from UnrealScript. The Parameters of the macro are the name of the (C++) class, the function ID number and the name of the function itself.
 
In the function body I use the "P_GET_STR" macro to grab the string we passed to the function in UnrealScript from the stack. This macro creates a variable of type "FString" with the name you provided as a parameter which is why I can use "StringVar" later in the function. There are also other macros for different parameter types like "P_GET_INT", "P_GET_FLOAT", "P_GET_ACTOR", etc... You can find all of them in "Core/Inc/unscript.h". So check that out if you want to. It is also important to always get all the parameters that were passed in UnrealScript or else the game will crash. For example: If my function also took in an integer variable but I only used "P_GET_STR" it'll crash. I have to do "P_GET_STR" and "P_GET_INT". Another important thing is "P_FINISH". Always use it when you're done grabbing parameters off the stack. You need it even if your function does not have any parameters. In that case just write "P_FINISH" at the beginning.


I'm then writing a simple message to the log file that shows that this function was called. I also print out the string we passed in UnrealScript. I'm using the '*' operator on an object of 'FString' to get the c-string (char*) representation of it since that is needed for the formatted logging function.
In the function body we use the "P_GET_STR" macro to grab the string that was passed to the function in UnrealScript from the stack. This macro creates a variable of type 'FString' with the name you provided as a parameter which is why I can use 'StringVar' later in the function. There are also other macros for different parameter types like "P_GET_INT", "P_GET_FLOAT", "P_GET_ACTOR", etc... You can find all of them in "Core/Inc/unscript.h".
Furthermore, I set "TestInt" to some random value so that I can print it out from UnrealScript to see that it is in fact the same.


Another important thing to remember is that the UnrealScript and C++ classes must be identical when it comes to class size. Don't ever add a member variable in C++ that doesn't exist in UnrealScript, don't remove one either or change the order they're defined in. Adding functions is perfectly fine obviously.
It is also important to always get all the parameters that were passed in UnrealScript or else the game will crash. For example: If my function also had another parameter of type int but I only used 'P_GET_STR' it would crash. I have to do 'P_GET_STR' and 'P_GET_INT' in the order the parameters are defined in UnrealScript. Another important thing is "P_FINISH". Always use it when you're done grabbing parameters off the stack. You need it even if your function does not have any parameters. In that case just write 'P_FINISH' at the beginning.


Compile your dll by pressing F7. If there are no errors it should be created in "\Documents\Visual Studio Projects\MySolution\release". For some reason there's an error sometimes where even though you set the configuration type to dll Visual Studio will create an exe. However, this is actually just the dll with a wrong file extension so you can simply change it. To get rid of that problem completely just open "MyProject.vcproj" with a text editor (Notepad will do just fine) and replace all occurences of ".exe" (should be just two) with ".dll".
This function uses the global logger object to write a message to the log file showing that it was called. It also prints out the string we passed in UnrealScript. I'm using the overloaded '*' operator on an object of 'FString' to get the c-string (char*) representation of it since that is needed for the formatted logging function.
Also, some random value is assigned to 'TestInt' so that it can be printed out from UnrealScript to show that it is in fact the same.


Place your dll in the System folder of Republic Commando, open the UnrealEditor and place your new native class in a map. It can be that it is invisible for some reason in the 3D viewport but you should be able to see it in the orthographic viewports.
Another important thing to remember is that the UnrealScript and C++ classes must be identical when it comes to class size and memory layout. Don't ever add a member variable in C++ that doesn't exist in UnrealScript, don't remove one either or change the order they're defined in. Always generate the new headers and source files when you've made any changes to the script.


Compile your dll by pressing F7. If there are no errors it should be created in the 'GameData\System' folder. Open the UnrealEditor and place your new native class in a map.
Now save your map and launch it with the "-log" command-line parameter and the following should show up in the log window (and in the log file too obviously...):
Now save your map and launch it with the "-log" command-line parameter and the following should show up in the log window (and in the log file too obviously...):


Line 192: Line 210:


As you can see, the function is being called and prints our message to the log. It also modifies the "TestInt" variable which then is printed out from UnrealScript.
As you can see, the function is being called and prints our message to the log. It also modifies the "TestInt" variable which then is printed out from UnrealScript.
==Returning a Value from a Native Function==
Native functions sometimes perform calculations and return the result to UnrealScript. You can't use the 'return' keyword for that, instead you have to modify the 'Result' parameter that every native function has.
It’s a pointer to void, so you first have to cast it to your function’s return type and then assign a value. For example, if you wanted to return an integer you would do the following:
<syntaxhighlight lang="C++" line>
*static_cast<INT*>(Result) = 12345; //Just some random value...
</syntaxhighlight>
==Code==
My finished C++ code looks like this:
MyPackageClasses.h:
<syntaxhighlight lang="C++" line>
/*===========================================================================
    C++ class definitions exported from UnrealScript.
    This is automatically generated by the tools.
    DO NOT modify this manually! Edit the corresponding .uc files instead!
===========================================================================*/
#ifndef MYPACKAGE_NATIVE_DEFS
#define MYPACKAGE_NATIVE_DEFS
#if SUPPORTS_PRAGMA_PACK
#pragma pack (push,4)
#endif
#define MYPACKAGE_API DLL_EXPORT
#ifndef MYPACKAGE_API
#define MYPACKAGE_API DLL_IMPORT
#endif
#include "../../Engine/Inc/Engine.h"
class TEST_API ATestNativeClass : public AActor{
public:
    INT TestInt;
    void execTestNativeFunc(FFrame& Stack, void* Result);
    DECLARE_CLASS(ATestNativeClass,AActor,0,MyPackage)
    NO_DEFAULT_CONSTRUCTOR(ATestNativeClass)
    DECLARE_NATIVES(ATestNativeClass)
};
#if SUPPORTS_PRAGMA_PACK
#pragma pack (pop)
#endif
#endif // CORE_NATIVE_DEFS
</syntaxhighlight>
MyPackageClasses.cpp:
<syntaxhighlight lang="C++" line>
/*===========================================================================
    C++ class definitions exported from UnrealScript.
    This is automatically generated by the tools.
    DO NOT modify this manually! Edit the corresponding .uc files instead!
===========================================================================*/
#include "TestClasses.h"
IMPLEMENT_PACKAGE(MyPackage)
IMPLEMENT_CLASS(ATestNativeClass);
FNativeEntry<ATestNativeClass> ATestNativeClass::StaticNativeMap[] = {
MAP_NATIVE(TestNativeFunc,0)
{NULL,NULL}
};
LINK_NATIVES(ATestNativeClass);
void ATestNativeClass::execTestNativeFunc(FFrame& Stack, void* Result){
P_GET_STR(StringVar);
P_FINISH;
GLog->Logf("Native Function called from UnrealScript. Param: %s", *StringVar);
TestInt = 12345;
}
</syntaxhighlight>
==Pure Native Classes==
Pure native classes are classes that don't have an UnrealScript implementation. They really aren't any different when it comes to C++ code though so you can have a look at how the different classes of the engine are implemented in native code.

Latest revision as of 16:58, 8 July 2021

Note: Some C++ and UnrealScript knowledge is required.

Introduction

Native coding in the Unreal Engine refers to code that is written in C++ instead of UnrealScript. This C++ code can be called from a script to execute tasks that are either too slow in UnrealScript (about 20 times slower than C++) or are not possible at all due to the limited features of the language. Native coding is only officially supported by Unreal Tournament from 1999 since it is the only game that has its native header files publicly available for download. Epic never released the headers for newer versions of the engine due to licensing issues with third party code. However, after countless hours of work it was possible to modify the UT99 headers in such a way that they're usable with Republic Commando. This offers a lot of new opportunities to modders or people who just like to experiment with old games like me.

The way native coding works in Unreal is that you compile your scripts into a .u package and then when you write your C++ code you compile it as a dll with the same name as your package. When the game is loading an UnrealScript class and detects that it was declared as native, it also looks for a dll which contains the native code for this UnrealScript class.

Getting Started

Before you can write any C++ code you first need to create a UnrealScript class that you declare as native. This tutorial assumes that you already know how to compile UnrealScript code with ucc. If you don't, then the download for the Republic Commando UCC contains a brief explanation in its readme.

For the sake of this tutorial I'm going create a really simple and quite useless class that has only one native function which writes a string to the log file.

class TestNativeClass extends Actor native
                                    placeable;

var int TestInt;

native final function TestNativeFunc(string param);

function PostBeginPlay(){
	Super.PostBeginPlay();

	TestNativeFunc("Hello World!");
	Log(TestInt); //Printing out TestInt which has been modified in C++ code
}

The class is going to be implemented in C++ and thus must be declared as 'native'. I added an integer variable called 'TestInt' that we're going to modify in C++ and then print from UnrealScript just to show that both, the C++ and Unrealscript classes refer to the exact same object in memory.

Furthermore, I declared a native function called 'TestNativeFunc' that takes in a string parameter. I also declared it as final since I don't want it to be overridden by a subclass but you don't have to do this. In my 'PostBeginPlay' function I simply call the native function with "Hello World!" as the string parameter and I also write 'TestInt' which at this point has been changed in C++ code to the log file.

When compiling a native class the ucc also generates a header and a source file with the C++ class definition and some other setup code. But these won't be created by default because the folder they're supposed to be created in doesn't exist so we'll have to do that before we compile our class. In the folder that contains the UnrealScript source code you have a folder with the name of your package which in turn contains a folder called 'Classes' that holds all the .uc files for this package. Navigate to your package's folder and create two new folders called 'Inc' and 'Src' in the same folder as 'Classes'.

FolderLayout.PNG

Now compile your package with ucc make. When the compilation is successful the ucc will prompt you whether you want to overwrite the files "MyPackageClasses.h" and "MyPackageClasses.cpp". Just enter yes for both. They are created in the 'Inc' and 'Src' folders respectively.

Setting Up Visual Studio .NET 2003

In order to write native code for Republic Commando you first need MS Visual Studio 2003. While it is possible to user a newer version of Visual Studio, it is not recommended due to C++ not having a standardized ABI. This means that there is no guarantee for binary compatibility between modules compiled with different compilers or even different versions of the same compiler. Compilation will work fine on newer compilers but there will most likely be random crashes at startup. Also mixing different versions of the MSVC++ runtime libraries will only lead to more problems.

VS .NET 2003 is rather old and thus, it has some issues with newer versions of Windows but there are workarounds. You can ignore any errors that might occur during the installation as it should work regardless. You can also use a newer better editor and only use VS to compile everything from the command line.

Once you have installed Visual Studio open it up and download or clone this solution that is going to contain all your native coding projects: Github. This repository always contains the most up-to-date version of the headers. Place the content of the 'CT' folder in the Code folder you just created. Open the solution up in visual studio and you will see that it already contains three native coding projects that you can use as an example. You can use these projects and add your code there but you probably want to create a new project with a different name. In order to do that go to the explorer on the left, right-click the solution ('CT') and go to "Add->New Project...". Create an "Empty Project (.NET)" with the same name as your UnrealScript package. We are actually not going to use .NET but most of the other project default configurations don't work because the buttons in the Wizard are broken on modern Windows for some reason. When you have successfully created a new project you have to adjust some settings. Right click the project (not the Solution!) in the solution explorer and select Properties".

You should see the following:

ProjectProperties.PNG

Change the following properties:

General:
	OutputDirectory = $(SolutionDir)/../GameData/System
	Configuration Type = Dynamic Library (.dll)
	Use Managed Extensions = No
	Character Set = Not Set
C/C++:
	Code Generation:
		Runtime Library = Multi-threaded DLL (/MD)
		Struct Member Alignment = 4 Bytes (/Zp4)
	Advanced:
		Calling Convention = __fastcall (/Gr)
Linker:
	Advanced:
		ImportLibrary =  $(ProjectDir)/lib/$(TargetName).lib

It could be that there is an issue where the properties on the right are not visible and instead there is just a big grey area. The solution to this problem is installing the .NET framework 1.0 which can be found here. Follow these instructions to install it on newer versions of windows.

When you've successfully created and configured your Project, you have to add the files generated by the ucc so that you can compile them. Just open the folders in Windows explorer and drag them onto your project in the solution explorer.

Writing Actual Code

Now go to Visual Studio and open both source files. It should look like this:

MyPackageClasses.h:

/*===========================================================================
    C++ class definitions exported from UnrealScript.
    This is automatically generated by the tools.
    DO NOT modify this manually! Edit the corresponding .uc files instead!
===========================================================================*/

#ifndef MYPACKAGE_NATIVE_DEFS
#define MYPACKAGE_NATIVE_DEFS

#if SUPPORTS_PRAGMA_PACK
#pragma pack (push,4)
#endif

#ifndef MYPACKAGE_API
#define MYPACKAGE_API DLL_IMPORT
#endif



class MYPACKAGE_API ATestNativeClass : public AActor
{
public:
    INT TestInt;
    void execTestNativeFunc(FFrame& Stack, void* Result);
    DECLARE_CLASS(ATestNativeClass,AActor,0,MyPackage)
    NO_DEFAULT_CONSTRUCTOR(ATestNativeClass)
    DECLARE_NATIVES(ATestNativeClass)
};



#if SUPPORTS_PRAGMA_PACK
#pragma pack (pop)
#endif

#if __STATIC_LINK

#define AUTO_INITIALIZE_REGISTRANTS_TEST \
	ATestNativeClass::StaticClass(); \

#endif // __STATIC_LINK

#endif // CORE_NATIVE_DEFS

MyPackageClasses.cpp:

/*===========================================================================
    C++ class definitions exported from UnrealScript.
    This is automatically generated by the tools.
    DO NOT modify this manually! Edit the corresponding .uc files instead!
===========================================================================*/

#include "MyPackagePrivate.h" //Either Create a file called "MyPackagePrivate.h" or just replace with "MyPackageClasses.h"

IMPLEMENT_PACKAGE(MyPackage)



IMPLEMENT_CLASS(ATestNativeClass);
FNativeEntry<ATestNativeClass> ATestNativeClass::StaticNativeMap[] = {
	MAP_NATIVE(TestNativeFunc,0)
	{NULL,NULL}
};
LINK_NATIVES(ATestNativeClass);

You should generally follow the advice in the comment at the top of the generated files and don't modify them. Place your function definitions and other code in their own files. You might want to make some changes to the UnrealScript code later down the line, like adding more classes or native functions which means that you have to re run the ucc which in turn would mean all your changes to these files are lost. For simplicity this tutorial does everything in the generated files but you really shouldn't.


But what if you wanted to add some member functions to your class declaration? You can't do that without modifying the generated code but luckily there's a functionality in the UnrealScript compiler that does this for you: At the end of your uc file add a block called 'cpptext'. Everything contained within is added to the C++ class declaration. For example the cpptext block from one of my projects looks like this:

cpptext
{
	//Overrides
	virtual UBOOL Exec(const char* Cmd, FOutputDevice& Ar);
	virtual void Tick(float DeltaTime);
	virtual float GetMaxTickRate();
}
Just make sure you don't add any non-static member variables there!!!

Where it says this:

#ifndef MYPACKAGE_API
#define MYPACKAGE_API DLL_IMPORT
#endif

replace "DLL_IMPORT" with "DLL_EXPORT" since we want to export symbols from our new dll. Alternatively you can also go to the project properties and under "C/C++ -> Preprocessor -> Preprocessor definitions" add "MYPACKAGE_API=DLL_EXPORT" that's a better and cleaner solution since you don't have to change it every time the files are generated.

Also in "MyPackageClasses.h" you need to include the necessary headers. Which ones depends on what classes you're using. I created a subclass of "AActor" (C++ classes have the prefix 'A' for subclasses of Actor, 'U' for subclasses of Object and 'F' for everything else) which means that I have to include the headers of "Engine" since that's where Actor is defined. So add this line above your class definition:

#include "../../Engine/Inc/Engine.h"    //This also includes "Core"

Now create the body of your native function in the .cpp file. The signature looks like this:

void execTestNativeFunc(FFrame& Stack, void* Result);

Notice how Unreal automatically generated the prefix "exec". This is only to differentiate between functions that are callable from UnrealScript and others. Also look at the parameters. Although we declared it to take in a string in UnrealScript it now has different types as parameters. The function signatures for all native functions are exactly the same. The parameters that are passed in UnrealScript are contained in the "Stack" which is of the type "FFrame".

The implementation for my function looks like this:

void ATestNativeClass::execTestNativeFunc(FFrame& Stack, void* Result){
	P_GET_STR(StringVar);
	P_FINISH;

	GLog->Logf("Native Function called from UnrealScript. Param: %s", *StringVar);

	TestInt = 12345;
}

In the function body we use the "P_GET_STR" macro to grab the string that was passed to the function in UnrealScript from the stack. This macro creates a variable of type 'FString' with the name you provided as a parameter which is why I can use 'StringVar' later in the function. There are also other macros for different parameter types like "P_GET_INT", "P_GET_FLOAT", "P_GET_ACTOR", etc... You can find all of them in "Core/Inc/unscript.h".

It is also important to always get all the parameters that were passed in UnrealScript or else the game will crash. For example: If my function also had another parameter of type int but I only used 'P_GET_STR' it would crash. I have to do 'P_GET_STR' and 'P_GET_INT' in the order the parameters are defined in UnrealScript. Another important thing is "P_FINISH". Always use it when you're done grabbing parameters off the stack. You need it even if your function does not have any parameters. In that case just write 'P_FINISH' at the beginning.

This function uses the global logger object to write a message to the log file showing that it was called. It also prints out the string we passed in UnrealScript. I'm using the overloaded '*' operator on an object of 'FString' to get the c-string (char*) representation of it since that is needed for the formatted logging function. Also, some random value is assigned to 'TestInt' so that it can be printed out from UnrealScript to show that it is in fact the same.

Another important thing to remember is that the UnrealScript and C++ classes must be identical when it comes to class size and memory layout. Don't ever add a member variable in C++ that doesn't exist in UnrealScript, don't remove one either or change the order they're defined in. Always generate the new headers and source files when you've made any changes to the script.

Compile your dll by pressing F7. If there are no errors it should be created in the 'GameData\System' folder. Open the UnrealEditor and place your new native class in a map. Now save your map and launch it with the "-log" command-line parameter and the following should show up in the log window (and in the log file too obviously...):

Result.PNG

As you can see, the function is being called and prints our message to the log. It also modifies the "TestInt" variable which then is printed out from UnrealScript.

Returning a Value from a Native Function

Native functions sometimes perform calculations and return the result to UnrealScript. You can't use the 'return' keyword for that, instead you have to modify the 'Result' parameter that every native function has. It’s a pointer to void, so you first have to cast it to your function’s return type and then assign a value. For example, if you wanted to return an integer you would do the following:

*static_cast<INT*>(Result) = 12345; //Just some random value...

Code

My finished C++ code looks like this:

MyPackageClasses.h:

/*===========================================================================
    C++ class definitions exported from UnrealScript.
    This is automatically generated by the tools.
    DO NOT modify this manually! Edit the corresponding .uc files instead!
===========================================================================*/

#ifndef MYPACKAGE_NATIVE_DEFS
#define MYPACKAGE_NATIVE_DEFS

#if SUPPORTS_PRAGMA_PACK
#pragma pack (push,4)
#endif

#define MYPACKAGE_API DLL_EXPORT

#ifndef MYPACKAGE_API
#define MYPACKAGE_API DLL_IMPORT
#endif

#include "../../Engine/Inc/Engine.h"

class TEST_API ATestNativeClass : public AActor{
public:
    INT TestInt;

    void execTestNativeFunc(FFrame& Stack, void* Result);

    DECLARE_CLASS(ATestNativeClass,AActor,0,MyPackage)
    NO_DEFAULT_CONSTRUCTOR(ATestNativeClass)
    DECLARE_NATIVES(ATestNativeClass)
};

#if SUPPORTS_PRAGMA_PACK
#pragma pack (pop)
#endif

#endif // CORE_NATIVE_DEFS

MyPackageClasses.cpp:

/*===========================================================================
    C++ class definitions exported from UnrealScript.
    This is automatically generated by the tools.
    DO NOT modify this manually! Edit the corresponding .uc files instead!
===========================================================================*/

#include "TestClasses.h"

IMPLEMENT_PACKAGE(MyPackage)


IMPLEMENT_CLASS(ATestNativeClass);

FNativeEntry<ATestNativeClass> ATestNativeClass::StaticNativeMap[] = {
	MAP_NATIVE(TestNativeFunc,0)
	{NULL,NULL}
};

LINK_NATIVES(ATestNativeClass);

void ATestNativeClass::execTestNativeFunc(FFrame& Stack, void* Result){
	P_GET_STR(StringVar);
	P_FINISH;

	GLog->Logf("Native Function called from UnrealScript. Param: %s", *StringVar);

	TestInt = 12345;
}

Pure Native Classes

Pure native classes are classes that don't have an UnrealScript implementation. They really aren't any different when it comes to C++ code though so you can have a look at how the different classes of the engine are implemented in native code.