UE4でaws sdk cppを使ってS3アップロードをするプラグインを作ってみた

aws sdk cppを利用してTexture2DをpngでAWS S3に[uuid].pngの名前でアップロード（[Bucket名は決め打ち](https://github.com/mechamogera/UE4S3UploadPlugin/blob/41c80a4446f6c6cc274a74f09e44714c2e21f2de/Plugins/AWSSDK/Source/AWSSDK/Public/S3UploadProxy.h#L40)）しPre-SignedURLを取得する機能を提供するプラグインを作成してみました。
Windows64bit環境にしか対応してません。
提供された機能はBlueprintで以下のように利用できます。

![bpuploadtexture.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/16768/cbe2afaa-c9dd-2de5-766e-3393518d05ae.png)

サンプルプロジェクトは以下に載せてみています。
https://github.com/mechamogera/UE4S3UploadPlugin

ここではプラグイン作成についてメモします。

2022/10/11追記
aws-sdk-cppの更新をしようとしてはまった内容を以下の記事にまとめました。

https://qiita.com/mechamogera/items/d4f392a2167f4a9a0703

# 利用環境

* UE4.23.1
* aws-sdk-cpp 1.7.235

# aws sdk cppのビルド

基本的にはhttps://github.com/aws/aws-sdk-cpp のReadme参照

1. GitForWindowsなどでコマンドプロンプトでgitを使えるようにしておく(cmake中に依存をcloneするため)。
1. Githubからaws-sdk-cppを取得し適切な場所(msbuildの際パスが260文字以上になると失敗する)に配置。
1. スタートメニューからVisualStudio2017フォルダ中のDeveloper Command Promptを起動する。
1. cmake <path-to-root-of-this-source-code> -G "Visual Studio 15 Win64" -DCMAKE_BUILD_TYPE=Release　を実行する。
1. <path-to-root-of-this-source-code>に移動してmsbuild INSTALL.vcxproj /p:Configuration=Release　を実行する。

## aws sdk cppのビルドでまったところ

ビルドの際にC2220エラーが出るので聞いてみたら丁寧に教えてもらえました。
https://github.com/aws/aws-sdk-cpp/issues/1286
結局テスト時の失敗だったので無視することにしました。

# プラグインの作成

UE4のPluginsの設定からNew PluginでBlankのTemplateを利用してベースを作成しています。

## プラグインの構成

プラグインの構成は以下のようになります。

```text
Plugins/AWSSDK/
├── AWSSDK.uplugin
├── Binaries
│   └── Win64
│       ├── aws-c-common.dll <= [aws sdk cpp]/binからコピー
│       ├── aws-c-event-stream.dll <= [aws sdk cpp]/binからコピー
│       ├── aws-checksums.dll <= [aws sdk cpp]/binからコピー
│       ├── aws-cpp-sdk-core.dll <= [aws sdk cpp]/binからコピー
│       ├── aws-cpp-sdk-s3.dll <= [aws sdk cpp]/binからコピー
├── Content
├── Resources
│   └── Icon128.png
├── Source
│   └── AWSSDK
│       ├── AWSSDK.Build.cs
│       ├── Private
│       │   ├── AWSSDK.cpp
│       │   └── S3UploadProxy.cpp
│       └── Public
│           ├── AWSSDK.h
│           └── S3UploadProxy.h
└── ThirdParty
    └── awssdk
        ├── include <= [aws sdk cpp]/aws-cpp-sdk-core/includeと[aws sdk cpp]/aws-cpp-sdk-s3/includeからコピー
        │   └── aws
        │       ├── core
        │       └── s3
        └── lib
            ├── aws-cpp-sdk-core.lib <= [aws sdk cpp]/aws-cpp-sdk-core/Releaseからコピー
            └── aws-cpp-sdk-s3.lib　<= [aws sdk cpp]/aws-cpp-sdk-s3/Releaseからコピー
```

## AWSクレデンシャル

AWSクレデンシャルはビルド時に環境変数を読み込んで利用するようにしています。
[Plugins/AWSSDK/Source/AWSSDK/AWSSDK.Build.cs](https://github.com/mechamogera/UE4S3UploadPlugin/blob/41c80a4446f6c6cc274a74f09e44714c2e21f2de/Plugins/AWSSDK/Source/AWSSDK/AWSSDK.Build.cs#L88-L91)

以下の記事を参照しています。
* [[UE4] 環境変数を利用してビルドオプションを変更する｜株式会社ヒストリア](http://historia.co.jp/archives/3636/)
* [UE4で開発者毎に固有のマクロ定義を宣言する(俺マクロ) - Qiita](https://qiita.com/suzuki_takashi/items/634980f96a73ee434469)

```cpp:AWSSDK.Build.cs
string AwsAccessKeyId = System.Environment.GetEnvironmentVariable("AWS_ACCESS_KEY_ID");
string AwsSecretAccessKey = System.Environment.GetEnvironmentVariable("AWS_SECRET_ACCESS_KEY");
Definitions.Add(string.Format("AWS_ACCESS_KEY_ID=\"{0}\"", AwsAccessKeyId));
Definitions.Add(string.Format("AWS_SECRET_ACCESS_KEY=\"{0}\"", AwsSecretAccessKey));
```

本当はクレデンシャルを直に利用するよりCognitoなどを利用する方が良さそうです。

## Blueprintの提供について

UOnlineBlueprintCallProxyを利用してDelegateを直に利用しなくても良いようにして提供しています。
[Plugins/AWSSDK/Source/AWSSDK/Public/S3UploadProxy.h](https://github.com/mechamogera/UE4S3UploadPlugin/blob/41c80a4446f6c6cc274a74f09e44714c2e21f2de/Plugins/AWSSDK/Source/AWSSDK/Public/S3UploadProxy.h)

[WebApi プラグインの設計思想 & 実装Tips - ほげたつブログ](http://hogetatu.hatenablog.com/entry/2016/12/01/000000)あたりを参照しています。

## 非同期処理

aws sdk cppにも非同期なメソッドが用意されていますがひとまず今回はUE4が用意している非同期処理を使ってみました。
書籍「C++でつくるUnreal Engineアプリ開発 for Windows & macOS」を参照してAsyncメソッドを使っているのですが、最近使い方が変わったようではまっていました。
[4.23 breaks my Async code... - Unreal Engine Forums](https://forums.unrealengine.com/development-discussion/c-gameplay-programming/1668529-4-23-breaks-my-async-code)見て対応できました。

[Plugins/AWSSDK/Source/AWSSDK/Private/S3UploadProxy.cpp](https://github.com/mechamogera/UE4S3UploadPlugin/blob/41c80a4446f6c6cc274a74f09e44714c2e21f2de/Plugins/AWSSDK/Source/AWSSDK/Private/S3UploadProxy.cpp#L50-L80)

```cpp:S3Upload.cpp
TUniqueFunction<void()> Task = [this, BucketName, ObjectName, ImageData, RegionStr]() {
   // 略
};
auto Result = Async(EAsyncExecution::ThreadPool, MoveTemp(Task));
```

### 非同期処理ではまったこと1

aws sdk cppのサンプルを参考にして以下のようなコードを書いているとエディタが落ちました。

```cpp
Aws::SDKOptions options;
Aws::InitAPI(options);
{
    // 非同期処理呼び出し
}
Aws::ShutdownAPI(options);
```
非同期処理を呼び出した後にShutdownAPI()してしまうのでS3アップロードのためのPutObject()が失敗するというオチでしたがしばらくはまりました。
[AWS SDK for C++をマルチスレッドで利用する際の注意点 - Qiita](https://qiita.com/kai_kou/items/d142493ed25f08aecac6)を参照しました。

結局InitAPI()とShutdownAPI()の処理をStartupModule()とShutdownModule'(で実行するようにしました。
[Plugins/AWSSDK/Source/AWSSDK/Private/AWSSDK.cpp](https://github.com/mechamogera/UE4S3UploadPlugin/blob/41c80a4446f6c6cc274a74f09e44714c2e21f2de/Plugins/AWSSDK/Source/AWSSDK/Private/AWSSDK.cpp)

### 非同期処理ではまったこと2

以下のような感じでUtexture2Dからpngへ変換したデータを保持するTArrayを非同期タスクの呼び出し前にローカルに宣言してポインタだけ渡していたため、ローカル変数が破棄されてポインタが指す正常でない値をpngとして扱っていました。

```cpp
Task()
{
    TArray<uint8> Buffer;
    // Utexture2Dからpngへ変換してBufferにしまう
    AsyncTask((char*)Buffer.GetData(), Buffer.Num());
}

void AsyncTask(char* Buffer, int BufferSize)
{

}
```

## UTexture2Dからpngへの変換

以下のサイトのコードを参照してオンメモリでUTexture2Dからpngへの変換を行っています。
[UTexture2D 转为png - qq_26384541的博客](https://blog.csdn.net/qq_26384541/article/details/79626658)

## パッケージ化

パッケージ化するとaws sdk cppのdllをexeと同じ場所におかないとアプリが実行できないという問題が起きました。
[How do you link to a DLL from a plugin? - UE4 AnswerHub](https://answers.unrealengine.com/questions/262233/how-do-you-link-to-a-dll-from-a-plugin.html)を参照してFPlatformProcess::GetDllHandle()を使ってDLLをロードするようにして解消できました。
なお、DLLは依存順（依存のないDLLから読み込みし、読み込まれたDLLに依存しているDLLを読み込んでいく）にGetDllHandle()していく必要があります。

[Plugins/AWSSDK/Source/AWSSDK/Private/AWSSDK.cpp](https://github.com/mechamogera/UE4S3UploadPlugin/blob/41c80a4446f6c6cc274a74f09e44714c2e21f2de/Plugins/AWSSDK/Source/AWSSDK/Private/AWSSDK.cpp#L13-L26)

そういうのも含めて[Build.cs](https://github.com/mechamogera/UE4S3UploadPlugin/blob/41c80a4446f6c6cc274a74f09e44714c2e21f2de/Plugins/AWSSDK/Source/AWSSDK/AWSSDK.Build.cs)の書き方は苦労しました。
（PublicDelayLoadDLLsやRuntimeDependenciesでパスを指定する/しないなど）

## .gitignore

[Plugins/AWSSDK/Binaries/Win64](https://github.com/mechamogera/UE4S3UploadPlugin/tree/41c80a4446f6c6cc274a74f09e44714c2e21f2de/Plugins/AWSSDK/Binaries/Win64)にaws sdk cppのdllを置いているために[.gitignore](https://github.com/mechamogera/UE4S3UploadPlugin/blob/41c80a4446f6c6cc274a74f09e44714c2e21f2de/.gitignore)に以下のような設定を追加してビルドしたプラグインのdllがリポジトリに含まれないようにしています。
（開発中にBinaries/Win64に置いてないとdllがなくてエディタが落ちるからしていたけど最終的に明示的にdllロードするようにしたから他の場所においてもいいかも？）

```text
Plugins/**/BInaries/*
!Plugins/**/Binaries/Win64
Plugins/**/Binaries/Win64/*
!Plugins/**/Binaries/Win64/aws-*.dll
```

[[Git] .gitignoreの仕様詳解 - Qiita](https://qiita.com/anqooqie/items/110957797b3d5280c44f)を参照しています。

## プラグインビルド時にはまったこと

プラグインのコードを変更してビルドする時に以下のようなエラーで失敗することがたびたび起きました。

```bash
cl : Command line error D8022 : cannot open E:\Hoge\Plugins\AWSSDK\Intermediate\Build\Win64\UE4Editor\Development\AWSSDK\Module.AWSSDK.cpp.obj.response'
  @progress 'Compiling C++ source code...' 29%
  [2/7] Microsoft (R) C/C++ Optimizing Compiler Version 19.16.27034 for x64
  Copyright (C) Microsoft Corporation.  All rights reserved.
  cl : Command line error D8022 : cannot open 'E:\Hoge\Plugins\AWSSDK\Intermediate\Build\Win64\UE4Editor\Development\AWSSDK\Module.AWSSDK.gen.cpp.obj.response'
  @progress 'Compiling C++ source code...' 43%
  [3/7] Microsoft (R) C/C++ Optimizing Compiler Version 19.16.27034 for x64
  Copyright (C) Microsoft Corporation.  All rights reserved.
  cl : Command line error D8022 : cannot open 'E:\Hoge\Plugins\AWSSDK\Intermediate\Build\Win64\UE4Editor\Development\AWSSDK\AWSSDK.cpp.obj.response'
  @progress 'Compiling C++ source code...' 57%
```

以下の2つのファイルを一旦他の場所に移してビルドして再び戻して再ビルドすると成功していました。

* Plugins/AWSSDK/Source/AWSSDK/Private/S3UploadProxy.cpp
* Plugins/AWSSDK/Source/AWSSDK/Public/S3UploadProxy.h

もしくは、プロジェクトのIntermediate、Build。Binariesあたりを削除してプラグインとプロジェクト本体を再ビルドするといけるみたいです。

原因がよくわかっていません。