前言
虚幻引擎是一个强大的游戏开发平台,它为我们提供了非常多的工具和技术,使我们能够构造出具备相当质量的游戏世界。
在利用虚幻引擎进行开发的过程中,有一点非常重要,就是代码的可见性,即代码与代码之间的如何连接,更具体的,我希望集中讨论一下我们每次创建类的时候,类名前面出现的这个宏模块名_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();
}
其他的模块可以访问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工具帮助我们生成的代码模板)。文章来源:https://www.toymoban.com/news/detail-715871.html
总结
MYGAME_API
宏是虚幻引擎开发的一个重要部分,它允许我们从类和函数的粒度去控制代码的可见性,构建起模块与模块之间的链接。通过有效地使用这个宏,可以确保代码被正确地链接和导出,从而使其对引擎的其他部分可见和可访问。文章来源地址https://www.toymoban.com/news/detail-715871.html
到了这里,关于虚幻C++中的细节之类名前面的宏(模块名_API)的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!