1. YosukeM

    Posted

    YosukeM
Changes in title
+libclangを使ってC++のメンバにアノテーションをつける
Changes in tags
Changes in body
Source | HTML | Preview
@@ -0,0 +1,101 @@
+# 動機
+[Unreal EngineのUPPROPETYマクロ](https://docs.unrealengine.com/latest/INT/Programming/Introduction/index.html)のようなことがやりたいです。
+
+```lang:c++
+class MyActor : public ActorBase {
+ ACTOR_DECL()
+
+ PROPETY(Serialize)
+ s32 mVarA;
+}
+```
+
+C#やJavaにはReflectionの機能があるので、こういったものが簡単に作れます。しかし、C++でメンバ変数の一覧や、その隣に書いてあるマクロを得るのは簡単ではありません。言語組み込みの機能を使って強引にやろうとすると、どうしても実行効率が犠牲になってしまいます。
+そこで、コードジェネレータを利用することにしました。
+![build_flow.png](https://qiita-image-store.s3.amazonaws.com/0/2547/ee6982e1-3314-97a9-82ba-358ba0d41822.png)
+このフローを実現するためには、C++のヘッダファイルを正しく解析する必要があります。今回はclangの構文解析器を使用して実装してみました。
+
+環境はMacOS X 10.11、Clang 3.7を使用しました。
+
+#Clang python bindingのセットアップ
+MacPortsからclang-mp-3.7をインストールします。
+続いて、公式svnから[python-bindings](http://llvm.org/svn/llvm-project/cfe/trunk/bindings/python/)をダウンロードします。
+環境変数にPYTHONPATHに、先ほどダウンロードしたパスを設定しておきます。
+`export PYTHONPATH="~/cfe-3.7.1.src/bindings/python"`
+これでめでたくpython bindingsが使えるようになりました。
+
+```lang:python
+#coding: utf-8
+import clang.cindex
+from clang.cindex import Config
+from clang.cindex import Index
+from clang.cindex import TranslationUnit
+
+# Clangの設定
+Config.set_library_path("/opt/local/libexec/llvm-3.7/lib")
+clang_args = [
+ "-std=c++11",
+ "-I./",
+ ]
+
+# 構文木をダンプ
+def dump_ast(cursor, indent = ""):
+ print("%s%s : %s" % (indent, cursor.kind.name, cursor.displayname))
+ for child in cursor.get_children():
+ dump_ast(child, indent + "\t")
+
+index = Index.create()
+tu = index.parse("MyActor.h", clang_args, None, TranslationUnit.PARSE_SKIP_FUNCTION_BODIES)
+dump_ast(tu.cursor)
+```
+
+# ヘッダファイルを読ませる
+そもそもclangがパースできるのは翻訳単位(≒cpp)であってヘッダではありません。
+上のコードでは何も考えずにヘッダファイルを渡してしまっているため、classなどが正しく読み込まれません。おそらくC言語のヘッダとして解釈されているのだと思います。
+そこで、#includeが一つだけ存在する架空のcppを作ることで、C++として認識させます。
+また、#includeによって連鎖的に読み込まれる他のファイルの情報は不要ですので、今回は無視します。
+
+```lang:python
+buf = "#include \"MyActor.h\""
+tu = index.parse("MyActor.cpp", clang_args, [("MyActor.cpp", buf)], TranslationUnit.PARSE_SKIP_FUNCTION_BODIES)
+for tu_cursor in tu.cursor.get_children():
+ for node_cursor in tu_cursor.get_children():
+ if node_cursor.location.file.name == "./MyActor.h":
+ dump_ast(node_cursor)
+```
+
+# アノテーションを取得する
+PROPETY(Serialize)の部分はマクロを使って記述しようと思います。
+しかし、構文木の段階ではマクロは展開されているため、そのまま見つけることはできません。
+そこで、今回はマクロ内でダミーの関数を定義することにしました。
+
+```lang:cpp
+#define UNIQ_NAME_IMPL(SYMBOL_, LINE_) SYMBOL_ ## LINE_
+#define UNIQ_NAME(SYMBOL_, LINE_) UNIQ_NAME_IMPL(SYMBOL_, LINE_)
+
+#define PROPETY(...) PROPETY_IMPL(#__VA_ARGS__)
+#define PROPETY_IMPL(ARGS_) void UNIQ_NAME(_dummy_property_, __LINE__)(const char* arg = ARGS_);
+```
+
+こういう感じでマクロを作っておけば、PROPETY()マクロの位置に_dummy_property_XXXという名前のダミー関数が宣言されます。
+構文木の内容は以下のようになっているはずです。
+
+```
+CLASS_DECL : MyActor
+ CXX_METHOD : _dummy_property_4(const char *)
+ PARM_DECL : args
+ UNEXPOSED_EXPR :
+ STRING_LITERAL : "Serialize"
+ FIELD_DECL : mVarA
+```
+
+ここまで来たら、あとは難しいことはなにもありません。
+注意するべき事があるとしたら、名前空間とクラスのネストを正しく扱うことくらいでしょうか。
+細かい実装は省略します。
+
+# まとめ
+libclangを使うのは初めてでしたが、意外と簡単に扱うことができました。
+ただ、clangの認識しているのは「翻訳単位」であって「型」ではないので、そこをきちんと考慮しないと、C#やJavaのリフレクションと同じようには使えませんでした。
+また、ダミー関数の存在に関しては、ある程度割り切る必要があるのかなという印象です。どうせストリップされるので、ほとんど問題ないとは思いますが。
+
+C++でリフレクションが使えるとなると、シリアライズの他にも、デバッグ描画を自動で作ったり、アクセサを自動で定義したりと、夢が広がります。