虚幻C++中的细节之类名前面的宏(模块名_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_NonExportedMyClass_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工具帮助我们生成的代码模板)。

总结

MYGAME_API宏是虚幻引擎开发的一个重要部分,它允许我们从类和函数的粒度去控制代码的可见性,构建起模块与模块之间的链接。通过有效地使用这个宏,可以确保代码被正确地链接和导出,从而使其对引擎的其他部分可见和可访问。

  • 10
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Claude的羽毛

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值