天天看点

虚幻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

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