文章目錄
- 前言
- 一、觀察自定義的類
- 二、什麼是 MYGAME_API
- 三、為什麼需要 MYGAME_API
- 四、關于 MYGAME_API 的一些小建議
- 總結
前言
虛幻引擎是一個強大的遊戲開發平台,它為我們提供了非常多的工具和技術,使我們能夠構造出具備相當品質的遊戲世界。
在利用虛幻引擎進行開發的過程中,有一點非常重要,就是代碼的可見性,即代碼與代碼之間的如何連接配接,更具體的,我希望集中讨論一下我們每次建立類的時候,類名前面出現的這個宏
子產品名_API
的作用,為什麼需要它,它起什麼作用,應該怎樣使用它。
這就是本篇文章要展示的細節。
一、觀察自定義的類
利用引擎的代碼建立功能或者編輯器的快捷代碼模闆建立功能,我們可以很快得建立出一個繼承
UObject
的類
// MyCustomObject.h
#include "CoreMinimal.h"
#include "UObject/Object.h"
#include "MyCustomObject.generated.h"
UCLASS()
class MYGAME_API UMyCustomObject : public UObject
{
GENERATED_BODY()
public:
UPROPERTY(EditAnywhere, Category = "Custom Object")
int32 MyCustomInt;
UPROPERTY(EditAnywhere, Category = "Custom Object")
FString MyCustomString;
UFUNCTION(BlueprintCallable, Category = "Custom Object")
void MyCustomFunction();
};
// MyCustomObject.cpp
#include "MyCustomObject.h"
void UMyCustomObject::MyCustomFunction()
{
UE_LOG(LogTemp, Warning, TEXT("MyCustomFunction called!"));
}
這算是非常簡單的一個自定義類,其中
UMyCustomObject
繼承自
UObject
,它添加了一些自定義的屬性和函數,這些内容都可以通過藍圖進行編輯或調用。類内的
GENERATED_BODY
宏用來生成一些用于類管理的模闆代碼。
這隻是一個簡單的示例,本篇文章真正要讨論的核心在
class
關鍵字和類名
UMyCustomObject
之間的
MYGAME_API
,也就是開篇中我們提到的
子產品名_API
。
後文中我将統一使用
MYGAME_API
這一具體例子來指代這個概念。
二、什麼是 MYGAME_API
在長期的開發中,我們似乎已經習慣了引擎和IDE幫助我們生成這樣模闆的代碼,以至于始終沒有去正視
MYGAME_API
這個宏的作用。我們更願意相信它是虛幻代碼編寫中一個再通常不過的習慣,就算在虛幻的源碼中,有時候類名前有它,有時候又沒有它,更多人願意騙自己說它就是一個可有可無的東西。
但是真的是這樣的嗎?
從單純c++的角度分析,
MYGAME_API
就是一個預處理指令,是一個定義為空的宏。如果了解過虛幻的反射系統,或者UHT,就基本明白這個空的宏意味着什麼——它用來占位,用來給一些内容打标記,以便在預編譯的時候替換進來一些真正起作用的東西。
在虛幻引擎的代碼中,
MYGAME_API
被用來标記一些類以及函數,進而将這些類和函數從子產品或者是插件中公布出去,即被
MYGAME_API
标記的類和函數可以允許被其他子產品或者插件通路,也就是我們前文提到的代碼的可見性和連接配接的問題。
三、為什麼需要 MYGAME_API
就如前面所說,
MYGAME_API
的作用就是将原本别的地方通路不到的類和函數公布給他們,進而實作代碼的連接配接。我們用一個例子來直覺得感受一下。
建立一個插件,起名為MyGame
沒有用
MYGAME_API
标記的類
#include "CoreMinimal.h"
class MyClass_NonExported
{
public:
void MyFunction();
};
使用
MYGAME_API
标記的類
#include "CoreMinimal.h"
class MYGAME_API MyClass_Exported
{
public:
void MyFunction();
};
另外建立一個插件,起名
OtherPluginDependonMyGame
,在其子產品下試圖編寫通路
MyClass_NonExported
和
MyClass_Exported
的代碼:
void UTestObject::MyFunction()
{
Class_Exported = new MyClass_Exported;
Class_NonExported = new MyClass_NonExported;
Class_Exported->MyFunction();
Class_NonExported->MyFunction();
}
![](https://img.laitimes.com/img/9ZDMuAjOiMmIsIjOiQnIsIyZuBnLmRTYiZ2MwYDMlFWMkZjN4YWOjRTOzIWNzMDO2QGZkN2Lc52YucWbp5GZzNmLn9Gbi1yZtl2Lc9CX6MHc0RHaiojIsJye.png)
其他的子產品可以通路
MyClass_Exported
的内容,而通路不到
MyClass_NonExported
的内容。
MYGAME_API
确實幫助我們控制了代碼對外的可見性。
在UBT的源碼中,有這樣的一段
// Add the import or export declaration for the module
if (Rules.Type == ModuleRules.ModuleType.CPlusPlus)
{
if(Rules.Target.LinkType == TargetLinkType.Monolithic)
{
if (Rules.Target.bShouldCompileAsDLL && (Rules.Target.bHasExports || Rules.ModuleSymbolVisibility == ModuleRules.SymbolVisibility.VisibileForDll))
{
Definitions.Add(ModuleApiDefine + "=DLLEXPORT");
}
else
{
Definitions.Add(ModuleApiDefine + "=");
}
}
else if(Binary == null || SourceBinary != Binary)
{
Definitions.Add(ModuleApiDefine + "=DLLIMPORT");
}
else if(!Binary.bAllowExports)
{
Definitions.Add(ModuleApiDefine + "=");
}
else
{
Definitions.Add(ModuleApiDefine + "=DLLEXPORT");
}
}
這裡就是一段關于如何實作子產品代碼可見性的一點小小的内容:根據标記,UBT在編譯時為子產品添加導入導出的聲明,通過這樣一種方式在DLL中公布或者不公布子產品的某些内容。
通過這樣一個小小的特性,我們可以很好的控制外部對自己編寫的子產品的通路權限。
四、關于 MYGAME_API 的一些小建議
在虛幻引擎代碼中使用
MYGAME_API
宏相對簡單。以下是有效使用宏的一些準則:
- 建立新類或函數時,請確定在聲明中包含`MYGAME_API``宏。
- 如果要建立插件或子產品,請確定将
宏包含在需要導出并可用于引擎其他部分的所有類和函數中。MYGAME_API
- 如果使用的是需要
宏的第三方庫或插件,請確定将其也包含在代碼中。MYGAME_API
即使這篇文章讀完都沒能讓你明白
MYGAME_API
的确切意圖是什麼,在建立新類或函數時,最好遵循Unreal Engine代碼所使用的慣例(即虛幻代碼建立工具和IDE工具幫助我們生成的代碼模闆)。
總結
MYGAME_API
宏是虛幻引擎開發的一個重要部分,它允許我們從類和函數的粒度去控制代碼的可見性,建構起子產品與子產品之間的連結。通過有效地使用這個宏,可以確定代碼被正确地連結和導出,進而使其對引擎的其他部分可見和可通路。