Unreal C++からAndroid API呼び出しのつらさを減らしてみた
Tags: android, ndk, ue4, unrealengine
Published: 2016-11-02 15:16:00

---
title: Unreal C++からAndroid API呼び出しのつらさを減らしてみた
published: true
tags: Android, NDK, UE4, UnrealEngine
date: 20161102T15:16Z
canonical_url: https://qiita.com/shiena/items/f07533702e2639a511c3
---
# はじめに

以前、[UE4のC++からAndroid APIを呼んでみた](http://qiita.com/shiena/items/7b9d0de97a733b1a5101)でUnreal C++からAndroid APIを呼んでみて、Android NDKがつらい感じでした。ですが、UE4はAndroid側を拡張する手段を[Unreal Plugin Language](http://qiita.com/shiena/items/fe0e4cc1de4ddbaa60f0) (以下、UPLと省略する)として用意しています。今回はそれを使って、Unreal C++からAndroid API呼び出しのつらさを軽減します。

# JavaのコードをGameActivity.javaに追い出す

Android NDKを使ったときのつらさは`FindClass()`や`GetMethodID()`で各IDを取得したり、メソッド呼び出し後に`DeleteLocalRef()`で参照を削除しなければならないことです。それならばJavaのコードはJava側に追い出してしまうのが自然でしょう。

以下ではUPLで、GameActivity.javaにPicturesパスを取得するメソッドを追加して、C++からそのメソッドを呼び出すまでの設定およびコードを解説します。

## CppTest.Build.cs

UE4でC++プロジェクトを作成すると必ず`Build.cs`が作成されます。
この中でUPLのXMLを読み込みます。

```csharp:Source/CppTest/CppTest.Build.cs
// Fill out your copyright notice in the Description page of Project Settings.

using UnrealBuildTool;
// ここから追加
using System.IO;
// ここまで追加

public class CppTest : ModuleRules
{
    public CppTest(TargetInfo Target)
    {
        PublicDependencyModuleNames.AddRange(new string[] { "Core", "CoreUObject", "Engine", "InputCore" });

        PrivateDependencyModuleNames.AddRange(new string[] {  });

        // ここから追加
        if (Target.Platform == UnrealTargetPlatform.Android)
        {
            string ProjectPath = Utils.MakePathRelativeTo(ModuleDirectory, BuildConfiguration.RelativeEnginePath);
            AdditionalPropertiesForReceipt.Add(new ReceiptProperty("AndroidPlugin", Path.Combine(ProjectPath, "CppTest_UPL.xml")));
        }
        // ここまで追加
    }
}
```

## CppTest_UPL.xml

このファイルでGameActivity.javaにPicturesフォルダのパスを取得するメソッドを追加しています。XMLの中のJavaのコードの断片を書くのが気持ち悪いですが我慢です。
詳しくは[Unreal Plugin Language リファレンス](http://qiita.com/shiena/items/fe0e4cc1de4ddbaa60f0)を参照してください。

```xml:Source/CppTest/CppTest_UPL.xml
<?xml version="1.0" encoding="utf-8"?>

<root xmlns:android="http://schemas.android.com/apk/res/android">

  <init>
    <!-- APK作成時にログを出力する -->
    <log text="CppTest init"/>
  </init>

  <!-- APK作成時にログ出力を有効にする -->
  <trace enable="true"/>

  <gameActivityImportAdditions>
    <insert>
      import android.os.Environment;
    </insert>
  </gameActivityImportAdditions>

  <gameActivityClassAdditions>
    <insert>
      public String AndroidThunkJava_GetPicturesPath() {
          File f = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES);
          return f.getPath();
      }
    </insert>
  </gameActivityClassAdditions>

</root>
```

## UMyBlueprintFunctionLibrary.h

ヘッダファイルは[UE4のC++からAndroid APIを呼んでみた](http://qiita.com/shiena/items/7b9d0de97a733b1a5101)と同じです。

```cpp:Source/CppTest/UMyBlueprintFunctionLibrary.h
// Fill out your copyright notice in the Description page of Project Settings.

#pragma once

#include "Kismet/BlueprintFunctionLibrary.h"
#include "MyBlueprintFunctionLibrary.generated.h"

/**
 * 
 */
UCLASS()
class CPPTEST_API UMyBlueprintFunctionLibrary : public UBlueprintFunctionLibrary
{
    GENERATED_BODY()

public:
    UFUNCTION(BlueprintPure, Category = "MyBPLibrary")
    static FString GetPicturesPath();

private:
    static FString GetPicturesPathJNI();
};
```

## UMyBlueprintFunctionLibrary.cpp

やっとC++の本体です。
以前はJava側の複数のクラスやメンバー変数を参照していましたが、今回は追加したメソッド1つだけになったので幾分か楽になりました。
Javaのインスタンスに対してメソッドを呼びたい場合はこれが限界だと思います。

```cpp:Source/CppTest/UMyBlueprintFunctionLibrary.cpp
// Fill out your copyright notice in the Description page of Project Settings.

#include "CppTest.h"
#include "MyBlueprintFunctionLibrary.h"

#if PLATFORM_ANDROID
#include "Android/AndroidApplication.h"
#endif

FString UMyBlueprintFunctionLibrary::GetPicturesPath()
{
    static FString PicturesPath = UMyBlueprintFunctionLibrary::GetPicturesPath();
    return PicturesPath;
}

FString UMyBlueprintFunctionLibrary::GetPicturesPathJNI()
{
    FString result;
#if PLATFORM_ANDROID
    JNIEnv* Env = FAndroidApplication::GetJavaEnv();

    if (nullptr != Env)
    {
		jclass GameActivity = FAndroidApplication::GetGameActivityThis();
        jmethodID getPicturesPathMethod = Env->GetStaticMethodID(GameCls, "AndroidThunkJava_GetPicturesPath", "()Ljava/lang/String;");

        jstring pathString = (jstring)Env->CallObjectMethod(GameActivity, getPicturesPathMethod, nullptr);
        Env->DeleteLocalRef(getPicturesPathMethod);

        const char *nativePathString = Env->GetStringUTFChars(pathString, 0);
        result = FString(nativePathString);

        Env->ReleaseStringUTFChars(pathString, nativePathString);
        Env->DeleteLocalRef(pathString);
    }
    else
    {
#endif
        result = FString("");
#if PLATFORM_ANDROID
    }
#endif

    return result;
}
```

# GameActivity.javaからネイティブメソッドを呼び出す

今度はゲーム起動後、変更されない値をC++側で受け取る場合を解説します。

## CppTest.Build.cs

`Source/CppTest/CppTest.Build.cs`は同じなので割愛します。

## CppTest_UPL.xml

今度はネイティブメソッド`nativeSetPicturesPath()`を定義して、Androidの`onCreate()`メソッドの中でPicturesフォルダのパスを渡します。

```xml:Source/CppTest/CppTest_UPL.xml
<?xml version="1.0" encoding="utf-8"?>

<root xmlns:android="http://schemas.android.com/apk/res/android">

  <init>
    <!-- APK作成時にログを出力する -->
    <log text="CppTest init"/>
  </init>

  <!-- APK作成時にログ出力を有効にする -->
  <trace enable="true"/>

  <gameActivityImportAdditions>
    <insert>
      import android.os.Environment;
    </insert>
  </gameActivityImportAdditions>

  <gameActivityClassAdditions>
    <insert>
      public native void nativeSetPicturesPath(String PicturesPath);
    </insert>
  </gameActivityClassAdditions>

  <gameActivityReadMetadataAdditions>
    <insert>
      File f = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES);
      nativeSetPicturesPath(f.getPath());
    </insert>
  </gameActivityReadMetadataAdditions>

</root>
```

## UMyBlueprintFunctionLibrary.h

Javaのネイティブメソッドの引数でPicturesフォルダのパスが渡されるのでそれを保存するための変数`PicturesPath`を追加して、JNI関数を使わなくなったので`GetPicturesPathJNI`関数を削除しています。

```cpp:Source/CppTest/UMyBlueprintFunctionLibrary.h
// Fill out your copyright notice in the Description page of Project Settings.

#pragma once

#include "Kismet/BlueprintFunctionLibrary.h"
#include "MyBlueprintFunctionLibrary.generated.h"

/**
 * 
 */
UCLASS()
class CPPTEST_API UMyBlueprintFunctionLibrary : public UBlueprintFunctionLibrary
{
    GENERATED_BODY()

public:
    static FString PicturesPath;

    UFUNCTION(BlueprintPure, Category = "MyBPLibrary")
    static FString GetPicturesPath();
};
```

## UMyBlueprintFunctionLibrary.cpp

C++の本体は`PicturesPath`のgetter関数とJavaで宣言したネイティブメソッドの実装だけになり、最初と比べると随分と短くなりました。

```cpp:Source/CppTest/UMyBlueprintFunctionLibrary.cpp
// Fill out your copyright notice in the Description page of Project Settings.

#include "CppTest.h"
#include "MyBlueprintFunctionLibrary.h"

#if PLATFORM_ANDROID
#include "Android/AndroidApplication.h"
#endif

FString UMyBlueprintFunctionLibrary::PicturesPath;

FString UMyBlueprintFunctionLibrary::GetPicturesPath()
{
    return UMyBlueprintFunctionLibrary::PicturesPath;
}

#if PLATFORM_ANDROID
extern "C"
{
    JNIEXPORT void JNICALL Java_com_epicgames_ue4_GameActivity_nativeSetPicturesPath(JNIEnv* jenv, jobject thiz, jstring picturesPath)
    {
        const char* javaChars = jenv->GetStringUTFChars(picturesPath, 0);

        UMyBlueprintFunctionLibrary::PicturesPath = FString(UTF8_TO_TCHAR(javaChars));

        //Release the string
        jenv->ReleaseStringUTFChars(picturesPath, javaChars);
    }
}
#endif
```

# まとめ

UPLを使って、Android API呼び出しのつらさを軽減できました！
Android NDKにも色々と関数が用意されているのでわざわざAndroid APIを呼ぶ機会はほとんどないと思いますが、誰かの助けになればよいかと思います。