C++でデータを保存する(Protocol Buffers編)


Protocol BuffersはGoogleが開発しているフリーのシリアライズライブラリ。
構造を定義したファイルをprotocでコンパイルしてコードを生成して利用する。

cocos2d-xで利用するにはiOS/Android用のバイナリを作成する必要がある。


## URL

- [公式](https://code.google.com/p/protobuf/)
- [ドキュメント](https://developers.google.com/protocol-buffers/docs/overview)

## Protocol Buffersをマシン上で利用する

### 環境

- Mac OS X 10.9.4

### インストール

* brewでinstall

	以下で完了。

	```
	brew install protobuf
	```

* ソースからコンパイルしてinstall
	[ここ](https://code.google.com/p/protobuf/downloads/list)から最新のものをダウンロードして解凍。以下ではバージョン2.5.0を選択。

	```
	curl https://protobuf.googlecode.com/files/protobuf-2.5.0.tar.gz | 	tar xvz
	```

	解凍したソースをmakeしてインストール

	```
	cd protobuf-2.5.0
	./configure
	make
	make install
	```

### データの読み書きをする

以下、チュートリアル通り。

以下の内容のaddressbook.protoというファイルを作成する。

```addressbook.proto
package tutorial;

message Person {
  required string name = 1;
  required int32 id = 2;
  optional string email = 3;

  enum PhoneType {
    MOBILE = 0;
    HOME = 1;
    WORK = 2;
  }

  message PhoneNumber {
    required string number = 1;
    optional PhoneType type = 2 [default = HOME];
  }

  repeated PhoneNumber phone = 4;
}

message AddressBook {
  repeated Person person = 1;
}
```

コマンドでコンパイル。以下ではカレントにソースを出力。

```
protoc -I=. --cpp_out=. addressbook.proto
```

addressbook.pb.hとaddressbook.pb.ccが生成されるのでこれらを利用する。

コンパイルは以下のコマンドで実行。

```
clang++ -g -Wall -std=c++11 -lprotobuf main.cc addressbook.pb.cc
```


## iOS/AndroidでProtocol Buffersを利用する

iOS/Androidで利用するには別途コンパイルが必要。

### iOS向けにコンパイル

スクリプトが[公開](https://gist.github.com/BennettSmith/7150245)されているのでそれを利用する。

こちらの環境では上記のスクリプトが動作しないので[これ](https://gist.github.com/carlbenson/10245203)を利用。

**注意**
上記スクリプトでコンパイルすると名前空間がgoogleからgoogle_publicに変更される


```
git clone https://gist.github.com/7150245.git build-protobuf
cd build-protobuf
sh ./build-protobuf-2.5.0.sh
```

protobufディレクトリ以下にバイナリ等が配置される。ライブラリはuniversalになるのでターゲットはどれでも利用できる。

```
protobuf/
├── bin
│   └── protoc
├── include(省略)
└── lib
    ├── libprotobuf-lite.a
    ├── libprotobuf-lite.la
    ├── libprotobuf.a
    ├── libprotobuf.la
    ├── libprotoc.a
    ├── libprotoc.la
    └── pkgconfig
        ├── protobuf-lite.pc
        └── protobuf.pc
```

### Android向けにコンパイル

ほぼ、[こちら](http://eaglesakura.hatenablog.com/entry/2014/01/12/221356)の通り。

**注意**
前のiOSスクリプトを利用してコンパイルしていると名前空間が変更されるのでcocos2d-xでも利用するには/tmp/protobuf-*内のソースを利用する必要がある(undefinedエラーでコケる)

以下要点。

- 解凍したディレクトリ名をjniに変更
    - undefinedエラーが出ないためには/tmp/protobuf-*内のソースを上書きコピーしておく

    ```
    cd jni
    cp -prf /tmp/protobuf-2.5.0/* .
    ```

- 適当なディレクトリを作成し、そこにjniを移動
- AndroidManifest.xml(適当なもの)を作成したディレクトリに配置
- Android.mkをjniに配置
- Application.mkをjniに配置
    - cocos2d-x v3.2だとc++_staticを利用しないとundefinedエラーが発生する
- スクリプトを実行。${ANDROID_NDK_HOME}にはndkのパスを指定
- ビルド

	```
	${ANDROID_NDK_HOME}/ndk-build
	```

作成したバイナリはobj以下にできる。

```
obj/
└── local
    ├── armeabi
    │   └── libprotobuf.a
    ├── armeabi-v7a
    │   └── libprotobuf.a
    └── x86
        └── libprotobuf.a
```

## Protocol Buffersをcocos2d-xで利用する

### cocos2d/externalにライブラリを追加する

生成したファイルを以下のように配置する。

```
protobuf
├── include(省略)
└── prebuilt
    ├── android
    │   ├── Android.mk
    │   ├── armeabi
    │   │   └── libprotobuf.a
    │   ├── armeabi-v7a
    │   │   └── libprotobuf.a
    │   └── x86
    │       └── libprotobuf.a
    └── ios
        └── libprotobuf.a
```

Android.mkは以下の内容で作成した。

```Android.mk
LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)
LOCAL_MODULE := protobuf_static
LOCAL_MODULE_FILENAME := protobuf
LOCAL_SRC_FILES := $(TARGET_ARCH_ABI)/libprotobuf.a
LOCAL_EXPORT_C_INCLUDES := $(LOCAL_PATH)/../../include
include $(PREBUILT_STATIC_LIBRARY)
```


### プロジェクトにライブラリを追加する

cocosコマンドで新しいプロジェクトを作成しておく。

#### iOSに追加

* XCodeで作成したプロジェクトを開き、protobufをcocos2d_libs.xcodeproj内のexternalにD&D
* 「Add To Target」はチェックなしにして追加
* 追加したprotobuf内のprebuilt/ios/libprotobuf.aファイルを選択して「Target Membership」のうちcocos2d Mac/cocos2dx iOSにチェックを入れる
* コンパイルできることを確認する
	* エラーが出る場合はLibraryやHeader Search Pathsを確認する。

#### Androidに追加

* cocos2d/cocos/Android.mkに以下を追加

	```
	LOCAL_WHOLE_STATIC_LIBRARIES += protobuf_static
	$(call import-module,external/protobuf/prebuilt/android)
	```
* コンパイルできることを確認する

### cocos2d-xからライブラリを呼び出す

* protocで生成したファイルをClassesに追加
* [ここ](https://developers.google.com/protocol-buffers/docs/cpptutorial)を参考にHelloWorld内の適当な所にコードを書く

```cpp:HelloWorldScene.cpp
    // ファイル書き込み
    {
        tutorial::AddressBook address_book;
        for (auto i = 0; i < 5; ++i) {
            auto person = address_book.add_person();
            person->set_id(i);
            person->set_name(StringUtils::format("Name-%d", i));
            person->set_email(StringUtils::format("email-%d", i));
        }
        CCLOG("### %s", address_book.DebugString().c_str());

        auto filename = FileUtils::getInstance()->getWritablePath() + "addressbook.bin";
        std::fstream output(filename, std::ios::out | std::ios::trunc | std::ios::binary);
        if (!address_book.SerializeToOstream(&output)) {
            CCLOG("Failed to write address book.");
            return false;
        }
    }


    // ファイル読み込み
    {
        tutorial::AddressBook address_book;

        auto filename = FileUtils::getInstance()->getWritablePath() + "addressbook.bin";
        std::fstream input(filename, std::ios::in | std::ios::binary);
        if (!address_book.ParseFromIstream(&input)) {
            CCLOG("Failed to parse address book.");
            return false;
        }

        CCLOG("### %s", address_book.DebugString().c_str());

    }

    google_public::protobuf::ShutdownProtobufLibrary();
```

* コンパイルして確認