C++シリアライザTheolizerをインストールして使ってみる

  • 5
    Like
  • 10
    Comment
More than 1 year has passed since last update.

1.はじめに

こんにちは。

つい先日、ここでご紹介させて頂いたTheolizer(セオライザ)をオープンβとしてGitHubで公開しました。
Theolizerはクラスやポインタを含む複雑なデータ構造をファイルへ保存したり、TCP/IP等で通信したりするプログラムの開発を容易にするC++用開発ツール(シリアライザ)です。

C#にはリフレクションがあり、メンバ変数を自動的に列挙できます。だから、クラスのメンバ変数を追加/削除してもシリアライズ用のソース・コードを修正することなく自動的にシリアライズできます。

しかし、残念なことに我らがC++にはリフレクションがなく、クラスのメンバ変数の列挙を自動化できません。そのため、クラスをシリライズするにはboost::serializationのように、メンバ変数を列挙するコードをプログラマ自身が書く必要があります。
そのため、クラス定義を修正した時、メンバ変数の列挙コードの修正とデバッグが必要になります。もし、プログラムを変更する前に保存したデータを読みたい場合、旧データを回復するためのコードも書いてデバッグする必要があります。(特にデバッグ中、テスト・データを作り直したくないので頻発します。)
C#の例でも分かるようにこれらの作業は自動化することが可能であり、ミスを冒しやすい人手による変更作業をなくしたいものです

Theolizerはソース・コードをlibTooligを用いて解析しクラス・メンバのシリアライズに必要なソース・コードを自動生成します。クラス・メンバの単純な追加/削除程度ならシリアライズ用コードを修正しなくても追従しますし、それ以上の変更にも対応できる機能を提供します。

これにより、多くのケースでデータ保存/回復や通信処理の開発工数を大幅に削減できます。
そこで、できるだけ多くの方に使って頂きたく、インストール方法とユーザ・プログラムのビルド方法を解説します。

また、theolizer.comの技術解説で、C言語+αの知識の範疇で複雑な構造のデータをファイルへ保存したりTCP/IPで通信したりするサンプルを解説しています。

2.必要なツールの準備

Theolizerはマルチ・プラットフォームでC++11規格コンパイラに対応できるよう設計しており、現時点でテストできている環境は下記です。
プリビルド版もこの環境でビルドしています。これらと互換性のある環境であれば使用可能ですので、使いたいコンパイラをご用意下さい。
ここでは下記のコンパイラの準備について簡単に説明します。

OS C++コンパイラ
Windows 10 Professional 64bit Visual Studio C++ 2015 Community update 3
MinGW 5.4.0 32bit posix dwarf
MinGW 5.4.0 64bit posix seh
ubuntu 16.04 LTS 64bit gcc 5.4.0(Ubuntu 5.4.0-6ubuntu1~16.04.2)

2.1 Visual Studio 2015 Communityの準備

Visual Studio 2015 Communityは学生、オープン ソース、個人の開発者向けで無料です。
インストール手順は他のサイトに優れた解説が多数ありますので省略致します。例えばここなどが参考になります。

2.2 MinGW 5.4.0の準備

MinGWは事実上全ての人が無料で使用できます。
上表のリンク先からMinGWのインストーラがダウンロードできます。
ダウンロードしたmingw-w64-install.exeを起動して必要なMinGWを選択してインストールして下さい。

32bit版
MinGW540x32-B.png

64bit版
MinGW540x64-B.png

なお、PATHは設定されませんので使う際にPATH設定が必要になります。
32bit版は<MinGWインストール先>\mingw32\binです。
64bit版は<MinGWインストール先>\mingw64\binです。

ところで、MinGWとセットでMSYSやMSYS2もインストールするよう説明しているサイトが多いですが、Theolizerを使うためにMSYSやMSYS2をインストールする必要はありません。(もちろんインストールされていても特に問題ありません。)

2.3 ubuntu gcc 5.4.0の場合

ubuntu 16.04 LTSにはgcc 5.4.0が標準でインストールされています。

2.4 CMake 3.5.0のダウンロードとインストール方法

Theolizerはビルド・ツールとしてCMake 3.5.0以上をサポートしています。CMakeがあればTheolizerを使うビルド・プロジェクト(makefileやVisual Studioのソリューション)を容易に生成できます。

OS CMake インストール方法
Windows 10 CMake 3.5.0 ダウンロードしたファイルをダブルクリック
ubuntu 16.04 LTS CMake 3.5.1 端末で"sudo apt-get install cmake"

WindowsへCMakeをインストールする場合、インストール中にPATH設定の選択肢がでてきます。例えば下記のようにPATH設定するように指示しておくとたいへん便利です。

CMake-C.png

CMakeはスクリプト機能や一通りのファイル操作機能も内蔵しており、マルチ・プラットフォーム向けにビルド・システムを構築できる優れものです。これ1つで大抵のことができますので、多数のツールをダウンロードする煩わしさを低減できます。

3.Theolizerの準備

3-1.プリビルド版のダウンロード

Theolizer自体のビルドはあまり重たくないのですが、Theolizerをビルドするために必要なClang/LLVMのビルドが大変重たいです。1セット1~3時間かかります。
そこで、下記のプリビルド版を用意していますのでお使いの環境に合わせてダウンロードして下さい。

ファイル名 内容
Theolizer-gcc540x64.tar.gz gcc 5.4.0(Ubuntu 5.4.0-6ubuntu1~16.04.2)用
Theolizer-mingw540x32.zip MinGW 5.4.0 32bits用
Theolizer-mingw540x64.zip MinGW 5.4.0 64bits用
Theolizer-msvc2015x32.zip Visual Studio C++ 2015 32bits用
Theolizer-msvc2015x64.zip Visual Studio C++ 2015 64bits用

3-2.Theolizerの動作概要

インストール手順を理解し易くするためTheolizerの動きを簡単に説明します。

冒頭で説明したようにTheolizerはユーザ・プログラムを解析して追加ソース・コードを自動生成します。
それを下記のような仕組みで実現しています。

  1. インストール時
    • 対象コンパイラを「コンパイラ名RenamedByTheolizer(.exe)」へリネームする。
    • 対象コンパイラの元のパス名へTheolizerDriverをコピーする。
  2. ビルド時
    • ビルド・システムからの要求に従い、TheolizerDriverがビルド対象のソースを解析し、
    • シリアライズ用の追加ソース・コードを自動生成する。
    • 元のコンパイラ「コンパイラ名RenamedByTheolizer(.exe)」へパススルーしてコンパイルする。

なお、ソース・コードの解析と自動生成機能はコンパイル時のコマンドライン・オプションでTHEOLIZER_ANALYZEマクロを定義(例えば、-DTHEOLIZER_ANALYZE)した時のみ機能します。
それ以外は何もせずにそのまま元のコンパイラへパス・スルーしますので、Theolizerを用いないプロジェクトへの影響は事実上ありません。
Windows 10、および、ubuntu 16.04にて、コンパイラを置き換えたままCMakeのジェネレーションやllvmのビルドができることを確認しております。

3-3.インストール

まず、ダウンロードしたTheolizerプリビルド版をお好きなフォルダへ解凍して下さい。以下、そのフォルダを<Theolizerルート>として説明します。
次に、TheolizerDriverの--theolizer-replaceコマンドにより、ドライバをリプレースします。
なお、2重にリプレースしようとしても2回目以降は何もしませんのでご安心下さい。

リプレースのテストは入念に行っておりますが、念のため、対象コンパイラをリプレースするまえにバックアップしておくことをお勧めします。

Visual Studio 2015の場合
環境変数VSSDK140InstallにVisual Studioのインストール先が設定されていることを利用して、ドライバをリプレースするバッチ・ファイルを用意しました。
Visual Stduioはコンパイラが下記のように複数あります。それらを全て置き換えます。

パス コンパイラの種別
%VSSDK140Install%..\VC\bin\cl.exe 32ビット・ビルド用32ビット・コンパイラ
%VSSDK140Install%..\VC\bin\amd64\cl.exe 64ビット・ビルド用64ビット・コンパイラ
%VSSDK140Install%..\VC\bin\amd64_x86\cl.exe 32ビット・ビルド用64ビット・クロス・コンパイラ
%VSSDK140Install%..\VC\bin\x86_amd64\cl.exe 64ビット・ビルド用32ビット・クロス・コンパイラ

リプレース時のコマンドは以下の通りです。

> <Theolizerルート>\replace.bat

リネーム後のcl.exeのファイル名はclRenamedByTheolizer.exeです。

MinGW(Windows)の場合

> <Theolizerルート>/bin/TheolizerDriver "--theolizer-replace=<g++のフル・パス>"

リネーム後のg++.exeのファイル名はg++RenamedByTheolizer.exeです。

gcc(ubuntu)の場合

> sudo <Theolizerルート>/bin/TheolizerDriver "--theolizer-replace=/usr/bin/g++-5"

リネーム後のg++-5のファイル名はg++-5RenamedByTheolizerです。

なお、リプレースを指示する先はg++-5へのシンボリックリンクでも大丈夫です。
シンポリックリンクを追跡して、対象のコンパンラ実体を置き換えます。

リプレース確認
リプレース後、PATHを通して下記コマンドを入力するとTheolizerを導入できたことを確認できます。

Visual Studio> cl --theolizer-version
MinGW        > g++ --theolizer-version
gcc          $ g++ --theolizer-version

Theolizer version 0.3.0 
Copyright (C) 2016 Yohinori Tahara (Theoride Technology)
    SourcesHash : 2555e35a9599a7df4b713672c74d52ca

SourceHashはソース・コードのMD5値です。gitリポジトリ上のどのバージョンか特定できるよう実験的に付けてみました。

4.Theolizerを使うプロジェクトを生成し、ビルド、実行

お試し用のソースを<Theolizerルート>/samples/exampleに置いてます。
CMakeを使ってこれのビルド用プロジェクトを生成できます。

適当に空のフォルダを1つ用意してください。例えばexample_buildフォルダを作ります。

> mkdir example_build
> cd example_build

下記操作でプロジェクトを生成し、ビルド、実行できます。

注意
この時、下記のようなエラーが出た場合、ドライバのリプレースができていません。
「3-3.インストール」のドライバのリプレース作業を忘れていないか確認して下さい。

example.cpp(11): fatal error C1083: include ファイルを開けません。
'example.cpp.theolizer.hpp':No such file or directory

Visual Studio 2015の32bitの場合
TheolizerはTheolizer-msvc2015x32.zipを使って下さい。

> cmake -G "Visual Studio 14" <Theolizerルート>/samples/example -DTHEOLIZER_DIR=<Theolizerルート>
> cmake --build . --config Release --target BuildTest

Visual Studio 2015の64bitの場合
TheolizerはTheolizer-msvc2015x64.zipを使って下さい。

> cmake -G "Visual Studio 14 Win64" <Theolizerルート>/samples/example -DTHEOLIZER_DIR=<Theolizerルート>
> cmake --build . --config Release --target BuildTest

MinGW 5.4.0の32bitの場合
TheolizerはTheolizer-mingw540x32.zipを使って下さい。
また、MinGW 5.4.0 32bit posix dwarfmingw32\binフォルダへPATHを通しておいて下さい。

> cmake -G "MinGW Makefiles" <Theolizerルート>/samples/example -DCMAKE_BUILD_TYPE=Release -DTHEOLIZER_DIR=<Theolizerルート>
> mingw32-make BuildTest

MinGW 5.4.0の64bitの場合
TheolizerはTheolizer-mingw540x64.zipを使って下さい。
また、MinGW 5.4.0 64bit posix sehmingw64\binフォルダへPATHを通しておいて下さい。

> cmake -G "MinGW Makefiles" <Theolizerルート>/samples/example -DCMAKE_BUILD_TYPE=Release -DTHEOLIZER_DIR=<Theolizerルート>
> mingw32-make BuildTest

gcc 5.4.0(ubuntu)の場合
TheolizerはTheolizer-gcc540x64.tar.gzを使って下さい。

$ cmake -G "Unix Makefiles" <Theolizerルート>/samples/example -DCMAKE_BUILD_TYPE=Release -DTHEOLIZER_DIR=<Theolizerルート>
$ make BuildTest

下記のようなメッセージが最後の方で出力されたら、正常にビルドでき、実行されています。

  test 1
      Start 1: example

  1: Test command:<example_buildフォルダ>/example.exe
  1: Test timeout computed to be: 9.99988e+006
  1: mEnum=2 mInt=1001 mString=[UTF-8 string]
  1/1 Test #1: example ..........................   Passed    0.03 sec

  100% tests passed, 0 tests failed out of 1

  Total Test time (real) =   0.03 sec

mEnum=2 mInt=1001 mString=[UTF-8 string]がプログラムの出力です。

5. ソース・コードの説明

exampleはenum型とクラスをJson形式で保存/回復するサンプルです。

5-1.ヘッダ example.h

使用するTheolizerのヘッダをインクルードします。このサンプルではJsonシリアラザを使うのでtheolizer/serializer_json.hをインクルードしています。

//############################################################################
//      Theolizer紹介用サンプル・プログラム
//############################################################################

#if !defined(EXAMPLE_H)
#define EXAMPLE_H

// ***************************************************************************
//      インクルード
// ***************************************************************************

#include <string>
#include <theolizer/serializer_json.h>

// ***************************************************************************
//      型定義
// ***************************************************************************

enum EnumType
{
    None,
    EnumA,
    EnumB,
    EnumC
};

struct StructType
{
    EnumType    mEnum;
    int         mInt;
    std::string mString;
    StructType() : mEnum(None), mInt(0), mString("") { }
};

#endif  // EXAMPLE_H

enumシンボルの追加や削除も可能です。
シンボルを追加しても一般に旧データ回復に問題はないです。
シンボルを削除した時、既にそのシンボルが保存されていた場合、回復できません。シリアライズ・ファイル内に削除したシンボルが含まれていた場合はエラーにしています。
しかし、存在している別のシンボルへ割り当てることが可能です。ここで詳しく説明しています。

クラスのメンバ変数の追加/削除可能です。
古いプログラムで保存されたデータから回復する際、新しいプログラムで追加されたメンバは変更しません。削除されたメンバのデータは回復先が存在しないので単に破棄します。
これにより、メンバ変数の追加/削除した時、シリアライズ・コードを修正することなく、旧プログラムで保存したシアライズ・データから回復できます。
ほとんどの型に対応していますが、一部対応していない型もあります。対応している型/対応していない型についてはここに記載しています。
なお、ポインタ型はポイント先も一緒に保存する必要が有ります。ポインタ型を試す場合はここを参照下さい。

5-2.実装部 example.cpp

Theolizerが自動生成するファイルをインクルードします。1
ファイル名は<実装部のファイル名>.theolizer.hppです。example.cppの場合example.cpp.theolizer.hppとなります。

//############################################################################
//      Theolizer紹介用サンプル・プログラム
//############################################################################

// ***************************************************************************
//      インクルード
// ***************************************************************************

#include <fstream>
#include "example.h"
#include "example.cpp.theolizer.hpp"                // Theolizer自動生成先

// ***************************************************************************
//      メイン
// ***************************************************************************

int main(int argc, char* argv[])
{
//----------------------------------------------------------------------------
//      保存
//----------------------------------------------------------------------------

    try
    {
        // データを生成する
        StructType aStructType;
        aStructType.mEnum   = EnumB;
        aStructType.mInt    = 1001;
        aStructType.mString = u8"UTF-8 string";

        std::ofstream   aStream("example.json");    // 保存先のファイルをオープンする
        theolizer::JsonOSerializer<> js(aStream);   // シリアライザを用意する
        THEOLIZER_PROCESS(js, aStructType);         // test.jsonファイルへ保存する
    }
    catch(theolizer::ErrorInfo& e)
    {
        std::cerr << e.getString() << "\n";
return 1;
    }

//----------------------------------------------------------------------------
//      回復
//----------------------------------------------------------------------------

    try
    {
        StructType aStructType;                     // データ領域を獲得する
        std::ifstream   aStream("example.json");    // 回復元のファイルをオープンする
        theolizer::JsonISerializer<> js(aStream);   // シリアライザを用意する
        THEOLIZER_PROCESS(js, aStructType);         // test.jsonファイルから回復する

        // 回復結果を表示する
        std::cout <<theolizer::print
                    (
                        u8"mEnum=%d mInt=%d mString=[%s]\n",
                        aStructType.mEnum,
                        aStructType.mInt,
                        aStructType.mString
                    );
    }
    catch(theolizer::ErrorInfo& e)
    {
        std::cerr << e.getString() << "\n";
return 2;
    }

    return 0;
}

保存処理
保存処理は下記の4ステップです。
1. std::ofstream aStream(“example.json”); example.logファイルを書き込みオープンします。
2. theolizer::JsonOSerializer<> js(aStream); Theolizerに対して上記でオープンしたファイルへシリアライズします。
3. THEOLIZER_PROCESS(js, aStructType); 事前に設定していたaStructType変数の内容をexample.logファイルへ出力します。
4. {…}ブロックから抜けた時にファイルがクローズされて書き込みが完了します。

example.log
{
    "SerialzierName":"JsonTheolizer",
    "GlobalVersionNo":1,
    "TypeInfoList":[1]
}
{
    "mEnum":"EnumB",
    "mInt":1001,
    "mString":"UTF-8 string"
}

回復処理
回復処理も下記の4ステップです。

  1. std::ifstream aStream("example.json"); example.logファイルを読み出しオープンします。
  2. theolizer::JsonISerializer<> js(aStream); Theolizerに対して上記でオープンしたファイルからデシリアライズします。
  3. THEOLIZER_PROCESS(js, aStructType); 事前に確保していたaStructType変数へexample.logファイルの内容を回復します。
  4. {…}ブロックから抜けた時にファイルがクローズされて読み出しが完了します。

おまけ
最後にtheolizer::print()関数を使ってaStructTypeの内容を標準出力へ出力しています。
これは便利のために用意したboost::formatの薄いラッパです。
printf()のようにカンマ区切りで使いたかったので設けました。C++11で追加された可変長引数テンプレートを使って実装しています。

5-3.自動生成ファイル example.cpp.theolizer.hpp

Theolizerがexample.cppと同じフォルダに自動生成します。これはクラス定義やenum型定義を保持します。

Theolizerでは、バージョンを上げた時に古いクラス定義やenum型定義をここに残しておくことで、古いシリアライズ・データを回復できるようにしています。
そのため、このファイルをお使いのバージョン管理システム(gitやsvn等)へ登録することを想定しています。

5-4.CMakeLists.txt

CMake用の設定ファイルです。

theolizerを組み込むためには、CMakeでジェネレートする際に<Theolizerルート>をTHEOLIZER_DIRにて指定して下さい。これはfind_package(THEOLIZER)でTheolizerを組み込むために使用されます。

そして、Theolizerをプログラムに組み込むためには通常のインクルード・パス指定、ライブラリ指定以外にTHEOLIZER_ANALYZEマクロの指定等が必要です。それらをsetup_theolizer()で行います。

最後の「テスト実行」の部分で、BuildTestターゲットを定義しています。
CMakeはテストとビルドが独立しています。CTestしてもそれに必要なプログラムは自動的にビルドされません。
そこで、テスト用のカスタム・ターゲットを作って、その中でCTestを起動しています。
そして、そのカスタム・ターゲットをビルド・ターゲットに依存させることで、コマンド一発でビルド→テストできるようにしています。

#[[###########################################################################
        Theolizer紹介用サンプルCMakeLists.txt
]]############################################################################

cmake_minimum_required(VERSION 3.5.0)

message(STATUS "BOOST_ROOT=${BOOST_ROOT}")

#-----------------------------------------------------------------------------
#       MSVCの通常使わないビルド・モードとZERO_CHECKプロジェクトの削除
#-----------------------------------------------------------------------------

set(CMAKE_CONFIGURATION_TYPES "Debug;Release" CACHE STRING "Configs" FORCE)
set(CMAKE_SUPPRESS_REGENERATION TRUE)

#-----------------------------------------------------------------------------
#       プロジェクト設定
#-----------------------------------------------------------------------------

#プロジェクト定義
set(TARGET_NAME example)
project(${TARGET_NAME}  VERSION 1.0.0)
set(SOURCE_LIST example.cpp example.h)

# Theolizer
find_package(THEOLIZER)

#-----------------------------------------------------------------------------
#       ビルド設定
#-----------------------------------------------------------------------------

if (${CMAKE_CXX_COMPILER_ID} STREQUAL MSVC)
    add_definitions(-D_UNICODE -DUNICODE)
    set(CMAKE_DEBUG_POSTFIX "d")
    set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /bigobj")
else()
    set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11")

    # MinGWの不具合対策(https://github.com/assimp/assimp/issues/177)
    if((CMAKE_COMPILER_IS_MINGW) AND (CMAKE_SIZEOF_VOID_P EQUAL 8))
        set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wa,-mbig-obj")
    endif()
endif()

# example
add_executable(example ${SOURCE_LIST})
setup_theolizer(example StaticWithBoost)

#-----------------------------------------------------------------------------
#       テスト実行
#-----------------------------------------------------------------------------

enable_testing()
add_test(NAME example COMMAND $<TARGET_FILE:example>)

add_custom_target(BuildTest COMMAND "ctest" "-V" "-C" $<CONFIG>)
add_dependencies(BuildTest example)

6.アンインストール

最後にアンインストール方法を説明します。インストールの逆の操作を行います。
まずドライバをリストア・コマントによりリストアし、次にTheolizerを保存したフォルダを削除します。

リストア・コマンドは対象のコンパイラ・パス名でコピーしていたTheolizerDriverを削除し、元のコンパイラの名前を元に戻します。
リストア・コマンドは、replacerestoreへ置き換えて起動します。下記の通りです。
なお、2重にリストアしようとしても2回目以降は何もしませんのでご安心下さい。

Visual Studio 2015の場合

> <Theolizerルート>\restore.bat

MinGW(Windows)の場合

> <Theolizerルート>/bin/TheolizerDriver "--theolizer-restore=<g++のフル・パス>"

gcc(ubuntu)の場合

$ sudo <Theolizerルート>/bin/TheolizerDriver "--theolizer-restore=/usr/bin/g++-5"

7.最後に

example.hのEnumTypeやStructTypeの定義を変更してもシリアライズ処理の変更が不要なことを確認頂き、Theolizerのパワーを感じて頂けると嬉しいです。そして、もし、うまく行かなかったら遠慮なくご質問下さい。

なお、EnumTypeやStructTypeのインスタンスの値を設定したり、表示したりする部分はご自身で修正する必要が有ります。この部分はアプリケーション部分です。プログラマがアプリション・ロジックの開発に注力できることがTheolizerを使うメリットです。

脚注



  1. 自動生成ファイルのインクルード場所について:自動生成ファイルでシリアライズするクラスやenum型のシリアライズに必要な追加定義を行っているため、「シリアライズするクラスやenum型の定義」より後、「それらをシリアライズするシリアライザ・インスタンスのコンストラクト」より前でインクルードして下さい。