yossi-tahara / theolizer Goto Github PK
View Code? Open in Web Editor NEWauto serializer for C++11, Do you want to update your classes easily ?
Home Page: https://theolizer.com
License: GNU General Public License v3.0
auto serializer for C++11, Do you want to update your classes easily ?
Home Page: https://theolizer.com
License: GNU General Public License v3.0
sourceフォルダ内のいくつかのCMakeList.txtで、message(SEND_ERROR "~")
やmessage(ERROR "~")
という記述があります。
前者について、軽微なエラーで使われるのはいいと思うのですが、「必要なライブラリが存在しない」とか「引数で与えられた設定が間違っている」などにおいては代わりにmessage(FATAL_ERROR "~")
を使ったほうがいいのではないでしょうか。
後者について、CMake公式のドキュメントを読んでもERROR
というオプションはないようです。
これもSEND_ERROR
やFATAL_ERROR
に変えたほうが良いのではないでしょうか。
C++はデファクトスタンダードなGUI開発環境が事実上ないことが結構辛いです。マルチプラットフォーム言語なのである意味しかたのないことですが、せめてWindowsに標準的なものがあると嬉しいです。
QtやC++BuilderなどC++用のGUI開発ツールはそれなりにありますが、どれを取っても流通している情報は潤沢ではなく、必要な情報の入手にいつも苦労します。
一方、Windows向けのGUI開発ツールとしてはC#やVBは圧倒的に情報量が豊富です。検索一発で知りたいページが出てくる確率がかなり高いです。しかし、C#やVBは生産性優先の言語ですので、危険な機能は排除していますので例えば画像処理のように高速性が必要なプログラム開発は厳しいです。またメモリ・リークのリスクを軽減するためガベージコレクション(GC)を採用しており、GCによる不意な遅延が発生するので、高応答性能なプログラムを開発することも厳しいです。
そこで、C#でGUIを開発しC++にて高速処理が必要な部分を開発するというスタイルを取るプロジェクトも少なくありません。(私もそのようなプロジェクトに関わった経験があります。)
問題は、C#とC++はデータ構造が似ているとは言えデータ管理方法が大きく異なるため、データ交換できるようにする手間が結構掛かり、かつ、C++形式でインタフェースするためにはCOM対応が必要となり骨が折れます。更にガベージコレクションにより不意に発生する遅延を吸収する仕組みは自力で開発する必要があり、こちらもキューとマルチスレッドかマルチプロセスが事実上必須なのでなかなかたいへんです。
そのため、C++プログラマがC#でGUIを開発するのはお手軽とはとても言えません。もし、C++BuilderやQtを使えるのであれば、データ交換の手間もなくガベージコレクションのないそれらを使った方が簡単でしょう。
このようにC++向けGUI開発はWindowsに限定してさえ、決定打がない点が辛いです。
元々TheolizerはC#も含め、クラスとenum定義を多言語間でやりとりすることを目標とするメタ・シリアライザを視野に入れいました。マスター側言語で作ったクラスやenum定義をメタ情報としてシリアライズし、データ交換したいスレーブ側の他の言語でそれをデシリアライズし、当該言語の構文でクラス/enum定義等を自動生成するのです。
そのような自動生成ツールを開発できれば、例えばC++で作ったデータ構造をほぼそのままC#のソース形式で手間なく再現でき、容易にデータ交換できるようになります。データ交換自体はTheolizerを使用できます。
そして、最近になって気がついたのですが、開発時にクラス/enum定義をやり取りできるのであれば、関数のシグニチャもやり取りできます。そして、実動作時に実パラメータや戻り値をTheolizerでやり取りできます。従って異言語間のプロシージャ・コールを容易に実現できる開発ツールを作れる可能性があります。
これを利用してC#からC++を容易に呼び出せる開発ツールを作れるのではないかと考えています。
Theolizerはデシリアライズ時に型チェックを行う機能を持っています。これはシリアライズ・データのヘッダに型情報を保存することで実現しています。その「型情報」は、主にクラス名、クラス・メンバ変数の型を保存しています。
これにもう少し情報を追加することで、クラス定義(メンバ変数のみ)やenum定義を生成するために必要な情報にすることができます。そのための「お試し」実装まで現Theolizerは含んでいます。(THEOLIZER_INTERNAL_ENABLE_META_SERIALIZERでディセーブルしています。)
メンテナンスしていないため、最近のTheolizerドライバの仕様変更に追従していませんが、メタ・デシリアライザでクラス定義とenum定義が出力されることまで確認しています。
C++とC#は文法的にも機能的にも近いですので、C++のクラスやenum定義をデータ交換可能なレベルで復元できる筈です。最も気になるString型についても、元々Theolizerは文字列については思い切ってchar*を切り捨て、std::string, std:wstring等を「プリミティブ」とし、かつ、UTF-8へ変換してシリアライズしていますので、大きな問題なく交換可能な筈です。
配列はC#とC++のそれぞれの各種配列をどのように対応させるのか要検討です。
C#の参照型をC++側のポインタへ対応させるのか、クラス型のオブジェクトに直接対応させるのか要検討です。どちらもよく考える必要はありますが、解はあると思います。
つまり、C#と連携したいデータの多くについて、その型情報を含むメタ・シリアライズ・データをJson形式で出力し、それをC#側でDynamicJson等を用いてデシリアライズ後、クラス定義とenum定義のC#ソース・コードを出力できる筈と考えています。
まだまだ検討が不十分ですが、基本的には以下のイメージになると思います。
C++側がC#側へExportするクラスのprivate/protectedメンバ変数は、C++側だけで記録しておけば十分な筈です。ですので、基本はC++側のみにフルのインスタンスを保持し、C#側にはその「影」を保持する形式にする予定です。
C++側では共有するクラス・インスタンスのリストをTheolizer拡張部で管理し、そのインスタンス・ハンドルをC#側の「影」が保持するイメージです。
またメンバ関数をExportするクラスは恐らくこの3-1形式のクラスのみになると思います。
そして共有するデータはインスタンス・ハンドル、および、保存先としてC#が指定されたメンバ変数です。これらのデータはC++/C#間でお互いに送信し合う形になります。
その実体を定義するのがC++側かC#側かの2種類が存在することになります。
実体の無い側が当該関数を呼び出し、実体の有る側がそのコールを処理します。
メンバ関数が呼び出されると
その「メンバ関数ID」と当該クラスの「インスタンス・ハンドルと共有メンバ」、「パラメータ群」をTheolizerにてシリアライズします。
シリアライズ・データをC++側へ送る部分はユーザ定義できるようにする予定です。
下記オプションを想定しています。
ガベージコレクションによる遅延を考慮しなくて良い場合は、キューとスレッドを使う必要がないのでもう少し簡単な仕組みを考えたいと思います。
上記仕組みで送信後、応答を待ちます。
関数コールを受ける側はシリアライズ・データを受信する部分から始まります。
ユーザ・プログラムにてデータ到着を検出したら、当該ストリームのインスタンスとともにTheolizerの受信関数を呼び出します。(周り巡ってこのスレッドが関数の実装部を呼び出します。)
呼び出されたTheolizerは通常通りデシリアライズします
それにより、クラス・インスタンスとパラメータを再構築し、それを使って当該メンバ関数実体を呼び出します。
メンバ関数から戻ってきたら
戻り値や戻すと指定されているパラメータをシリアライズして相手方へ送ります。
2.で待っていたスレッドが起動され
データをデシリアライズして戻り値や戻りパラメータを回復し、呼び出し元へreturn;します。
C#の速度が既にC++に追いついたという評価記事をちらほら見かけます。
流石に原理的にC#がC++を置き換えられるほど高速になることはありえないとは思いますが、やはり事前に確認しておきます。
もし、本当にC#の速度がそこまで速くなっていたら、少なくともC++/C#連携の出番はありません。無用の長物になってしまうので、リスクが大きすぎます。
軽い評価を行ったところ、上記評価記事の通り確かにC#は予想していたより速いです。しかし、C++に追いついていないケースも多々あり、C++を置き換える程ではないように感じています。
もう少し密な評価を行い、Qiitaにて公表したいと思います。しばらくお待ち下さい。
現在、正規リリースにタグを付けて、masterブランチのみで運用しています。
開発中のブランチがあった方が良いような気もしています。
開発中ブランチの必要が生じたら、GitHub Flowベースの開発フローで運用したいと思います。
すぐにでも開発中ブランチを作った方が良いとのご意見があれば対応しますので、こちらへご意見下さい。
バグを3点修正しました。
コミット・メッセージを参照下さい。
Theolizerは、クラスのバージョン番号と対応するクラス情報を管理することで、旧バージョン・データの回復や旧バージョン形式での保存に対応しています。
各バージョンについてシリアライズ対象のメンバ変数を集めたクラス(TheolizerVersion)を自動生成しています。(*.theolizer.hpp + version_auto.inc)
そして、このTheolizerVersionの隣り合うバージョン同士について対応するメンバ変数の引継ぎを自動処理しています。
その際、プリミティブ(int型等の組み込み型とstd::basic_string型)については、旧バージョンは新バージョンの対応するメンバ変数への参照を保持してます。
参照を保持する方式ではなく同じ型の変数を保持してコピーで引継ぎすることも考えられますが、開発当初、参照保持の方が効率が良いと判断して参照にしました。
バージョン変更テスト実装中に、1つ問題が見つかりました。
各クラスはバージョン変更に対して、バージョン・アップ処理とバージョン・ダウン処理をユーザ・プログラマが記述できます。クラスはクラスを含むことができるので、内側のクラスの特定の値を外側のバージョン・アップ処理が変更する場合、内側のクラスのバージョン・アップ処理と競合する可能性があります。(両方のバージョン・アップ処理が内側のクラスの同じメンバ変数を変更する場合)
同様な競合がenum型についても発生します。他にポインタ型や非侵入型手動クラスでも発生します。
競合する場合は、外側のバージョン・アップ処理を有効とします。これはバージョン・アップ処理された変数については先にされた方を有効にすることで対処できます。
そのために、現状を見直し、下記のような対処を行います。
プリミティブの対処
参照ではなくコピーを保持するようにします。その際、オブジェクト追跡のために、元の変数のアドレスも伝達する必要があるので、ヘルパー・クラスを作って対処しました。
enum型の対処
enum型もバージョン変更に対応しているため、TheolizerVersionクラスを作って対応していました。
途中で基底型の変更も考えられるため、最初からコピー方式とし、オブジェクト追跡のための元の変数へのポインタも保持しています。このように上記のプリミティブの対処と同様な構造になっているので微小変更で対処できました。
ポインタ型の対処
バージョン・アップ処理でボインタによるオブジェクトの連結関係を変更するユーザ・プログラムの複雑さは気が遠くなりそうなので、現時点では禁止しました。変更しようとするとコンパイル・エラーになります。(operator=を実装しない。)
プリミティブと同じ仕組みを導入することで、バージョン・アップ時の変更を可能にすることもできますので、将来的に有用であることが分かったら対応する方向で検討します。
非侵入型手動クラスの対処
そもそも非侵入型手動クラスは、STLのコンテナ対応を主目的として実装しています。
これをコピー方式で引き継ぐと、非常に重くなることが予想されため、当面はポインタ型と同じくバージョン・アップ時の変更を禁止しました。(ポインタと同様operator=を実装しない。)
必要が生じるまでコピーを遅らせる実装も可能ですが、過去の経験からそこまでバージョン・アップ時の対応が必要になることはかなりレアと思いますので、当面は禁止とし、将来的に有用であることが分かったら対応する方向で検討します。
配列と参照について
配列はその基本型に応じて、上記対処します。
参照は「別名」として取り扱いますので、その参照先の型に応じて、上記対処します。
(2017/04/16 現状に合わせて修正。)
ところどころ、COMPLIER
という記述があるのですが、COMPILER
の誤字ではないでしょうか?
(Pull Requestすべきではあるのですが手元でビルドが通らずコミットできないため、Issueにて失礼します)
クラス・メンバ変数に対して、保存の有無、および、分割して保存/回復できるようにするための保存先指定機能の自動テスト実装中です。
sourcesフォルダの方でも依存ソフトウェアのダウンロード(できればビルドまで)の自動化が欲しいです。
実例を挙げますと、neovimではビルド時に依存ソフトウェアを自動でダウンロード&ビルドしています。
単純に回復すれば良いと思っていたのですが、複数回保存して回復した時、参照カウンタが回復できません。
boostのshared_ptrのシリアライズ・コードを確認したところ、この問題に対策されてました。
シリアライズしたshared_ptrをシリアライズする際、shared_ptrの追跡テーブルをstd::mapにて別途管理してました。firstはオブジェクトのアドレス、secondはshared_ptr自身です。
しかし、実装は意外に複雑です。
2番目以降の基底クラスへのポインタと派生クラス・オブジェクトのポインタはアドレスが一致しません。
なので、shared_ptr追跡テーブルへは派生クラス・オブジェクトのアドレスを登録しています。
更に回復処理時、aliasingコンストラクタを使っています
これは、元のshared_ptrと参照カウンタ、および、デリート処理(デリート先オブジェクトとデリータ)を共有しているがポイント先を自由に選択できるshared_ptrのコントラクタです。ポイント先の寿命が元の寿命以上の場合に使えます。
なぜ、aliasingコンストラクタを使っているのか非常に不思議だったのですが、この対策は近藤貴俊氏が行っていて丁寧に解説されていました。
簡単に説明すると、派生クラスへのポインタを管理しているshared_ptrと、それと同じインスンタスを2番目以降の基底クラスへのポインタで管理しているshart_ptrの2つを保存し、回復した時に不具合が出るのです。
shared_ptr追跡テーブルはshared_ptr<派生クラスの型>を使うことも可能な筈ですが、boostはstd::shared_ptr<void>
型で管理しています。
そのため、std::mapに登録されているshared_ptrを単純に返却すると、基底クラスへのポインタに派生クラスのアドレスが設定され、ずれてしまいます。
この問題を回避するために、aliasingコンストラクタをうまいこと使っていました。(これを使わないと複雑なことになりそうです。)
この2点の対処を実装する予定です。
これはIssue #6 からの引継ぎです。
現在、ビルド時にTheolizerライブラリとTheolizerドライバのソース、および、CMakeLists.txtのハッシュ値を計算しています。
これを、それぞれのバイナリから取れるようにしています。
そして、コミット時にこのハッシュ値をコミット・メッセージへ自動登録するようにしています。
バイナリで不具合が発生した時、そのソース・コードを特定することが目的です。
この目的に使えるハッシュ値として、gitのcommitハッシュ値もあります。コミット後にビルドして、バイナリへ埋め込むことが考えられます。
しかし、commitハッシュ値はコミット・メッセージを変更すると変わってしまうため、コミット後にビルドする必要が有ります。そのため、次のどちらかしか選択できません。
前者では、コミット・メッセージにテストにPASSしている旨を書けません。後者は無駄な手間がかかり、かつ、間違って再ビルド前のバイナリを配布すると特定できなくなります。
これが最大の理由で、現在の仕組みを採用しています。
他にも、次のような理由もあります。
ビルド時に登録するハッシュ値を計算しているため、ビルドしないと生成できず、コミット・メッセージに記録されません。そのため、リベースした時、非常に手間がかかります。
また、ビルド直前にハッシュ値を計算しているため、ビルドに失敗している可能性もあります。
更に、テストにPASSしたかどうかも記録されません。
ソースのハッシュ値はビルドしなくても計算できるようにして、リベース時も自動的に登録します。
既存のハッシュ値を更新するよう、コミット・メッセージに記録されている古いハッシュ値は削除します。
また、テストにPASSしたことの記録があれば、それも登録します。
全てのバイナリ(linux用やwindows用、staticやshared等)のテストは困難なので1セットのバイナリのテストとします。
なお、以上の処理を行うために、ビルド・フォルダをgitフック処理からアクセスできる必要が有ります。そこで、ビルド・フォルダパスが記録されたbinary_dir.cmakeがルートに有る時のみ上記処理を行い、無い時は上記のテストとハッシュの記録を削除してコミットするようにします。
見た目には、上記処理がないのと同等の動作となる予定です。
現在のTheolizerは、ユーザのビルド・システムに割り込むため、コンパイラを置き換えています。
これがTheolizerを評価していただく際の大きな障害になっているのではないかと懸念しています。
CMake + gccの組み合わせであれば、CMAKE_CXX_COMPILERを置き換えることで対処できそうなのですが、msvcはそれではできませんでした。MS Buildに割り込まないといけないため、単純にはできそうにないです。
何か良いアイデアはないでしょうか?
できることがわかればクラウド・ワークスで実験的な実装を募集してみたいと考えています。
travis_wait 40にて40分タイムアウトにしているのですが、10分~20分程度でタイムアウトしてます。
Travisは、ビルド・プロセスから10分間出力がでなければタイムアウトする仕様です。
Theolizerのビルド・プロセスは一度結果をCMake変数で獲得し、エラー・チェックと整形を行いログ出力しているため、ビルドやテスト中に出力がでません。(linuxならteeを使えば良いのですが、windowsで対処できないのです。)そして、ビルド時間が10分を超えることは少なくありません。テストはもっと長いです。
そこで、travis_waitコマンドで40分を指定することでほぼタイムアウトしないようになっていたのですが、今日のビルドでは10分~20分でタイムアウトします。
Auto Cancellation機能が22日に導入されたばかりのようですので、これが原因でtravis_waitが機能しなくなっているのかも知れません。
しばし様子を見たいと思います。
春頃のVisual Studio 2017のアップデートにより enum 型の処理でコンパラ内部エラーが出るようになりました。
対処する予定ですが、現在、他の業務が多忙なため、時間がかかりそうです。
boostを自動ダウンロードしてビルドするCMakeスクリプトを実装しています。
Windows 10上のVirtualBoxにインストールしたubuntu 16.04においてboostのダウンロードに失敗します。
CMakeのfile(DOWNLOAD ...)コマンドを使っています。
Windows 10ではboostのダウンロードは成功します。
(macでもダウンロードに成功するとの報告も受けています。)
llvmはWindows 10、および、上記ubuntuでもダウンロードに成功します。
VirtualBoxは特殊なので当面は手動対応とします。
もし、何か関連情報があったら、お寄せ頂ければ助かります。
関連Issue:#3
特にシリアライズ指定していないクラスについても、そのインスタンスを保存/回復コードがあるコンパイル単位ではシリアライズに必要なコードが自動生成されます。
しかし、コンパイル単位内で最初の保存/回復処理だけでシリアライズしている上記クラスについてシリアライズに必要なコードが自動生成されないことに気が付きました。
保存/回復処理(THEOLIZER_PROCESS)すると、内部的にSwitcherクラス・テンプレートにて型の種別毎に分岐するため、libToolingを用いてSwitcherの実体化を列挙し自動生成するクラスを判定しています。この列挙に出てこないため、自動生成されないことまで分かっています。
暫定対策は、下記があります。
早めに修正したいのですが、libToolingの動作が原因の可能性もあるため、修正には少し時間がかかりそうです。
現在、コミットする際には、ビルド完了後に得られるハッシュ値が必要です。
この状況においては、現在ビルドが通らない環境からのPull Requestによる貢献が出来ません。
私もそのような環境にあります。
この環境から問題解決をするには、Issueを通じてオーナーに任せるしかありません。
コントリビューターとしては、外野から意見ばかり言うのは心苦しく思います。
オーナー側としてもコードを(些細な誤字脱字等の修正でも)すべてご自分で書くというのは負担が大きいのではないでしょうか。
CIサービスを利用すれば、Pull Requestを自動でビルド、テストすることも出来ます。
これによって、動作しない状態のコミットが混入することは防げるかと思います。
ご一考よろしくお願いします。
Issue #30対策中に顕在化した問題です。
enum変更テストの追加により、テスト時間不足になる場合が出てきたようです。
更にクラス変更テストも追加するため、テスト時間は足りなくなるので、テストを多重起動する方向で検討します。
突然、Travis-CIのビルドに失敗するようになりました。
llvmのリンクでlibclangFrontend.a(FrontendAction.cpp.o): unrecognized relocation (0x2a) in section .text._ZN5clang14FrontendActionD2Ev
エラーでビルドに失敗します。
また、以前テストに成功していたものをre-runすると同じエラーで失敗します。
libclangFrontend.aはllvmに含まれるLibToolingライブラリの1つです。
そして、travisのllvmインストール・ログを見ると以前とインストール元が異なっているようです。
旧> http://apt.llvm.org/trusty/ llvm-toolchain-trusty-3.9/main libllvm3.9 amd64 1:3.9~svn288847-1~exp1 [10.6 MB]
新> http://archive.ubuntu.com/ubuntu/ trusty-updates/universe libllvm3.9v4 amd64 1:3.9.1-4ubuntu3~14.04.2 [10.6 MB]
ubuntuのリポジトリの別のバージョンのllvmですが、次のような不具合が報告されており、llvm-3.8でも同様な不具合で使えませんでした。
travis-ciへ報告しました。修正されることを期待しつつ、この問題は一旦保留します。
バージョン変更テストの記述中、エラー・ハンドリングの使い勝手が悪いことに気がついたので、見直しました。
現在、エラー情報は2ヶ所で保持してます。
ErrorReporterのstatic領域
こちらはerrornoに近いハンドリングをします。thread_localに記録してます。
API呼び出し時に自動クリアされ、API呼び出し中に発生したエラーと警告を記録します。
あまり使う必要はないと思いますが、シリアライザのデストラクタ内で発生したエラーを記録する場合に使えると考えています。(現時点では使っていません。)
シリアライザのインスタンス
シリアライズ処理を制御するための使い勝手を考慮してハンドリングします。
コンストラクト時とresetError()を呼び出した時のみクリアします。自動クリアはしません。
エラーが発生していたら、次のAPI呼び出しもエラーにしています。エラーが波及することを防ぐためです。
警告は通常通り処理されます。エラー同様自動クリアしません。
デストラクト時、エラーか警告が残っていたら意図的に異常終了させていますので、デストラクト前に明示的にクリアする必要があります。(クリア前にできるだけチェックして欲しいためです。)
例外を許可していたらエラー発生時例外を投げます。警告発生時は例外を投げません。
以上により、例外を許可することで、エラー/警告処理をあまりあちこちに書かないで済むことを狙っています。
今後ももう少し見直す可能性があるので、当Issueはしばらくオープンしておきます。
エラー処理について何かご意見がある時は当Issueまでお願いします。
http://doc.theolizer.com/html/_how_to_build_theolizer.html#HowToBuildLibrary121
によればcmakeファイルを書き換えろとのことだが、そういうのはoptionにするべきではないのか
https://twitter.com/yumetodo/status/843463875152379904
もしかすると、ビルド時間を短縮できる可能性があるようです。
時間が取れたらトライします。
追加情報。
https://twitter.com/yumetodo/status/843618927896739840
Issue #8 で報告した問題です。
MinGW x64で自動テストプログラムをコンパイルする際にFile too big
エラーがでてコンパイルできません。
この問題は、Compilation fails with MinGW-64 (gcc 4.8.1) #177で報告されているMinGW x64不具合と同じものと思われます。
そこで、上記報告にある対策で-O3 -Wa,-mbig-objを付けたところコンパイルは通るようになりましたが、通常数分で終わるビルドが1時間経過しても終わりません。ld.exeがいつまでも走ってます。途中で中断しました。
上記報告によるとこれはMinGW 4.9以前のもので確認され、その後5.3でも発生するとの報告があります。
また、この報告にgcc 6では発生しなかったとの報告があるのでMinGW 6.2.0でトライしましたが、File too big
は発生しました。
msvc、gcc、 MinGWx32でのビルドとテストにはPASSします。
そこで、v0.4.1ではMinGWx64のみ自動テストを少し縮小しています。
トップ・レベルにおける保存/回復の実装が概ね完了したので、プッシュします。
コミットを細かく刻む方向にしようと考えているのですが、今回はコミットを刻みこそないました。
今後、もう少し刻むよう努力します。
★1点仕様を検討中です。
普通にクラス・オブジェクトを保存する際、同じ領域を複数回保存してもエラー・チェックできないため、エラーにできません。・・・(1)
しかし、オブジェクト追跡している時は、同じ領域が複数回保存されると回復時混乱するため、エラーにしてました。
そして、クラス・オブジェクトへのポインタをシリアライズした場合、そのクラスのオブジェクトを全て自動的にオブジェクト追跡します。
クラスのバリエーション・テストで複数回保存していた(1)のクラスを、今回のオブジェクト追跡テストに用いたところ、クラスのバリエーション・テストでエラーが発生するようになりました。
私自身が原因追求に苦しんだため、一旦、(1)のケースはエラーにならないように修正しました。
だがしかし、本質的にエラーを検出した方がユーザのバグを検出しやすいです。
なので、どうするか検討中です。
Issue #18検討中に、別の問題に気が付きました。
現在のオブジェクト追跡は基底クラスのアドレスでオブジェクトのマッチングを行っています。
そのため、2番目以降の基底クラスのアドレスと実オブジェクト(派生クラス)のアドレスが一致しません。
追跡に失敗するケースがある筈であることがわかりました。
派生クラスのインスタンスを保存し、その派生クラスの2番目以降の基底クラスのポインタを保存した時、基底クラスのアドレスと派生クラスのアドレスが一致しないため、インスタンス未保存エラーになる筈です。(まだ現象未確認です。)
派生クラスへのポインタにてオブジェクト追跡すれば対策できるので、そのように実装予定です。
現在Theolizerをビルドするにあたっては、LLVMやBoostのソースコードをユーザーがダウンロードする必要があります。
これをビルド時に自動でダウンロードするようには出来ないでしょうか?
CMakeにはファイルをダウンロードするコマンドがあるようです。
https://cmake.org/cmake/help/v3.0/command/file.html?highlight=DOWNLOAD
また、関係あるかわかりませんが、LLVMの「NCSA/イリノイ大学オープンソースライセンス」およびBoostの「Boostソフトウェアライセンス」はいずれもGPLと両立できるようです。
https://www.gnu.org/licenses/license-list.ja.html#GPLCompatibleLicenses
Issue #15 からの分岐です。
ubuntu用にTravis CIで導入しました。Windows用は将来的に検討します。
一度使用したクラスについて、定義と使用の両方を同時に消した時に発生します。
一度使用すると、*.theolizer.hppにそのシリアライズ用定義が生成されます。
この定義が残っている状態でクラス定義を削除すると、clangのAST解析がメモリ不正アクセスで落ちます。
暫定対策として、問題発生時、最後のエラーを表示して処理中断するようにしました。
そのエラー・メッセージにより、一旦当該クラスの定義のみ戻して(使用はしない)、ビルドすることで回復できます。
恒久対策は、clangにて落ちないようにするしかないと思われます。
すぐには厳しいので、当問題を記録しておきます。
なお、当問題が発生することを確認したllvmのバージョンは3.9.0です。
当初よりxmlに対応するべきと考えていたので、それに着手します。
そのために、今更ですがXMLを学ぼうなどでxmlについて勉強中です。
さて、boost::serializationやC#のxmlシリアライザは、要素名に変数名を対応させています。
以前、C#でシリアライザを作った時も要素名に変数名を対応させたのですが、特に配列で違和感が強く感じました。配列には要素1つ1つに名前はついてなく、配列全体に1つの変数名がついてます。それをxmlの要素名に対応させるのが難しく、全部同じ名前にしてしまったことがあります。
boostも同様で、"item"という固定の要素名を付けるようです。更にboostはxmlの時はBOOST_SERIALIZATION_NVPマクロを使って変数名を要素名としてちょっと無理やりシリアライズしています。(他のシリアラアイザではBOOST_SERIALIZATION_NVPを使いません。)
この対応に違和感を感じていたのですが、それが「常識」かなと思い、Theolizerも要素名を変数名に対応させる方向で考えていました。
しかし、上記のxml入門を読んで要素名を変数名に対応させるから違和感がでているのではないかと感じています。「要素名」を「型名」に対応させた方が良いのではないかと思うのです。
例えば、オブジェクト指向言語の多くは1つのクラスに同じ型のメンバ変数を複数定義できますが、要素名に変数名を対応させた場合、DTDの定義が冗長になります。メンバ変数1つ1つを定義することになるので同じ定義が何回も出現することになります。配列でも同様です。更にboostのように"item"固定ではDTDを書けません。これはあまり適切な対応とは思えません。
TheolizerはTypeCheckモードかTypeCheckByIndexモードでシリアラズする時、型情報をヘッダへ出力して型チェックしています。これによりプログラム変更した結果デシリアライズできなくなっていないか積極的にチェックすることができます。これとDTDの目的は事実上同じのようです。
であれば、TheolizerにてDTDをサポートすることが原理的には可能な筈です。(現在の構造でスムーズにDTDをサポートできるかどうかは、今xmlを勉強している段階ですので未確認ですが。)
そこで、DTDを現実的な工数でサポートできるのか、まずは検討しようと思います。
Theolizerの実装だけでなく、ポインタの概念はxmlにないのでそれをどうするか等課題がいくつかありそうです。
先月末からメタ・デシリアライザに着手しています。(他業務が忙しくなかなか進みませんが、じわじわとは進めています。)
メタ・デシリアライザは、メタ・シリアライザ・データを読み込んでC#プログラムのソース・コード(クラス定義やenum定義、および、それらのデータを交換するために必要な各種定義)を自動生成するものです。
C#にはソース・コード自動生成にも使えるT4テキスト・テンプレートという機能があります。C++のマクロを超強力にした感じのツールです。(マクロ展開中にif文やfor文を記述して展開処理を制御できる。)
これを使うと、比較的お手軽に各種定義を出力できそうです。
メタ・デシリアライザ用のプロジェクト・ファイルもCMakeで自動生成しようとしていたのですが、CMakeは T4テキスト・テンプレートに対応していませんでした。また、ヒョイッと対応できるほど簡単ではないです。(たぶん1~2週間かければ対応できるでしょうけど、需要はあまりなさそう)
そこで、手で作った(CMakeで生成後、手動でT4テキストテンプレート生成)プロジェクト・ファイルを雛形とし、CMakeのconfigure_fileで必要なパラメータ(各フォルダのパス等)を埋め、include_external_msprojectでプロジェクトへ取り込むことにしました。
メタ・デシリアライザはTheolizerのC#側のTheolizerドライバ的な位置づけのプログラムですので、一般のユーザさんが修正するものではないですし、そもそもVisual Studio限定ですので問題はないと思います。
enum型の様々なケースの自動テスト実装中
この作業も少しステップを踏みます。
そもそもC#側をGUI対応にしたいので、Windows Formsとして生成中です。
概ねできたのですが、CMakeの不具合が2つ見つかっています。
CMake で C# のプロジェクトを作ろうとしてハマった話はこれです。
CMakeのGitLabにマージリクエストを投げる予定ですが、上記発見者の了解を貰おうと待っているところです。しばらく待って回答を貰えない場合は、必要なリンクを貼ってから投げようと思います。
もう一つは、正直ほっといても良いと思うのですが、Settings.Designer.csをC#プロジェクト・ファイルに仕込む時にミスがあるようで、Visual Studioが警告してきます。警告通り処理するとSettings1.Designer.csができてしまい、ちょっと気持ち悪い状況です。
CMakeのソースをcloneしていたので、ちょっと覗いてみると意外に簡単に修正できそうなので修正してみたところ、少なくとも上記トライしているプロジェクトでは警告がなくなりました。
マージリクエストしようかどうか考え中です。CTestするとFailするので悩ましい状況です。
C#とC++間のデータ交換はシリアライズされたバイト列で行います。それをC#側クライアントexeとC++側サーバdllにてやり取りしたいため、その仕組みを実装する予定です。
C#側はStreamを派生、C++側はstdd::streambufを派生して出来るはずですので、チャレンジしてみます。
最初はデバッグしやすいようJson形式で考えています。
その後Binary形式の開発になりますが、こちらはもしかするとクラウドソーシングで外注するかもしれません。恐らく年内は厳しいと思います。
コンパイラ、ライブラリ、ツール群のバージョンアップを開始します。
Visual C++とgcc, MinGWを最新版へ。
libToolingとbootを最新版へ。
また、libToolingはできるだけバッケージを使用するように変更したいと思います。
Windows版のlibToolingはプリビルドが提供されていないため、ローカル環境でビルドしたものを共有できないか検討してみます。
CMakeはboostのバージョンアップに合わせてバージョンアップが必要なため、バージョンアップする可能性があります。(なお、Issue #16 にてVisual C++用は3.8.0へ上げています。)
その他、DoxygenとDotについては、積極的な理由が発生しなければ現状のままとします。
「2.ライブラリ」の仕組みを導入するかどうか検討します。
sudoが必要になるためちょっと悩ましいですが、ターゲットのビルド・システムにより近いテストができるので好ましいことと、Issue #38 のような問題を避けられることを期待できます。
やっと確定申告も終わったので、enum型について変更方法とバージョンアップ方法の解説、および、その自動テストの実装を着手します。
今までの網羅的な自動テストはバージョンアップの自動テストを見据えて、version/ver1で開発してましたがテスト時間がかなり長いので、この基本部分の網羅テストは「変更/バージョンアップ」テストから分離しようと思います。
「変更/バージョンアップ」テストは必要な部分に絞って実装します。
Issue #16 からの分岐です。
CMake 3.8.0でCTestに次の修正が入りました。
恐らくそれが原因で、Theolizerにより生成されたアプリのビルド・テストが文字化けするようになりました。コンパイラのエラー・メッセージが全く読めないので、非常に痛いです。
このIssueでこの問題に対処します。
現在、THEOLIZER_INTRUSIVE()マクロで、侵入型半自動クラスであることを指定していますが、ここに当該クラス名を書く必要が有ります。
しかし、teratailにて聞いてみたところ、クラス名を省略できる方法が見つかりました。
後日、具体的な対応を検討します。
もっと早く聞いておけば良かった。ほとんど諦めていたので聞きそびれてました。orz
「version.hのコンフリクト回避のための対策実施」の追加対策を検討し、必要なら実施します。
主旨としては、ビルドされていないソースがcommitされるとSourcesHashが役に立たなくなるので、できれば避けたい。
メモリ・リーク検出をmsvc向けに導入していたのですが、事情によりオープンβ開始時に一旦削除していました。それを回復したところ、特殊なケースですがリークが見つかりました。
オーナーポインタp, qがシリアライズ時に同じインスタンスを指していたとします。
そのデータをデシリアライズする際に、オーナーポインタpとqが異なるインスタンスを指していた場合にqが指していたインスタンスの解放漏れが見つかりました。
この不具合は早期に修正します。
またgcc用にもリーク検出を追加する予定です。
対応可能な標準のコンテナとスマート・ポインタへの正規対応を行います。
現在は下記について仮対応しています。
現在は、デフォルトがあるテンプレート・パラメータに対応していません。
それらも含めて、できるだけフル・スペックでの対応を目指します。
なかなか時間が取れていませんが、C#連携用のメタ・シリアライザ開発を進めてます。
概ねの方針を決定できましたので開示します。
現在は、NoTypeCheck、TypeCheck、TypeCheckByIndexの3種類をサポートしています。
NoTypeCheckは型チェックなし。TypeCheckとTypeCheckByIndexは、型チェックのためヘッダに型情報を入れています。TypeCheckは型名、TypeCheckByIndexは型に割り振ったインデックス番号をTHEOLIZER_PROCESSで保存/回復するデータに記録し、型の不一致が起きないことをチェックしています。
データ内に記録する制御用データが最小なのでTypeCheckByIndexが最も効率が高いです。
NoTypeCheckはヘッダ情報が少ないため、小さなデータのやり取りには向いていますが、名前対応型のクラスについてはメンバ変数名をデータ中に記録するため大きなデータでは効率が悪化します。
そこで、TypeCheckByIndexをベースにメタ・シリアライズ・データをヘッダ形式で出力するようにします。
enum型に属するシンボルのリストや、各型のTheolizerとしての拡張情報(enum型の定義情報などなど)を追加するイメージです。
なお、開発工数削減のため、最も必要性の薄いTypeCheckを削除します。
以上の結果、従来のデータとの互換性を失います。
現在、プリミティブの構成は派生シリアライザで決定しています。
これにより、JsonSerializerとXmlSerializerの文字列はUTF-8の1種類のみ、BinarySerializerとFastSerializerはエンコード変換しません。BinarySerializerは型チェックするのでstd::string, std::wstring, std::u16string, std::u32stringの4種類を異なるプリミティブとしています。
JsonSerializerとXmlSerializerでUTF-8以外のデータをstd::string, std::wstring, std::u16string, std::u32string型変数との間で保存/回復する時は、それぞれの派生シリアライザが自動的にエンコードを変換しています。
さて、C#連携では性能をできるだけ上げるため早期にBinarySerializerを使えるようにしたいと考えています。その際、BinarySerializerで用いる文字列のエンコードはC#の文字エンコードであるUTF-16LEが最適です。
そこで、BinarySerializerはstd::string, std::wstring, std::u16string, std::u32stringのデータを全てUTF-16LEへ自動的に変換して保存/回復します。これによりC#側ではエンコード変換が発生しません。
また、C++側の文字列をstd::wstring、もしくは、std::u16stringで記録した場合、C++側でもエンコード変換が発生しません。なお、経験的にはC++側はstd::stringでUTF-8を用いて文字列を管理する方が、他のASCIIで文字列を取り扱うライブラリとの親和性が高く、開発工数を節減できると思います。性能的に問題ないならC++側はstd::stringのUTF-8で文字列を管理することをご検討下さい。
そのためのサポートしてtheolizer::u8stringが便利と思います。
保存/回復する変数の型にTypeIndexという番号を割り当てて管理しています。現在は配列とポインタにも元の型とは異なる専用のTypeIndexを割り当てています。
しかし、これはヘッダを肥大化させるのであまり好ましくありませんので下記対応を行います。
TypeIndexは配列やポインタについては割り当てません
基本の型についてのみTypeIndexを割り当てます。int型等の基本型、enum型、クラス、実体化したテンプレートに割り当てます。それらの型の配列やポインタは同じTypeIndexをベースとして表現します。
型チェックのためにはデータが配列やポインタであることを判定する必要がありますので、付加情報を追加します。
ただし、付加情報の肥大化を防ぐため、サポートする配列の最大の次元数は3とします。3次元を超える配列を使いたいケースは稀です。また、もしも4次元以上の配列をシリアライズしたい場合は、一旦クラスでラップすることで対応可能です。
配列へのポインタをサポートから外します
現在のTheolizerは配列へのポインタをサポートしています。(例えば、'typedef int IntArray[100]; IntArray* IntArrayPointer;'のIntArrayPointerは、int[100]
型へのポインタです。このIntArrayPointerをオーナーポインタとして保存/回復をサポートしています。)
しかし、そもそもC/C++では配列全体へのポインタを使うことは稀です。(配列の要素へのポインタは普通に使いますが、配列全体へのポインタは言語仕様上、使いづらいためです。)
そして、配列へのポインタをサポートすると「配列へのポインタ」の配列、『「配列へのポインタ」の配列へのポインタ』、...を構成することが可能となるのでTypeIndexの付加情報で管理するためにはTypeIndexを可変長にする必要があります。
使用頻度の低い機能のサポートのためにTypeIndexが複雑化することを避けるため「配列へのポインタ」をサポートから外します。
C++側のメタ・シリアライザ
これはユーザ・プログラムの各種クラスやenumの定義情報等が必要ですので、ユーザ・プログラム内でC#連携を指定してシリアライザをコンストラクトすることで出力されるようにします。
具体的な起動手順は未定ですが、ユーザ・プログラムがオプションを指定して起動されたら、メタ・シリアライズ機能を呼び出して頂くイメージになる予定です。
僅かなコードをmain()関数やWinMain()関数へ組込むことで対応できるようにする予定です。
C#側のメタ・デシリアライザ
メタ・シリアライズ・データを解析し、C#のenumやクラス定義ソースを出力する、メタ・デシリアライザを追加します。こちらはユーザ・プログラムではなく独立したプログラムをTheolizer側で用意します。
Issue #42 の「4-1.C#側シリアライザの開発」に記載したように、C#側はそのソースが自動生成された時の最新版のみをサポートします。C++側は自分より古いもの全てをサポートします。
これにより、C++側の「サーバ」を最新版へアップしておくことで古いクライアントも機能できる状態にする計画です。これはTheolizerのバージョン変更対応機能を使用します。
C++側ユーザ・プログラムは、古いバージョンのユーザ・プログラムが出力したデータを回復する新しいバージョンのユーザ・プログラムを開発することは比較的容易です。逆に古いバージョンのデータを出力することもTheolizerのサポートにより可能ですが、バージョンアップ対応に比べると手間がかかります。
C#連携の場合、C#側とC++側のユーザ・プログラムのバージョンが一致していない場合、ユーザ・プログラムは旧バージョン・データ出力に対応する必要があります。従って、事情が許す場合はC++側とC#側のユーザ・プログラムのバージョンを一致させる運用にすることがお薦めです。
C++側ユーザ・プログラムが呼び出すメンバ関数、および、C#側ユーザ・プログラムが呼び出すメンバ関数については、当該メンバ関数呼び出しを相手側へ中継するため、下記の処理を行います。(C#側からの呼び出しのみ説明します。C++側からの呼び出しはこの逆のサブセットです。)
C#側のメンバ関数にて、メンバ関数クラス・オブジェクトを生成する
メンバ関数クラスは、当該関数の引数をメンバ変数として持つクラスです。これをシリアライズしてC++側へ送ることで、C++側の該当のメンバ関数呼び出しに各引数を伝達します。
その際に、共有オブジェクトを共有テーブルへ登録する
thisは往復しますから、常に共有オブジェクトとなります。また、C++側で定義したメンバ関数のシグニチャで非const参照渡しされている引数も同様に往復しますから、やはり共有オブジェクトとなります。
C++側スレッドはデシリアライズしたメンバ関数クラス・オブジェクトを取り出す
そして、その引数を与えて指定のメンバ関数を呼び出します。これにより、C#側のメンバ関数呼び出しがC++側へ中継されます。
C++側のユーザ・プログラムが定義したメンバ関数から帰ってきたら
this、戻り値、非const参照引数をメンバ変数とするメンバ関数の戻りクラス・オブジェクトを生成し、それをシリアライズしてC#側へ返却します。
C#側のメンバ関数を送信したスレッドがそれを受信し、
メンバ関数の戻りクラス・オブジェクトを取り出す際に、this、および、非const参照引数へ反映後、C++側から送られてきた戻り値をreturnします。
なお、C++側のユーザ・プログラムがメンバ関数を呼び出す時は、C#側のガベージ・コレクションによるブロックを回避するため、FIFOへ書き込んだらC#側スレッドの受信処理を待つこと無くreturnします。つまり、上記の4以降はスキップされます。
C#とC++のプロジェクトをCMakeにて生成できることは判っていますが、プロジェクトのメンテナンス方法の学習はたいへんです。C++だけならまだしも、C#プロジェクトまでCMakeだけで管理するのは厳しそうです。
そこで、Visual Studioについては、Visual Studioのtoolset機能を使い、ユーザ・プログラム開発はCMakeフリーにする方向で検討中です。
Theolizer自体のビルド・システムも見直す予定です。Issue #37 に記載したCTestの変更の内容より、ビルドとテストは明確に分けるべきと判断しました。現在はテストをビルドに依存させることでテストを起動すればビルドされるようにしています。しかし、CMakeの一般的な流れに戻し、ビルドとテストを独立させ、CMakeスクリプトにより必要に応じて連続呼び出しするようにします。
また、Theolizerドライバも含むテストについて、よりスマートに記述できることが分かりました。現在はCMakeスクリプトで直接コンパイラを起動しています。素直にCMakeでプロジェクトを生成してCMakeでビルドし、CTestでテストするよう変更する予定です。
STLコンテナを交換できるようにしたいと思います。そこで、非侵入型手動のテンプレートはC#連携でもサポートする予定です。
またC#はジェネリックの明示的特殊化や部分的特殊化をサポートしていません。現在のTheolizerも同様です。そこで、明示的特殊化と部分的特殊化は非対応のままと致します。
また、標準のコンテナ名はC#とC++では異なります。またC++側はかなり細分化されています。そこで、C++側の複数のコンテナ(vectorやlist等)をC#の1つのコンテナ(List等)に対応させる仕様にする予定です。
なお、メンバ変数の変更に自動的に対応する侵入型半自動テンプレート、および、非侵入型完全自動テンプレート対応の難易度は高く、その必要性は比較的低いと思いますので、C#連携としては当面は対応しないことにします。必要性が見えてきたら再検討します。
v0.5.0にてほぼ全ての機能の説明と自動テストの実装が終わりました。
エラー処理周り等、残りの解説と必要なら自動テスト、および、細々した調整を行って正式公開します。
クラス(class/struct)の定義変更方法とバージョンアップ方法の解説、および、その自動テストの実装に着手しました。
クラスの変更対応に着手し、全体を俯瞰した結果、「参照」処理を見直すことにしました。
参照は単なる別名として扱い、複数回シリアライズ指定されたら、その度に保存してました。
しかし、よく考えてみると重複チェックできる場面があります。
オブジェクト追跡指定(Pointee指定)している場合は重複チェック可能
オブジェクト追跡しない時は重複チェックできないため、多重保存します。
この2つの仕様が混乱することを恐れてました。
当初はクラスのオブジェクト追跡の有無は自動化していたが混乱回避のため削除した
なので、明示的に指定しない限り追跡しないため重複保存されるかどうかは明確です。
そこで、オブジェクト追跡しているインスタンスについては、シリアライズ単位内で複数回同じインスタンスをシリアライズしたら、先頭のみ保存するようにする予定です。
なお、これは参照経由に限りません。変数を直接複数回シリアライズした時も同様です。
(追跡指定しているものは重複保存を避け、追跡指定していないものは重複保存する。)
基底クラスへの参照で、派生クラスのインスタンスを指すことができます。
そして、typeid()を使えば派生クラスであることを判別できます。
ポインタ経由の派生クラスへの回復は実装していますので、それを流用して参照にも対応できそうです。ポインタ経由の場合と異なり参照はアドレスを変更できないため、型不一致の場合の処理の変更が主な変更になります。
こんにちは。田原です。
当面、Theolizerに関するコミュニケーションの場としてIssuesを運用します。
私、および、Theolizerを使っている方に何かお伝えになりたい時はここを用いて下さい。
もし、公開したくないことを私へ伝えたい場合は、theolizer.comのお問い合わせをご利用下さい。
ここからの分岐です。
現在、addonsのインストールとboostのビルドにどちらも2 ~3分かかってます。
この2つは常に同じものなのでできればキャッシュを有効にする方向で検討します。
A declarative, efficient, and flexible JavaScript library for building user interfaces.
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google ❤️ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.