TAGGING ALL THE THINGS!
October 23, 2024
Introduction
Unreal supports splitting a project into modules (or plugins - which are more or less a collection of modules except plugins can contain several modules ). This makes it easier to manage the code base and divide it into logical chunks of code. A plugin ideally will have no dependencies on other plugins or modules if it is to be truly portable. (With the exception of engine dependencies).
Ideally you would want to keep modules from depending on other modules too of course - although sometimes this is not practical. A "sub module" (IE not the main game) module should never depend on the game module.
These modules are stored in dlls. (At least they are on a windows build). So that the modules know about each other a .lib file is also generated. This is essentially a "map" of what is available in the DLL.
The main module then links to the things it needs in the modules. When you add an API tag it creates an entry in the .lib file.
Implementation
To make the process easier unreal automatically defines a macro for this in the IMPLEMENT_MODULE macro. So can then use YOURMODULE_API to tag classes, functions etc.
I know about this _API tag and when to use it – why does this matter?
OK, so you know about the _API tag and have been adding it to your classes. It all links simply fine – what is the issue.
Well, the issue is the .lib file has a finite size. 65535 entries in-fact. This sounds like a lot, but it quickly gets big when you use the _API tag on a class and not functions the whole class is tagged. And when the limit is reached you get an error:
LNK1189 "library limit of 65535 obj exceeded".
Chances are this is not going to happen at the start of your project (if you are lucky, it might not happen at all). But if it does happen it is invariably near the end – as the project has gotten as large as it will … which is of course when you least want to worry about some error that prevents the project from building!
You can inspect the number of entries in a specific .lib with the command:
dumpbin /exports
I need to put the tags on, or it will not link - what is the alternative?
Consider the following class:
UCLASS()
class UAPITaggingTest : public UObject
{
GENERATED_BODY()
public:
void ThePublicFunction();
bool bThePublicBool = false;
private:
void ThePrivateFunction();
bool bThePrivateBool = false;
};
If you tried to instantiate this from the main project module it would fail to link. Likewise, any calls to those functions and variables would fail to link. So, you simply pop an API tag on the class declaration, and all is well. You end up with something like this:
UCLASS()
class YOURMODULE_API UAPITaggingTest : public UObject
With that additional tag all will seem well, and it will now link. However, what is not so obvious is that under the hood what adding that tag does is tag EVERYTHING in the class.
Using dumpbin we can see that in the original version about 3 entries are in the .lib for UAPITaggingTest.
When the YOURMODULE_API tag is added this jumps to 18 entries. Considering the size of the class it is not hard to see that this will quickly get out of hand.
Bear in mind also this will apply to every class in the module that gets tagged.
Furthermore there is no need/little to gain from adding protected and private members to the link library - because you cannot use these externally anyway. (Although protected functions may need the tag when overloading in a derived class). Additionally, statics may need the tag.
Solution
Obviously, you need to be able to link to other modules from the main module. So…
If you wish to inherit from, create an instance of, or create a new object of the class then you will need at least MinimalAPI in the class declaration:
UCLASS(MinimalAPI)
MinimalAPI exposes the constructor/destructor for the class as well as unreal functions such as GetPrivateStaticClass, the declaration that allows you to use YourClass::StaticClass etc.
Adding this will only add around 5 entries - which is somewhat of an improvement over the 18 you get from tagging the class.
Of course, this will not actually help if you want to call any functions in the new class - these will still give you a link error. And this is what the _API tag was really intended for. If you want to call a function or access a public member then you need to tag it. (An exception here is virtual calls or non-static data).
YOURMODULE_API void ThePublicFunction();
Summary
First you should try just not adding a tag at all. In a lot of instances classes in a module are used by that module. These do not need external linkage. And the linker will tell you soon enough if they do via unresolved external errors.
If you see linker errors the next step is to try adding MinimalAPI to the CLASS declaration. This will probably clear some, or all of the errors.
For any remaining linker errors, then you should tag only the function(s)/variable(s) that are showing as unresolved by the linker.