天天看點

虛幻c++中的細節之類名前面的宏(子產品名_API)前言一、觀察自定義的類二、什麼是 MYGAME_API三、為什麼需要 MYGAME_API四、關于 MYGAME_API 的一些小建議總結

文章目錄

  • 前言
  • 一、觀察自定義的類
  • 二、什麼是 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();
}
           
虛幻c++中的細節之類名前面的宏(子產品名_API)前言一、觀察自定義的類二、什麼是 MYGAME_API三、為什麼需要 MYGAME_API四、關于 MYGAME_API 的一些小建議總結

其他的子產品可以通路

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

宏是虛幻引擎開發的一個重要部分,它允許我們從類和函數的粒度去控制代碼的可見性,建構起子產品與子產品之間的連結。通過有效地使用這個宏,可以確定代碼被正确地連結和導出,進而使其對引擎的其他部分可見和可通路。