UE4C++基础(三)

本文介绍了UE4中C++如何使用静态库和动态库,包括导入库文件和调用库函数的步骤。接着讨论了断言的种类及其在调试中的作用。此外,文章还详细讲解了Json、Xml和ini文件的读写操作。最后,探讨了蓝图和C++中异步节点的使用以及不同类型转换的方法。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

UE4C++基础(三)

一.静态库和动态库

静态库
  • 将静态库编译的.lib文件和.h头文件放入指定的文件夹

这里我选择在项目目录下新建一个ThirdParty文件夹,将.lib放入ThirdParty/Lib文件夹中,.h放进ThirdParty/Include文件夹中。

  • 打开YourProjectName.Build.cs文件编写导入静态库的代码
// Copyright Epic Games, Inc. All Rights Reserved.
using System.IO;
using UnrealBuildTool;

public class YourModuleName : ModuleRules
{
	private string ModulePath
	{
		get
		{
			return ModuleDirectory;	
		}
	}


	private string ThirdPartyPath
	{
		get
		{
			return Path.GetFullPath(Path.Combine(ModulePath, "../../ThirdParty"));
		}
	}

	public YourModuleName(ReadOnlyTargetRules Target) : base(Target)
	{
		... 
		// 依赖的头文件
		PublicIncludePaths.Add(Path.Combine(ThirdPartyPath, "Include"));
        // 依赖的静态库或导入库
        PublicAdditionalLibraries.Add(Path.Combine(ThirdPartyPath, "Lib", "LibTest.lib"));
       
    }
}
  • 在需要调用的地方引入头文件,调用静态库函数
#include "../../ThirdParty/Include/MyLib.h"

void YourFunctionName()
{
    // 静态库函数
	YourStaticLibFunctionName();
}
动态库
  • 编写代码,将代码标记上导出动态库的标识符,然后编译成动态库。
//.h
#pragma once
#define DLL_EXPORT __declspec(dllexport)
#ifdef __cplusplus
extern "C"
{
#endif
	float DLL_EXPORT GetCircleArea(float radius);
#ifdef __cplusplus
}
#endif 

//.cpp
#include "MyDLLTest.h"
float DLL_EXPORT GetCircleArea(float radius)
{
    return radius*radius*3.14159;
}
  • 导出动态库后,将得到的.dll文件放在指定的文件夹,这里我放在ThirdParty/DLL文件夹中。
  • 加载.dll文件夹,得到动态库文件句柄后,通过函数指针调用动态库函数。
typedef float(*_DLLExportFunctor)(float radius);

void DynamicLibTest()
{
	FString DLLPath = FPaths::Combine(*FPaths::ProjectDir(),TEXT("ThirdParty/"), TEXT("DLL/"), TEXT("DLLTest.dll"));
	if (FPaths::FileExists(DLLPath)) {
		void* DLLHandle = nullptr;
		DLLHandle = FPlatformProcess::GetDllHandle(*DLLPath);
		if (DLLHandle) {
			_DLLExportFunctor ExportFunctor = nullptr;
			FString ProcessName = "GetCircleArea";
			ExportFunctor = (_DLLExportFunctor)FPlatformProcess::GetDllExport(DLLHandle, *ProcessName);
			if (ExportFunctor) {
				UE_LOG(LogTemp, Warning, TEXT("DynamicLib Circle Area:%f"), ExportFunctor(20.f));
			}
		}
	}
}

二.断言

  • 简单断言
check(param);

其中param为空指针或者为false,断言就会被执行到。

  • 额外信息断言
verifyf(param,TEXT("..."),__VARGS__);

checkf(param,TEXT("..."),__VARAGS__);

在简单断言的基础上多了打印信息的功能。

  • 阻塞流程断言
checkNoEntry();

使用这个断言的函数体只要被执行就会被断言。

checkNoReentry();

使用这个断言的函数体被执行了两次才会被断言。

checkNoRecursion();

检测到无限递归的断言。

  • 断点式断言
ensure(param);

ensureMesgf(param,TEXT("..."),__VARAGS__);

模拟断点的断言,不会产生崩溃。

三.Json、Xml、ini文件读写

一.Json

项目Build.cs中添加模块Json、JsonUtilities

Json的数据格式如下:

{
	"Name":"张三", // 字符串
	"Age":18,	// 整数
	"体重":62.1, // 浮点数
	"结婚":true, // 逻辑值
    
    // 数组用[]表示
    // {} 表示对象
    "银行卡":[
        {
           "银行名":"农行",
            "数量":1
        },
        {
           "银行名":"工行",
            "数量":1
        },
        {
           "银行名":"邮政银行",
            "数量":1
        },
    ]"余额":[
    	52,13.2,100
    ]
}
  • 设置字段
TSharedPtr<FJsonObject> JsonObjectRoot = MakeShareable(new FJsonObject());

JsonObjectRoot->SetBoolField("bMan", true);
JsonObjectRoot->SetStringField("Id", "522201199811060050");
JsonObjectRoot->SetStringField("Name", "UExplorer");
TSharedPtr<FJsonObject> SubObj = MakeShareable(new FJsonObject());
SubObj->SetStringField("Company", "HongYu");
SubObj->SetNumberField("Salary", 9000);
JsonObjectRoot->SetObjectField("Work", SubObj);
  • 设置array
// Set json string array
TArray<TSharedPtr<FJsonValue>> OverageValus;

{
    TSharedPtr<FJsonValueString> StringValue1 = MakeShared<FJsonValueString>(TEXT("1000"));
    TSharedPtr<FJsonValueString> StringValue2 = MakeShared<FJsonValueString>(TEXT("2000"));
    TSharedPtr<FJsonValueString> StringValue3 = MakeShared<FJsonValueString>(TEXT("3000"));

    OverageValus.Add(StringValue1);
    OverageValus.Add(StringValue2);
    OverageValus.Add(StringValue3);
    JsonObjectRoot->SetArrayField("Overage", OverageValus);
}

// Set json object array
TArray<TSharedPtr<FJsonValue>> Family;
{
    TSharedPtr<FJsonObject> p1 = MakeShareable(new FJsonObject());
    p1->SetNumberField("Age", 20);
    p1->SetStringField("Name", "Ann");

    TSharedPtr<FJsonObject> p2 = MakeShareable(new FJsonObject());
    p2->SetNumberField("Age", 10);
    p2->SetStringField("Name","WangXianer");

    TSharedPtr<FJsonValueObject> n_p1 = MakeShareable(new FJsonValueObject(p1));
    TSharedPtr<FJsonValueObject> n_p2 = MakeShareable(new FJsonValueObject(p2));
    Family.Add(n_p1);
    Family.Add(n_p2);
    JsonObjectRoot->SetArrayField("Family", Family);
}
  • Json的序列化
// Json serialize
FString OutputStr;
TSharedRef<TJsonWriter<>> JsonWriter = TJsonWriterFactory<>::Create(&OutputStr);
FJsonSerializer::Serialize(JsonObjectRoot.ToSharedRef(), JsonWriter);
// Save json to file
FFileHelper::SaveStringToFile(OutputStr,TEXT("YourSavePath\\Info.Json"));
  • 反序列化
TSharedRef<TJsonReader<>> JsonReader = TJsonReaderFactory<>::Create(OutputStr);
TSharedPtr<FJsonObject> DeserializeObject = MakeShared<FJsonObject>();
FJsonSerializer::Deserialize(JsonReader,DeserializeObject);
  • 解析Json
//使用Json中的get方法来进行解析
if (DeserializeObject.IsValid()) {
	bool IsMan = DeserializeObject->GetBoolField("bMan");
	FString Id = DeserializeObject->GetStringField("Id");
	TArray<TSharedPtr<FJsonValue>> Members = DeserializeObject->GetArrayField("Family");
for (const auto& Member : Members) {
	TSharedPtr<FJsonObject> MemberPure = Member->AsObject();
	UE_LOG(LogTemp, Warning, TEXT("%f %s"), MemberPure->GetNumberField("Age"), *MemberPure->GetStringField("Name"));
	}
}
  • 加载Json文件和写入Json文件
// Save json to file
FFileHelper::SaveStringToFile(OutputStr,TEXT("YourSavePath\\Info.Json"));

// Load json file
FString NewjsonStr;
FString JsonPath(TEXT("YourSavePath\\Info.Json"));
FFileHelper::LoadFileToString(NewjsonStr,*JsonPath);
二.Xml

在项目模块中添加XmlParser,Xml语法如下:

<root>
    <child>
        <subchild>....</subchild>
    </child>
</root>
  • 属性值必须加引号
<note date="12/11/2007"></note>

在UE4中解析XML

在UE4中解析XML

使用FXmlFileFXmlNode这两个类。

例子:

<bookstore>
    <book category="COOKING">
        <title lang="en">Everyday Italian</title>
        <author>Giada De Laurentiis</author>
        <year>2005</year>
        <price>30.00</price>
    </book>
    <book category="CHILDREN">
        <title lang="en">Harry Potter</title>
        <author>J K. Rowling</author>
        <year>2005</year>
        <price>29.99</price>
    </book>
    <book category="WEB">
        <title lang="en">Learning XML</title>
        <author>Erik T. Ray</author>
        <year>2003</year>
        <price>39.95</price>
    </book>
</bookstore>
bool ReadXmlFile(const FString& InXmlFilePath) {
	// Check file path is valid
	IPlatformFile& PlatformFile = FPlatformFileManager::Get().GetPlatformFile();
	if (!PlatformFile.FileExists(*InXmlFilePath)) {
		UE_LOG(LogTemp, Warning, TEXT("Does not exists such file path!"));
		return false;
	}

	TSharedPtr<FXmlFile> XmlFile = MakeShareable(new FXmlFile(InXmlFilePath));
	if (XmlFile->IsValid()) {
		FXmlNode* RootNode = XmlFile->GetRootNode();
		for (FXmlNode* ChildNode : RootNode->GetChildrenNodes()) {
			UE_LOG(LogTemp, Warning, TEXT("%s"), *ChildNode->GetAttribute("category"));
			for (FXmlNode* SubChildNode : ChildNode->GetChildrenNodes()) {
				UE_LOG(LogTemp, Warning, TEXT("%s"), *SubChildNode->GetAttribute("lang"));
				FString Content = SubChildNode->GetContent();
				UE_LOG(LogTemp, Warning, TEXT("Content: %s"), *Content);
			}
		}
		return true;
	}
	else {
		return false;
	}	
}
三.ini

ini俗称Config文件,基本格式如下:

[KeyName]
VarName = Value

UE4中通过GConfigini文件进行读取。

bool ReadConfigFile(FString FilePath){
    FString ConfigPath = FilePath;
    bool ReadSuccess = false;
    IPlatformFile& PlatformFile = FPlatformFileManager::Get().GetPlatformFile();
    if(PlatformFile.FileExists(*ConfigPath)){
        FString Info;
        ReadSuccess = GConfig->GetString(KeyName,VarName,Info,ConfigPath);
    }
    return ReadSuccess
}

四. 异步节点

一.蓝图调用

创建一个继承自UFunctionLibrary的类。

// .h
#pragma once
#include "CoreMinimal.h"
#include "Kismet/BlueprintFunctionLibrary.h"
#include "LatentLibrary.generated.h"

UENUM(BlueprintType)
enum class EDelayExec :uint8 {
	HalfExec,
	CompleteExec
};
UCLASS()
class REFLECTLEARNING_API ULatentLibrary :public UBlueprintFunctionLibrary
{
	GENERATED_BODY()
public:
	UFUNCTION(BlueprintCallable,meta=( ExpandEnumAsExecs = "Exec", Latent,LatentInfo="ActionInfo" ,HidePin="WorldContextObject" , DefaultToSelf="WorldContextObject"))
		static void TwiceDelay(UObject* WorldContextObject, EDelayExec& Exec,struct FLatentActionInfo ActionInfo,float Duration);

};

其中的FLatentActionInfo因为是在蓝图端调用,可以由系统自动传递进来。

//.cpp
#include "LatentActions.h"

class FTwiceDelayLatent :public FPendingLatentAction
{
public:
	FTwiceDelayLatent(float Duration,
		const FLatentActionInfo& ActionInfo,
		EDelayExec& DelayExec) :TotalTime(Duration), TimeRemaining(Duration),
		ExecutionFunction(ActionInfo.ExecutionFunction), OutputLink(ActionInfo.Linkage), 
		CallbackTarget(ActionInfo.CallbackTarget), ExecRef(DelayExec), bTriggerHalf(false) {}


	virtual void UpdateOperation(FLatentResponse& Response)
	{
		TimeRemaining -= Response.ElapsedTime();
		if (TimeRemaining < TotalTime / 2.f && !bTriggerHalf) {
			ExecRef = EDelayExec::HalfExec;
			Response.TriggerLink(ExecutionFunction, OutputLink, CallbackTarget);	//回调函数
			bTriggerHalf = true;
		}
		else if (TimeRemaining <= 0.f) {
			ExecRef = EDelayExec::CompleteExec;
			Response.TriggerLink(ExecutionFunction, OutputLink, CallbackTarget);
			Response.DoneIf(true); //终止函数
		}
	}

private:
	float TotalTime;
	float TimeRemaining;
	FName ExecutionFunction;
	int32 OutputLink;
	FWeakObjectPtr CallbackTarget;
	EDelayExec& ExecRef;
	bool bTriggerHalf;
};

void ULatentLibrary::TwiceDelay(UObject* WorldContextObject, EDelayExec& Exec, struct FLatentActionInfo ActionInfo, float Duration)
{
	if (UWorld* World = GEngine->GetWorldFromContextObject(WorldContextObject)) {
		// 获取LatentActionManager
		FLatentActionManager& LatentActionManager = World->GetLatentActionManager();
		if (LatentActionManager.FindExistingAction<FTwiceDelayLatent>(ActionInfo.CallbackTarget,ActionInfo.UUID)==NULL) {
			LatentActionManager.AddNewAction(ActionInfo.CallbackTarget, ActionInfo.UUID, new FTwiceDelayLatent(Duration,ActionInfo,Exec));
		}
	}
}
二.CPP调用

CPP和蓝图调用主要的区别就是,FLetentActionInfo变量的获取,蓝图中可以自动由系统传递。在CPP中我们需要自行声明。

int32 LinkAge = 100;
int32 UUID = 9;
FLatentActionInfo ActionInfo(LinkAge,UUID,TEXT("LatentActionCall"),this);

FLatentActionManager& ActionManager = GetWorld()->GetLatentActionManager();
if (ActionManager.FindExistingAction<FLatentCallInCPP>(ActionInfo.CallbackTarget, UUID) == NULL) {
ActionManager.AddNewAction(ActionInfo.CallbackTarget, UUID, new FLatentCallInCPP(ActionInfo,FOnLatentComplete::CreateUObject(this, &AAsyncLoadActor::LatentActionObjectCall)));

其中LinkAgeUUID的随意指定一个你不会重复用到的int32类型值就行。其余关于继承FPendingLatentAction子类的实现,就大同小异了。

class FLatentCallInCPP :public FPendingLatentAction
{
public:
	FLatentCallInCPP(const FLatentActionInfo& ActionInfo, FOnLatentComplete InDelegate)
		:CallBackTarget(ActionInfo.CallbackTarget), 
		ExecuFunction(ActionInfo.ExecutionFunction), 
		LinkAge(ActionInfo.Linkage),
		CompleteDelegate(InDelegate),
		TotalTime(3.f),
		TimeRemaining(3.f){}

	virtual void UpdateOperation(FLatentResponse& Response)
	{
		TimeRemaining -= Response.ElapsedTime();
		if (TimeRemaining < TotalTime / 2.f) {
			Response.TriggerLink(ExecuFunction, LinkAge, CallBackTarget);
			if (TimeRemaining <= 0.f) {
				Response.DoneIf(true);
			}
		}
		else {
			// 委托通知
			CompleteDelegate.Execute(CallBackTarget.Get());
		}
	}
private:
	float TotalTime;
	float TimeRemaining;
	FWeakObjectPtr CallBackTarget;
	FName ExecuFunction;
	int32 LinkAge;
	FOnLatentComplete CompleteDelegate;
};

五. 类型转换

一.FString与基本类型之间的转换
// Fstring to int
int32 IntValue = FCString::Atoi(*Str); 

// FString to float
float floValue = FCString::Atoi(*Str);

// FString to bool
bool BoValue = Str.ToBool();

// FString to FName
FName NewName = FName(*Str);

// FString to FText
FText NewText = FText::FromString(Str);

// Int to FString
FString NewStr = FString::FromInt(NewInt);

// Float to FString
FString NewStr = FString::SanitizeFloat(NewFloat);

// FText to FString
FString NewStr = NewText.ToString();

// FName to FString
FString NewStr = NewName.ToString();
二. 字符之间的转换
// FString to TCHAR
// 方式1:
TCHAR* NewTchar = *Str;
// 方式2:
TCHAR* NewTchar = Str.GetCharArray().GetData();

// TCHAR to char*
char* Newchar = TCHAR_TO_ANSI(NewTchar);	
// 如果是UTF8格式请使用TCHAR_TO_UTF8()
// 同理UTF8_TO_TCHAR()

// Char* to TCHAR
TCHAR* NewTchar = ANSI_TO_TCHAR(Newchar);
三. UEnum的转换
// Enum to FString
template<class TEnum>
FORCEINLINE FString GetEnumValueAsString(const FString& EnumName,TEnum Value){
    UEnum* EnumPtr = FindObject<UEnum>((UObject*)ANY_PACkAGE,*EnumName,true);
    return EnumPtr?EnumPtr->GetNameStringByIndex((int32)Value):"InValid";
}

// FString to Enum
template<class TEnum>
FORCEINLINE TEnum GetEnumValueFromString(const FString& EnumName,FString value){
    UEnum* EnumPtr = FindObject<UEnum>((UObject*)ANY_PACkAGE,*EnumName,true);
    return EnumPtr?EnumPtr->(TEnum)GetIndexByName(FName(*Value)):TEnum(0);
}

// Enum to int32
FORCEINLINE int32 GetEnumIndexFromName(const FString& EnumName, FName InValue) {
    UEnum* EnumPtr = FindObject<UEnum>(ANY_PACKAGE, *EnumName,true);
    return EnumPtr?EnumPtr->GetIndexByName(InValue):-1;
}

六.传递函数的方法

  • TFunction

对应C++标准库中的std::function

void TFunctionTest(){
    TFunction<void(FString,int32)> Functor = [this](FString Str,int32 Val){
    	//to do...    
    };
    //调用
    Functor("Param",10);
}

使用TFunction定义复用接口

template<class RetType,class ...VarTypes>
void TemplateFunction(TFunction<RetType(VarTypes...)> Functor){
    RetType Ret = Functor(VarTypes...);
}
  • TMemFunPtrType

类似于传递类函数指针。

DECLARE_DELEGATE(FOnMemFuncCalled);

void MemFuncCallBack(FString InStr);

template<class UserClass>
void TMemFuncPtrTypeTest(UserClass* Object, typename TMemFunPtrType<false, UserClass,void(FString)>::Type InFunctor)
{
	FOnMemFuncCalled OnMemFuncCalled;
	OnMemFuncCalled.BindUObject(Object, InFunctor, FString("Hello MemFunPtrtype"));
	OnMemFuncCalled.ExecuteIfBound();
}

// 调用
TMemFuncPtrTypeTest<YourClass>(this,&YourClass::MemFuncCallBack);
  • FMethodPtr

TBaseUObjectMethodDelegateInstance里面的一个成员变量。我们使用的是继承他的子类TUObjectMethodDelegate

template <typename UserClass> 
struct TUObjectMethodDelegate:TBaseUObjectMethodDelegateInstance<false, UserClass, FuncType, UserPolicy> 
{ typedef TBaseUObjectMethodDelegateInstance<false, UserClass, FuncType, UserPolicy> Super; 
 TUObjectMethodDelegate(UserClass* InUserObject, typename Super::FMethodPtr InMethodPtr):Super(InUserObject, InMethodPtr){}};

示例:

// 声明委托
DECLARE_DELEGATE(FOnMemFuncCalled)
// 对应的委托参数的函数声明
void CallFunc();
    
template<class UserClass>
void CommonMethodFunc(UserClass* Object,typename FOnMemFuncCalled::TUObjectMethodDelegate<UserClass>::FMethodPtr InMethod){};

// 调用
TestMethodFunc<YourClass>(this, &YourClass::CallFunc);

使用泛型来对绑定的代理、参数和返回值进行定制化。

template<class DelegateType,class UserClass,class ...ParamTypes>
void templateMethodFunc(UserClass* Object,typename DelegateType::template TUObjectMethodDelegate<UserClass>::FMethodPtr InMethod, ParamTypes...Params){}

调用的时候需要把自己的委托的类型传递进去。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值