UnrealEngineでaws-sdk-cppのメモリ管理をUnrealEngineと合わせる

UneralEngineでaws-sdk-cppを利用する際に、aws-sdk-cppのメモリ管理の方法をUnrealEngineと同じにする方法をメモしておきます。
一応、デバッグ実行でカスタムしたメモリ管理の関数が呼ばれていること、パッケージングしてaws-sdk-cppの機能を利用して問題ないことは簡単に確認しましたが過不足ある可能性があります。

# 確認した環境

* UE5.2.1
* aws-sdk-cpp 1.11.162
* Visual Studio 2022 17.7.3
* cmake 3.26.4-msvc4

# aws-sdk-cppのビルド

基本的には、以前に書いた[UnrealEngineでaws-sdk-cppを利用する - Qiita](https://qiita.com/mechamogera/items/d4f392a2167f4a9a0703)の手順で進めます。

ただし、以下のドキュメントにあるようにaws-sdk-cppのMemoryManagementをカスタムするには、SDKのcmakeで-DCUSTOM_MEMORY_MANAGEMENTオプションを指定してビルドしないといけないようです。
[AWS SDK for C++: Using the SDK](https://sdk.amazonaws.com/cpp/api/LATEST/root/html/md_docs_2_s_d_k__usage__guide.html)
> . If you wish to install your own memory manager to handle allocations made by the SDK, you will need to pass the CUSTOM_MEMORY_MANAGEMENT cmake parameter (-DCUSTOM_MEMORY_MANAGEMENT) as well as define AWS_CUSTOM_MEMORY_MANAGEMENT in all build targets dependent on the SDK.

このため以下のaws-sdk-cppリポジトリのReadme.mdにあるBuilding From Source中の以下の手順で-DCUSTOM_MEMORY_MANAGEMENTオプションを指定するようにします。
[aws-sdk-cppのリポジトリ](https://github.com/aws/aws-sdk-cpp)
> cd < BUILD_DIR >
cmake < path-to-root-of-this-source-code > -DCMAKE_BUILD_TYPE=Debug -DCMAKE_PREFIX_PATH=< path-to-install >

実際に指定して実行した例が以下になります。
参照:[CMake parameters - AWS SDK for C++](https://docs.aws.amazon.com/ja_jp/sdk-for-cpp/v1/developer-guide/cmake-params.html)

```bat
$ cmake . -DCMAKE_BUILD_TYPE=Release -DBUILD_ONLY="s3;core" -DCMAKE_PREFIX_PATH=build -DCUSTOM_MEMORY_MANAGEMENT=1
...
-- Custom memory management enabled; stl objects now using custom allocators
...
```

# USE_AWS_MEMORY_MANAGEMENTの定義

AWSの公式ドキュメントは以下のようにAWS_CUSTOM_MEMORY_MANAGEMENTをdefineしろになっています。
[Memory Management - AWS SDK for C++](https://docs.aws.amazon.com/ja_jp/sdk-for-cpp/v1/developer-guide/memory-management.html)
> Custom memory management is available only if you use a version of the library built using the defined compile-time constant AWS_CUSTOM_MEMORY_MANAGEMENT.

しかし、実際のコードを確認するとUSE_AWS_MEMORY_MANAGEMENTが使われているようです。
https://github.com/aws/aws-sdk-cpp/blob/main/src/aws-cpp-sdk-core/source/Aws.cpp#L31-L36

そのissueもあるようなのですが2021年のもので何も対応されてないみたいです。
https://github.com/aws/aws-sdk-cpp/issues/1589

UnrealEngineのプロジェクトのbuild.csで以下のようにUSE_AWS_MEMORY_MANAGEMENTを定義しておきます。

```c#
PublicDefinitions.Add("USE_AWS_MEMORY_MANAGEMENT");^
```

# MemoryManagerの設定

以下を参考にしてUnrealEngine側でMemoryManagerを定義して利用します。

[AWS SDK for C++: Using the SDK](https://sdk.amazonaws.com/cpp/api/LATEST/root/html/md_docs_2_s_d_k__usage__guide.html)
> MyMemoryManager memoryManager;
> 
> Aws::SDKOptions options;
> options.memoryManagementOptions.memoryManager = &memoryManager;
> Aws::InitAPI(options);
> .....
> Aws::ShutdownAPI(options);

[Memory Management - AWS SDK for C++](https://docs.aws.amazon.com/ja_jp/sdk-for-cpp/v1/developer-guide/memory-management.html)
> class MyMemoryManager : public Aws::Utils::Memory::MemorySystemInterface
> {
> public:
>     // ...
>     virtual void* AllocateMemory(
>         std::size_t blockSize, std::size_t alignment,
>         const char *allocationTag = nullptr) override;
>     virtual void FreeMemory(void* memoryPtr) override;
> };

UnrealEngineのメモリ確保、解放のコードは以下のOpenCVプラグインのコードを参考にできそうです。

https://github.com/EpicGames/UnrealEngine/blob/release/Engine/Plugins/Runtime/OpenCV/Source/OpenCVHelper/Private/OpenCVHelperModule.cpp#L20-L50

上記を参考にしてMemoryManagerを設定した例です。

```cpp
#include "aws/core/utils/memory/MemorySystemInterface.h"

static TSet<void*> UnrealAllocationsSet;
static FCriticalSection UnrealAllocationsSetMutex;

// MemoryManager定義
class MyMemoryManager : public Aws::Utils::Memory::MemorySystemInterface
{
public:
	// ...
	virtual void* AllocateMemory(
		std::size_t blockSize, std::size_t alignment,
		const char* allocationTag = nullptr) override;
	virtual void FreeMemory(void* memoryPtr) override;
	virtual void Begin() override {};
	virtual void End() override {};
};

// メモリ確保
void* MyMemoryManager::AllocateMemory(std::size_t blockSize, std::size_t alignment, const char* allocationTag)
{
	void* Address = FMemory::Malloc(static_cast<SIZE_T>(blockSize), static_cast<uint32>(alignment));

	{
		FScopeLock Lock(&UnrealAllocationsSetMutex);
		UnrealAllocationsSet.Add(Address);
	}

	return Address;
}

// メモリ解放
void MyMemoryManager::FreeMemory(void* memoryPtr)
{
	{
		FScopeLock Lock(&UnrealAllocationsSetMutex);

		if (!UnrealAllocationsSet.Contains(memoryPtr))
		{
			return;
		}

		UnrealAllocationsSet.Remove(memoryPtr);
	}

	FMemory::Free(memoryPtr);
}

static MyMemoryManager MemoryManager;

// StartupModuleなどでMemoryManagerを指定して初期化
void FAWSSDKModule::StartupModule()
{
	Aws::SDKOptions options;
	options.memoryManagementOptions.memoryManager = &MemoryManager;
	Aws::InitAPI(options);
```