C++で例外発生箇所(ファイルパス/ファイル行番号/関数名)の特定
まえがき
今回は、C++で例外発生箇所を特定する方法についてです。
C++やC#といったオブジェクト指向言語では、エラー処理は例外(Exception)という機構を使うことが一般的です。
C++では、例外をthrowするときにエラー情報を文字列として渡すことで、例外をcatchしたときにその文字列を参照することが出来ます。
try{/* エラーメッセージを指定して例外をthrow */throw std::runtime_error("Error Message");}/* 例外をcatch */catch(const std::exception& ex){/* エラーメッセージを標準エラー出力に表示 */std::cerr << ex.what() << std::endl;}
しかし、文字列の情報だけだと、どの関数でどういう状況で例外が発生したかを特定するのが困難なことがよくあります。
C#などの場合は、スタックトレース(関数の呼出履歴)などの情報も参照することが出来ますが、C++デフォルトでは見ることが出来ません。
そこで、今回は標準で定義されている例外を拡張して、例外発生箇所やスタックトレースを取得する方法を実装してみたいと思います。
前提条件
今回実装したソースは以下の環境でビルド、動作確認しています。
OS | Ver | Compiler | Remarks |
---|---|---|---|
Windows | 10 | Visual Studio 2019 | |
macOS | 10.15.6 | Clang 11.0 | |
Ubuntu | 18.04 | GCC 7.5.0 |
例外発生箇所の特定
まずは、例外発生箇所を特定できるような例外クラスを定義します。
ExceptionBaseクラス
ExceptionBase
クラスは、例外発生箇所を特定する情報を持った基底クラスです。
このクラスを継承した例外クラスを定義して使用します。
【ExceptionBase.h】
#pragma once#include <exception>#include <string>namespace exception{/* エラー情報 */struct ErrorInfo{/* ファイルパス */std::string file_path;/* 関数名 */std::string function_name;/* ファイル行番号 */int line_number;/* コンストラクタ */ErrorInfo(const std::string& file, const std::string& func, const int line): file_path(file), function_name(func), line_number(line){/* Nothing to do */}};/* Exception Baseクラス宣言 */class ExceptionBase : public std::exception{public:/* コンストラクタ */ExceptionBase(const std::string& message);/* コンストラクタ */ExceptionBase(const std::string& message, const std::string& file, const std::string& func, const int line);/* デストラクタ */virtual ~ExceptionBase();/* ファイルパスを取得 */const std::string& GetFilePath();/* 関数名を取得 */const std::string& GetFunctionName();/* ファイル行番号を取得 */int GetLineNumber();/* エラー情報を取得 */const ErrorInfo& GetErrorInfo();/* エラー要因を取得 */virtual char const* what() const noexcept override;protected:/* エラーメッセージ */std::string m_Message;/* ファイルパス */std::string m_FilePath;/* 関数名 */std::string m_FunctionName;/* ファイル行番号 */int m_LineNumber;/* エラー情報 */ErrorInfo m_ErrorInfo;/* エラー情報有無 */bool m_IsErrorInfoExists;};}
【ExceptionBase.cpp】
#include "ExceptionBase.h"#include <sstream>namespace exception{/* コンストラクタ */ExceptionBase::ExceptionBase(const std::string& message): m_Message(message), m_FilePath(""), m_FunctionName(""), m_LineNumber(0), m_ErrorInfo("", "", 0), m_IsErrorInfoExists(false){/* Nothing to do */}/* コンストラクタ */ExceptionBase::ExceptionBase(const std::string& message, const std::string& file, const std::string& func, const int line): m_Message(message), m_FilePath(file), m_FunctionName(func), m_LineNumber(line), m_ErrorInfo(file, func, line), m_IsErrorInfoExists(true){/* Nothing to do */}/* デストラクタ */ExceptionBase::~ExceptionBase(){/* Nothing to do */}/* ファイルパスを取得 */const std::string& ExceptionBase::GetFilePath(){return this->m_FilePath;}/* 関数名を取得 */const std::string& ExceptionBase::GetFunctionName(){return this->m_FunctionName;}/* ファイル行番号を取得 */int ExceptionBase::GetLineNumber(){return this->m_LineNumber;}/* エラー情報を取得 */const ErrorInfo& ExceptionBase::GetErrorInfo(){return this->m_ErrorInfo;}/* エラー要因を取得 */char const* ExceptionBase::what() const noexcept{/* エラー情報が存在する場合 */if (this->m_IsErrorInfoExists == true){std::stringstream ss;/* エラーメッセージに、エラー発生箇所(関数名、ファイルパス、ファイル行番号)を付加 */ss << this->m_Message << " @ " << this->m_FunctionName << "[" << this->m_FilePath << ": L." << this->m_LineNumber << "]";std::string message = ss.str();return message.c_str();}/* エラー情報が存在しない場合 */else{/* エラーメッセージのみ出力 */return this->m_Message.c_str();}}}
ExceptionBase
クラスでは、コンストラクタの引数としてエラーメッセージ(message
)以外に、ファイルパス(file
)、関数名(func
)、ファイル行番号(line
)を渡せるようにします。
また、各種エラー情報を取得するための公開関数(GetFilePath
/ GetFunctionName
/ GetLineNumber
/ GetErrorInfo
)を定義しています。
更に、エラーメッセージを取得するwhat
関数をオーバーライドし、エラー情報(ファイルパス、行番号、関数名)が渡されている場合は、エラーメッセージにエラー発生箇所を付加して返すようにします。
ExceptionBase継承サンプル
ExceptionBase
クラスを継承した例外クラスのサンプルとして、以下の2種類を実装します。
- AppException
- SocketException
基本的な方針としては、以下の方法で例外クラスを定義します。
- ExceptionBaseクラスを継承する
- 必要に応じて必要なパラメータを追加したコンストラクタを定義する
- what関数をオーバーライドしてエラー情報を表すメッセージの出力をカスタマイズする
- ファイルパス、ファイル行番号、関数名を取得するマクロを自動的に挿入するマクロ関数を定義する(★1)
★1 ファイルパス、ファイル行番号、関数名を取得するマクロはコンパイラが提供しており、それぞれ以下のマクロになります。
マクロ名 | 機能 | 備考 |
---|---|---|
FILE | ファイルパス(絶対パス)の取得 | |
LINE | ファイル行番号の取得 | |
FUNCTION | 関数名の取得 |
AppExceptionクラス
AppException
クラスは、汎用的に使用できる例外クラスで、ExceptionBase
をそのまま継承したクラスになります。
【AppException.h】
#pragma once#include "ExceptionBase.h"#include <vector>namespace exception{/* Application Exceptionクラス宣言 */class AppException : public ExceptionBase{public:/* コンストラクタ */AppException(const std::string& message);/* コンストラクタ */AppException(const std::string& message, const std::string& file, const std::string& func, const int line);/* デストラクタ */virtual ~AppException();};}/* Application Exceptionのthrow */#define THROW_APP_EXCEPTION(message) \throw exception::AppException(message, __FILE__, __FUNCTION__, __LINE__)
【AppException.cpp】
#include "AppException.h"namespace exception{/* コンストラクタ */AppException::AppException(const std::string& message): ExceptionBase(message){/* Nothing to do */}/* コンストラクタ */AppException::AppException(const std::string& message, const std::string& file, const std::string& func, const int line): ExceptionBase(message, file, func, line){/* Nothing to do */}/* デストラクタ */AppException::~AppException(){/* Nothing to do */}}
使用方法としては、エラー発生時に、マクロ関数THROW_APP_EXCEPTION
を呼び出すことで、呼び出し箇所のファイルパス、関数名、ファイル行番号を表すマクロが自動的に引数として渡され、エラー情報として格納されます。
そして例外をcatchしたときにwhat
関数を呼び出すことで、エラー発生箇所の情報が付加されたエラーメッセージを取得することが出来ます。
【Test.cpp】
#include <iostream>#include "StringFormat.h"#include "AppException.h"static void ExceptionTest();static void TestFunc1();static void TestFunc2();int main(){try{ExceptionTest();}catch (std::exception& ex){std::cerr << StringFormat("[ERROR] %s", ex.what()) << std::endl;}}static void ExceptionTest(){TestFunc1();}static void TestFunc1(){TestFunc2();}static void TestFunc2(){THROW_APP_EXCEPTION("Invoke Application Exception");}
上記のような例の場合、実行結果は以下のようになります。
[ERROR] [Application Error] Invoke Application Exception @ TestFunc2[/<省略>/Test.cpp:L.34]
SocketExceptionクラス
SocketException
クラスは、ソケット通信関連処理で発生したエラーを通知する例外クラスで、ソケットAPIのエラーコードを保持することが出来ます。
【SocketException.h】
#pragma once#include "ExceptionBase.h"namespace exception{/* Socket Exceptionクラス宣言 */class SocketException : public ExceptionBase{public:/* コンストラクタ */SocketException(const std::string& message, const int error_code);/* コンストラクタ */SocketException(const std::string& message, const int error_code, const std::string& file, const std::string& func, const int line);/* デストラクタ */virtual ~SocketException();/* エラーコードを取得 */int GetErrorCode();/* エラー要因を取得 */virtual char const* what() const noexcept override;private:/* エラーメッセージ生成 */const std::string GenerateErrorMessage();private:/* エラーコード */int m_ErrorCode;/* エラーメッセージ */std::string m_ErrorMessage;};}/* Socket Exceptionのthrow */#define THROW_SOCKET_EXCEPTION(message, error_code) \throw exception::SocketException(message, error_code, __FILE__, __FUNCTION__, __LINE__)
【SocketException.cpp】
#include "SocketException.h"#include "StringFormat.h"namespace exception{/* コンストラクタ */SocketException::SocketException(const std::string& message, const int error_code): ExceptionBase(message), m_ErrorCode(error_code), m_ErrorMessage(""){/* エラーメッセージ生成 */this->m_ErrorMessage = this->GenerateErrorMessage();}/* コンストラクタ */SocketException::SocketException(const std::string& message, const int error_code, const std::string& file, const std::string& func, const int line): ExceptionBase(message, file, func, line), m_ErrorCode(error_code), m_ErrorMessage(""){/* エラーメッセージ生成 */this->m_ErrorMessage = this->GenerateErrorMessage();}/* デストラクタ */SocketException::~SocketException(){/* Nothing to do */}/* エラーコードを取得 */int SocketException::GetErrorCode(){return this->m_ErrorCode;}/* エラー要因を取得 */char const* SocketException::what() const noexcept{return this->m_ErrorMessage.c_str();}/* エラーメッセージ生成 */const std::string SocketException::GenerateErrorMessage(){std::stringstream ss;/* エラー情報がある場合は、エラー情報付きメッセージを生成 */if (this->m_IsErrorInfoExists == true){ss << StringFormat("[Socket Error] %s : Error Code = %d @ %s[%s:L.%d]", this->m_Message, this->m_ErrorCode, this->m_FunctionName, this->m_FilePath, this->m_LineNumber);}else{ss << StringFormat("[Socket Error] %s : Error Code = %d", this->m_Message, this->m_ErrorCode);}ss << std::endl;return ss.str();}}
使用方法としては、AppException
クラスと同様、エラー発生時にマクロ関数THROW_SOCKET_EXCEPTION
を呼び出すことで、呼び出し箇所のファイルパス、関数名、ファイル行番号を表すマクロが自動的に引数として渡され、エラー情報として格納されます。
その際に、ソケットAPI失敗時のエラーコードも引数として渡すことが出来ます。
そして例外をcatchしたときにwhat
関数を呼び出すことで、エラー発生箇所の情報とエラーコードが付加されたエラーメッセージを取得することが出来ます。
/* UDPユニキャスト送信用ソケットオープン */void OpenUdpUniTxSocket(const std::string& remote_ip, const uint16_t remote_port){/* UDP用ソケットをオープン */int socket = socket(AF_INET, SOCK_DGRAM, 0);/* ソケットオープン失敗時のエラー処理 */if (socket < 0){/* ソケット例外送出 */THROW_SOCKET_EXCEPTION("UDP Unicast Tx Socket Open Failed", errno);}/* 〜省略〜 */}
あとがき
長くなったので今回はここまでにして、次回はスタックトレースを取得する方法を実装したいと思います。
C++で文字列フォーマッティング(書式付き文字列生成)
まえがき
C++でprintf
ライクな文字列フォーマッティングの実現方法です。
C++で文字列フォーマットをする方法はいくつかあります。
標準では、std::cout
などのiostream
を使った方法ですが、個人的にはちょっと使いづらいです。
C++20では、format
ライブラリが標準化されますが、まだ実装されているコンパイラはありません。
他にも、boostライブラリにフォーマットライブラリがありますが、インストールなどの手間があります。
色々と探した結果、std::snprintf
と可変引数テンプレートを使った方法が、分かりやすく簡単だったので紹介します。
前提条件
今回実装したソースは以下の環境でビルド、動作確認しています。
OS | Ver | Compiler | Remarks |
---|---|---|---|
Windows | 10 | Visual Studio 2019 | |
macOS | 10.15.6 | Clang 11.0 | |
Ubuntu | 18.04 | GCC 7.5.0 |
実装
まずは、メインの文字列フォーマットを行う関数です。
/* 文字列のフォーマッティング(内部処理) */template<typename ... Args>std::string StringFormatInternal(const std::string& format, Args&& ... args){/* フォーマット後の文字数を算出 */int str_len = std::snprintf(nullptr, 0, format.c_str(), std::forward<Args>(args) ...);/* フォーマット失敗 */if (str_len < 0){throw std::runtime_error("String Formatting Error");}else{/* Nothing to do */}/* バッファサイズを算出(文字列長 + null文字サイズ) */size_t buffer_size = str_len + sizeof(char);/* バッファサイズ分メモリ確保 */std::unique_ptr<char[]> buffer(new char[buffer_size]);/* 文字列のフォーマット */std::snprintf(buffer.get(), buffer_size, format.c_str(), args ...);/* 文字列をstd::string型に変換して出力 */return std::string(buffer.get(), buffer.get() + str_len);}
最初に、std::snprintf
に空のバッファを渡すことで、フォーマット後の文字列サイズを算出しています。
文字列フォーマットの引数は、可変引数テンプレートを使って入力しています。
その後、取得した文字サイズから、std::unique_ptr
でバッファを確保し、そのバッファを指定してstd::snprintf
を呼び出すことで、フォーマットされた文字列(const char*
)を取得できます。
後は、C++の文字列型(std::string)に変換することで、C++で使いやすい文字列フォーマットができます。
しかし、このままだと少し問題があります。
std::snprintf
は、C言語の関数snprintf
のラッパーなので、C++の文字列型(std::string
)はそのままでは入れることが出来ません。
そのため、以下のように、C言語の文字列(const char*
)に変換して入力する必要があります。
/* C++の文字列 */std::string text = "Test Message";/* C++の文字列をCの文字列に変換して入力 */std::string format_text = StringFormat("%s %d", text.c_str(), 100);
そこで、std::string
を直接引数として入力できるように、可変引数テンプレートArgs&& ... args
の各要素に変換関数(Convert
関数)をかまして、std::string
型の場合は、const char*
に変換して入力するようにします。
Convert
関数は以下のように実装します。
/* C++ 11/14版 *//* std::string型をconst char*に変換 */template<typename T, typename std::enable_if<std::is_same<std::remove_cv_t<std::remove_reference_t<T>>, std::string>::value>::type* = nullptr>auto Convert(T&& value){return std::forward<T>(value).c_str();}/* std::string型以外は、そのまま出力 */template<typename T, typename std::enable_if<!std::is_same<std::remove_cv_t<std::remove_reference_t<T>>, std::string>::value>::type* = nullptr>auto Convert(T&& value){return std::forward<T>(value);}
上記Convert
関数では、引数の型(T
)がstd::string
型だったら、c_str()
関数を呼び出し、const char*
に変換して返します。
そして、std::string
型以外の場合には、そのままの型で値を返します。
なお、Convert
関数は、C++17に対応しているコンパイラであれば、以下のようにif constexpr
式を使うことで、1つの関数で実現することも出来ます。
/* C++ 17版 *//* std::string型をconst char*に変換し、それ以外はそのまま出力 */template<typename T>auto Convert(T&& value){/* std::string型をconst char*に変換 */if constexpr (std::is_same<std::remove_cv_t<std::remove_reference_t<T>>, std::string>::value){return std::forward<T>(value).c_str();}/* std::string型以外は、そのまま出力 */else{return std::forward<T>(value);}}
後は、Args&& ... args
の各要素にConvert
関数をかまして、StringFormatInternal
関数を呼び出すことで、std::string
型もそのまま引数として入力できるようになります。
/* 文字列のフォーマッティング */template<typename ... Args>std::string StringFormat(const std::string& format, Args&& ... args){/* 各パラメータの型を変換して、文字列のフォーマッティング */return detail::StringFormatInternal(format, detail::Convert(std::forward<Args>(args)) ...);}
以下に、コード全文を示します。
【StringFormat.h】
#pragma once#include <string>#include <cstdio>#include <stdexcept>#include <vector>#include <iostream>#include <memory>namespace detail{#if 0/* C++ 17版 *//* std::string型をconst char*に変換し、それ以外はそのまま出力 */template<typename T>auto Convert(T&& value){/* std::string型をconst char*に変換 */if constexpr (std::is_same<std::remove_cv_t<std::remove_reference_t<T>>, std::string>::value){return std::forward<T>(value).c_str();}/* std::string型以外は、そのまま出力 */else{return std::forward<T>(value);}}#else/* C++ 11/14版 *//* std::string型をconst char*に変換 */template<typename T, typename std::enable_if<std::is_same<std::remove_cv_t<std::remove_reference_t<T>>, std::string>::value>::type* = nullptr>auto Convert(T&& value){return std::forward<T>(value).c_str();}/* std::string型以外は、そのまま出力 */template<typename T, typename std::enable_if<!std::is_same<std::remove_cv_t<std::remove_reference_t<T>>, std::string>::value>::type* = nullptr>auto Convert(T&& value){return std::forward<T>(value);}#endif/* 文字列のフォーマッティング(内部処理) */template<typename ... Args>std::string StringFormatInternal(const std::string& format, Args&& ... args){/* フォーマット後の文字数を算出 */int str_len = std::snprintf(nullptr, 0, format.c_str(), std::forward<Args>(args) ...);/* フォーマット失敗 */if (str_len < 0){std::runtime_error("String Formatting Error");}else{/* Nothing to do */}/* バッファサイズを算出(文字列長 + null文字サイズ) */size_t buffer_size = str_len + sizeof(char);/* バッファサイズ分メモリ確保 */std::unique_ptr<char[]> buffer(new char[buffer_size]);/* 文字列のフォーマット */std::snprintf(buffer.get(), buffer_size, format.c_str(), args ...);/* 文字列をstd::string型に変換して出力 */return std::string(buffer.get(), buffer.get() + str_len);}}/* 文字列のフォーマッティング */template<typename ... Args>std::string StringFormat(const std::string& format, Args&& ... args){/* 各パラメータの型を変換して、文字列のフォーマッティング */return detail::StringFormatInternal(format, detail::Convert(std::forward<Args>(args)) ...);}
また、StringFormat
関数を使ったサンプルコードは以下のようになります。
static void StringFormatTest(){/* C++形式文字列 */std::string text1 = "Test Message1";/* C形式文字列 */const char* text2 = "Test Message 2";/* 数値 */int value = 256;/* 文字列をフォーマットしてコンソールに出力 */std::cout << StringFormat("%s | %s | 0x%x = %d", text1, text2, value, value) << std::endl;}
実行結果は以下のようになります。
Test Message1 | Test Message2 | 0x100 = 256
C++でソケット通信 (UDPマルチキャスト/ブロードキャスト編)
まえがき
ソケットを使ったEthernet通信の実装方法メモです。
今回は、前回に引き続きC++でUDPマルチキャスト/ブロードキャスト通信を行うための機能を実装します。
ソケット通信は、OSによって実装方法が異なるので、Windows / Mac / Linuxそれぞれの実装を行い、環境によって自動的に切り替わるようにしています。
前提条件
今回実装したソースは以下の環境でビルド、動作確認しています。
OS | Ver | Compiler | Remarks |
---|---|---|---|
Windows | 10 | Visual Studio 2019 | |
macOS | 10.15.6 | Clang 11.0 | |
Ubuntu | 18.04 | GCC 7.5.0 |
また、プロジェクトソース一式は GitHub に置いてあります。
インタフェース
前回作成したSocketAdapter
クラスに、マルチキャスト / ブロードキャスト通信用インタフェースを追加します。
コンセプト
SocketAdapter
クラスに追加するインタフェース一覧を以下の表に示します。
Interface | Type | Detail | Remarks |
---|---|---|---|
OpenUdpMultiTxSocket | Instance | UDマルチキャスト送信用ソケットオープン | |
OpenUdpMultiRxSocket | Instance | UDPマルチキャスト受信用ソケットオープン | |
OpenUdpBroadTxSocket | Instance | UDPブロードキャスト送信用ソケットオープン | |
OpenUdpBroadRxSocket | Instance | UDPブロードキャスト受信用ソケットオープン |
ソケット通信では、ソケットオープン時のオプションと送信先IPアドレスによって、ユニキャスト / マルチキャスト / ブロードキャストを切り替えることが出来ます。
そのため、マルチキャスト、ブロードキャストそれぞれ用のソケットオープン用メソッドを追加します。
OpenUdpMultiTxSocket / OpenUdpMultiRxSocketメソッドはそれぞれマルチキャスト送受信用のソケットオープン用メソッドです。
OpenUdpBroadTxSocket / OpenUdpBroadRxSocketメソッドはそれぞれブロードキャスト送受信用のソケットオープン用メソッドです。
実装
ソケットアダプタ
SocketAdapter
クラスに、マルチキャスト / ブロードキャスト通信用インタフェースを追加します。
以下に、前回からの差分コードを示します。
【SocketAdapter.h】
/* Socket Adapterクラス宣言 */class SocketAdapter final{public:/* 〜省略〜 *//* UDPマルチキャスト送信用ソケットオープン */void OpenUdpMultiTxSocket(const std::string& multicast_ip, const std::string& local_ip, const uint16_t multicast_port, const int32_t ttl);/* UDPマルチキャスト受信用ソケットオープン */void OpenUdpMultiRxSocket(const std::string& multicast_ip, const uint16_t multicast_port);/* UDPブロードキャスト送信用ソケットオープン */void OpenUdpBroadTxSocket(const std::string& remote_ip, const uint16_t remote_port);/* UDPブロードキャスト受信用ソケットオープン */void OpenUdpBroadRxSocket(const uint16_t local_port);/* 〜省略〜 */};
【SocketAdapter.cpp】
/* 〜省略〜 *//* UDPマルチキャスト送信用ソケットオープン */void SocketAdapter::OpenUdpMultiTxSocket(const std::string& multicast_ip, const std::string& local_ip, const uint16_t multicast_port, const int32_t ttl){this->m_Impl->OpenUdpMultiTxSocket(multicast_ip, local_ip, multicast_port, ttl);}/* UDPマルチキャスト受信用ソケットオープン */void SocketAdapter::OpenUdpMultiRxSocket(const std::string& multicast_ip, const uint16_t multicast_port){this->m_Impl->OpenUdpMultiRxSocket(multicast_ip, multicast_port);}/* UDPブロードキャスト送信用ソケットオープン */void SocketAdapter::OpenUdpBroadTxSocket(const std::string& remote_ip, const uint16_t remote_port){this->m_Impl->OpenUdpBroadTxSocket(remote_ip, remote_port);}/* UDPブロードキャスト受信用ソケットオープン */void SocketAdapter::OpenUdpBroadRxSocket(const uint16_t local_port){this->m_Impl->OpenUdpBroadRxSocket(local_port);}/* 〜省略〜 */
Windows版(WinSock2)
Windowsにおけるマルチキャスト / ブロードキャスト通信用インタフェースの実装を行います。 以下に、前回からの差分コードを示します。
【SocketAdapterImpl_WinSock.h】
/* Socket Adapter Implクラス定義 */class SocketAdapterImpl final{public:/* 〜省略〜 *//* コンストラクタ */SocketAdapterImpl(): m_Socket(0), m_Address(), m_LocalIpAddress(0) // ★追加, m_TTL(0) // ★追加, m_MulticastRequest() // ★追加, m_IsSocketOpened(false){/* Nothing to do */}/* 〜省略〜 *//* UDPマルチキャスト送信用ソケットオープン */void OpenUdpMultiTxSocket(const std::string& multicast_ip, const std::string& local_ip, const uint16_t multicast_port, const int32_t ttl){/* UDP用ソケットをオープン */this->m_Socket = socket(AF_INET, SOCK_DGRAM, 0);/* ソケットオープン失敗時のエラー処理 */if (this->m_Socket == INVALID_SOCKET){/* エラーコードセット */SocketAdapterImpl::s_ErrorCode = WSAGetLastError();/* ソケット例外送出 */throw SocketException(SocketAdapterImpl::GetErrorMessage("UDP Multicast Tx Socket Open Failed", SocketAdapterImpl::s_ErrorCode), SocketAdapterImpl::s_ErrorCode);}/* UDPマルチキャスト送信用アドレス情報セット */this->m_Address.sin_family = AF_INET;this->m_Address.sin_port = htons(multicast_port);this->m_Address.sin_addr.S_un.S_addr = this->ConvertIpStrToNum(this->m_Address.sin_family, multicast_ip);/* ローカルIPアドレスセット */this->m_LocalIpAddress = this->ConvertIpStrToNum(this->m_Address.sin_family, local_ip);/* UDPマルチキャストソケットオプションセット */int set_opt_result = setsockopt(this->m_Socket, IPPROTO_IP, IP_MULTICAST_IF, (char*)&this->m_LocalIpAddress, sizeof(this->m_LocalIpAddress));/* ソケットオプションセット失敗時のエラー処理 */if (set_opt_result != 0){/* エラーコードセット */SocketAdapterImpl::s_ErrorCode = errno;/* ソケット例外送出 */throw SocketException(SocketAdapterImpl::GetErrorMessage("UDP Multicast Tx Socket Option Set Failed", SocketAdapterImpl::s_ErrorCode), SocketAdapterImpl::s_ErrorCode);}/* UDPマルチキャストTTLセット */this->m_TTL = (int)ttl;/* UDPマルチキャストTTLソケットオプションセット */int set_ttl_result = setsockopt(this->m_Socket, IPPROTO_IP, IP_MULTICAST_TTL, (char*)&this->m_TTL, sizeof(this->m_TTL));/* ソケットオプションセット失敗時のエラー処理 */if (set_ttl_result != 0){/* エラーコードセット */SocketAdapterImpl::s_ErrorCode = errno;/* ソケット例外送出 */throw SocketException(SocketAdapterImpl::GetErrorMessage("UDP Multicast Tx TTL Socket Option Set Failed", SocketAdapterImpl::s_ErrorCode), SocketAdapterImpl::s_ErrorCode);}/* ソケットオープン状態更新 */this->m_IsSocketOpened = true;}/* UDPマルチキャスト受信用ソケットオープン */void OpenUdpMultiRxSocket(const std::string& multicast_ip, const uint16_t multicast_port){/* UDP用ソケットをオープン */this->m_Socket = socket(AF_INET, SOCK_DGRAM, 0);/* ソケットオープン失敗時のエラー処理 */if (this->m_Socket == INVALID_SOCKET){/* エラーコードセット */SocketAdapterImpl::s_ErrorCode = WSAGetLastError();/* ソケット例外送出 */throw SocketException(SocketAdapterImpl::GetErrorMessage("UDP Multicast Rx Socket Open Failed", SocketAdapterImpl::s_ErrorCode), SocketAdapterImpl::s_ErrorCode);}/* UDPマルチキャスト受信用アドレス情報セット */this->m_Address.sin_family = AF_INET;this->m_Address.sin_port = htons(multicast_port);this->m_Address.sin_addr.S_un.S_addr = INADDR_ANY;/* ソケットにアドレス情報をバインド */int bind_result = bind(this->m_Socket, (struct sockaddr*)&this->m_Address, sizeof(this->m_Address));/* ソケットバインド失敗時のエラー処理 */if (bind_result != 0){/* エラーコードセット */SocketAdapterImpl::s_ErrorCode = WSAGetLastError();/* ソケット例外送出 */throw SocketException(SocketAdapterImpl::GetErrorMessage("UDP Multicast Rx Socket Bind Failed", SocketAdapterImpl::s_ErrorCode), SocketAdapterImpl::s_ErrorCode);}/* マルチキャストリクエストのセット */memset(&this->m_MulticastRequest, 0, sizeof(this->m_MulticastRequest));this->m_MulticastRequest.imr_interface.S_un.S_addr = INADDR_ANY;this->m_MulticastRequest.imr_multiaddr.S_un.S_addr = this->ConvertIpStrToNum(this->m_Address.sin_family, multicast_ip);/* UDPマルチキャストソケットオプションセット */int set_opt_result = setsockopt(this->m_Socket, IPPROTO_IP, IP_ADD_MEMBERSHIP, (char*)&this->m_MulticastRequest, sizeof(this->m_MulticastRequest));/* ソケットオプションセット失敗時のエラー処理 */if (set_opt_result != 0){/* エラーコードセット */SocketAdapterImpl::s_ErrorCode = WSAGetLastError();/* ソケット例外送出 */throw SocketException(SocketAdapterImpl::GetErrorMessage("UDP Multicast Rx Socket Option Set Failed", SocketAdapterImpl::s_ErrorCode), SocketAdapterImpl::s_ErrorCode);}/* ソケットオープン状態更新 */this->m_IsSocketOpened = true;}/* UDPブロードキャスト送信用ソケットオープン */void OpenUdpBroadTxSocket(const std::string& remote_ip, const uint16_t remote_port){/* UDP用ソケットをオープン */this->m_Socket = socket(AF_INET, SOCK_DGRAM, 0);/* ソケットオープン失敗時のエラー処理 */if (this->m_Socket == INVALID_SOCKET){/* エラーコードセット */SocketAdapterImpl::s_ErrorCode = WSAGetLastError();/* ソケット例外送出 */throw SocketException(SocketAdapterImpl::GetErrorMessage("UDP Broadcast Tx Socket Open Failed", SocketAdapterImpl::s_ErrorCode), SocketAdapterImpl::s_ErrorCode);}/* UDPブロードキャスト送信用アドレス情報セット */this->m_Address.sin_family = AF_INET;this->m_Address.sin_port = htons(remote_port);this->m_Address.sin_addr.S_un.S_addr = this->ConvertIpStrToNum(this->m_Address.sin_family, remote_ip);/* UDPブロードキャスト送信用ソケットオプションセット */BOOL yes = 1;int set_opt_result = setsockopt(this->m_Socket, SOL_SOCKET, SO_BROADCAST, (char*)&yes, sizeof(yes));/* ソケットオプションセット失敗時のエラー処理 */if (set_opt_result != 0){/* エラーコードセット */SocketAdapterImpl::s_ErrorCode = WSAGetLastError();/* ソケット例外送出 */throw SocketException(SocketAdapterImpl::GetErrorMessage("UDP Broadcast Tx Socket Option Set Failed", SocketAdapterImpl::s_ErrorCode), SocketAdapterImpl::s_ErrorCode);}/* ソケットオープン状態更新 */this->m_IsSocketOpened = true;}/* UDPブロードキャスト受信用ソケットオープン */void OpenUdpBroadRxSocket(const uint16_t local_port){/* UDP用ソケットをオープン */this->m_Socket = socket(AF_INET, SOCK_DGRAM, 0);/* ソケットオープン失敗時のエラー処理 */if (this->m_Socket == INVALID_SOCKET){/* エラーコードセット */SocketAdapterImpl::s_ErrorCode = WSAGetLastError();/* ソケット例外送出 */throw SocketException(SocketAdapterImpl::GetErrorMessage("UDP Broadcast Rx Socket Open Failed", SocketAdapterImpl::s_ErrorCode), SocketAdapterImpl::s_ErrorCode);}/* UDPブロードキャスト受信用アドレス情報セット */this->m_Address.sin_family = AF_INET;this->m_Address.sin_port = htons(local_port);this->m_Address.sin_addr.S_un.S_addr = INADDR_ANY;/* ソケットにアドレス情報をバインド */int bind_result = bind(this->m_Socket, (struct sockaddr*)&this->m_Address, sizeof(this->m_Address));/* ソケットバインド失敗時のエラー処理 */if (bind_result != 0){/* エラーコードセット */SocketAdapterImpl::s_ErrorCode = WSAGetLastError();/* ソケット例外送出 */throw SocketException(SocketAdapterImpl::GetErrorMessage("UDP Broadcast Rx Socket Bind Failed", SocketAdapterImpl::s_ErrorCode), SocketAdapterImpl::s_ErrorCode);}/* ソケットオープン状態更新 */this->m_IsSocketOpened = true;}/* 〜省略〜 */private:/* ソケット */SOCKET m_Socket;/* ソケットアドレス情報 */struct sockaddr_in m_Address;/* ローカルIPアドレス */ULONG m_LocalIpAddress; // ★追加/* マルチキャストTTL */int m_TTL; // ★追加/* マルチキャストリクエスト */ip_mreq m_MulticastRequest; // ★追加/* ソケットオープン状態 */bool m_IsSocketOpened;};
Mac / Linux版(Socket)
MacおよびLinuxにおけるマルチキャスト / ブロードキャスト通信用インタフェースの実装を行います。 以下に、前回からの差分コードを示します。
【SocketAdapterImpl_Socket.h】
/* Socket Adapter Implクラス定義 */class SocketAdapterImpl final{public:/* 〜省略〜 *//* コンストラクタ */SocketAdapterImpl(): m_Socket(0), m_Address(), m_LocalIpAddress(0) // ★追加, m_TTL(0) // ★追加, m_MulticastRequest() // ★追加, m_IsSocketOpened(false){/* Nothing to do */}/* 〜省略〜 *//* UDPマルチキャスト送信用ソケットオープン */void OpenUdpMultiTxSocket(const std::string& multicast_ip, const std::string& local_ip, const uint16_t multicast_port, const int32_t ttl){/* UDP用ソケットをオープン */this->m_Socket = socket(AF_INET, SOCK_DGRAM, 0);/* ソケットオープン失敗時のエラー処理 */if (this->m_Socket < 0){/* エラーコードセット */SocketAdapterImpl::s_ErrorCode = errno;/* ソケット例外送出 */throw SocketException(SocketAdapterImpl::GetErrorMessage("UDP Multicast Tx Socket Open Failed", SocketAdapterImpl::s_ErrorCode), SocketAdapterImpl::s_ErrorCode);}/* UDPマルチキャスト送信用アドレス情報セット */this->m_Address.sin_family = AF_INET;this->m_Address.sin_port = htons(multicast_port);this->m_Address.sin_addr.s_addr = inet_addr(multicast_ip.c_str());#if COM_TYPE == COM_MACSOCKthis->m_Address.sin_len = sizeof(this->m_Address);#endif/* ローカルIPアドレスセット */this->m_LocalIpAddress = inet_addr(local_ip.c_str());/* UDPマルチキャスト送信用ソケットオプションセット */int set_opt_result = setsockopt(this->m_Socket, IPPROTO_IP, IP_MULTICAST_IF, (char*)&this->m_LocalIpAddress, sizeof(this->m_LocalIpAddress));/* ソケットオプションセット失敗時のエラー処理 */if (set_opt_result != 0){/* エラーコードセット */SocketAdapterImpl::s_ErrorCode = errno;/* ソケット例外送出 */throw SocketException(SocketAdapterImpl::GetErrorMessage("UDP Multicast Tx Socket Option Set Failed", SocketAdapterImpl::s_ErrorCode), SocketAdapterImpl::s_ErrorCode);}/* UDPマルチキャストTTLセット */this->m_TTL = (int)ttl;/* UDPマルチキャストTTLソケットオプションセット */int set_ttl_result = setsockopt(this->m_Socket, IPPROTO_IP, IP_MULTICAST_TTL, (char*)&this->m_TTL, sizeof(this->m_TTL));/* ソケットオプションセット失敗時のエラー処理 */if (set_ttl_result != 0){/* エラーコードセット */SocketAdapterImpl::s_ErrorCode = errno;/* ソケット例外送出 */throw SocketException(SocketAdapterImpl::GetErrorMessage("UDP Multicast Tx TTL Socket Option Set Failed", SocketAdapterImpl::s_ErrorCode), SocketAdapterImpl::s_ErrorCode);}/* ソケットオープン状態更新 */this->m_IsSocketOpened = true;}/* UDPマルチキャスト受信用ソケットオープン */void OpenUdpMultiRxSocket(const std::string& multicast_ip, const uint16_t multicast_port){/* UDP用ソケットをオープン */this->m_Socket = socket(AF_INET, SOCK_DGRAM, 0);/* ソケットオープン失敗時のエラー処理 */if (this->m_Socket < 0){/* エラーコードセット */SocketAdapterImpl::s_ErrorCode = errno;/* ソケット例外送出 */throw SocketException(SocketAdapterImpl::GetErrorMessage("UDP Multicast Rx Socket Open Failed", SocketAdapterImpl::s_ErrorCode), SocketAdapterImpl::s_ErrorCode);}/* UDPマルチキャスト受信用アドレス情報セット */this->m_Address.sin_family = AF_INET;this->m_Address.sin_port = htons(multicast_port);this->m_Address.sin_addr.s_addr = INADDR_ANY;#if COM_TYPE == COM_MACSOCKthis->m_Address.sin_len = sizeof(this->m_Address);#endif/* ソケットにアドレス情報をバインド */int bind_result = bind(this->m_Socket, (struct sockaddr*)&this->m_Address, sizeof(this->m_Address));/* ソケットバインド失敗時のエラー処理 */if (bind_result != 0){/* エラーコードセット */SocketAdapterImpl::s_ErrorCode = errno;/* ソケット例外送出 */throw SocketException(SocketAdapterImpl::GetErrorMessage("UDP Multicast Rx Socket Bind Failed", SocketAdapterImpl::s_ErrorCode), SocketAdapterImpl::s_ErrorCode);}/* UDPマルチキャスト受信リクエストのセット */memset(&this->m_MulticastRequest, 0, sizeof(this->m_MulticastRequest));this->m_MulticastRequest.imr_interface.s_addr = INADDR_ANY;this->m_MulticastRequest.imr_multiaddr.s_addr = inet_addr(multicast_ip.c_str());/* UDPマルチキャスト受信ソケットオプションセット */int set_opt_result = setsockopt(this->m_Socket, IPPROTO_IP, IP_ADD_MEMBERSHIP, (char*)&this->m_MulticastRequest, sizeof(this->m_MulticastRequest));/* ソケットオプションセット失敗時のエラー処理 */if (set_opt_result != 0){/* エラーコードセット */SocketAdapterImpl::s_ErrorCode = errno;/* ソケット例外送出 */throw SocketException(SocketAdapterImpl::GetErrorMessage("UDP Multicast Rx Socket Option Set Failed", SocketAdapterImpl::s_ErrorCode), SocketAdapterImpl::s_ErrorCode);}/* ソケットオープン状態更新 */this->m_IsSocketOpened = true;}/* UDPブロードキャスト送信用ソケットオープン */void OpenUdpBroadTxSocket(const std::string& remote_ip, const uint16_t remote_port){/* UDP用ソケットをオープン */this->m_Socket = socket(AF_INET, SOCK_DGRAM, 0);/* ソケットオープン失敗時のエラー処理 */if (this->m_Socket < 0){/* エラーコードセット */SocketAdapterImpl::s_ErrorCode = errno;/* ソケット例外送出 */throw SocketException(SocketAdapterImpl::GetErrorMessage("UDP Broadcast Tx Socket Open Failed", SocketAdapterImpl::s_ErrorCode), SocketAdapterImpl::s_ErrorCode);}/* UDPブロードキャスト送信用アドレス情報セット */this->m_Address.sin_family = AF_INET;this->m_Address.sin_port = htons(remote_port);this->m_Address.sin_addr.s_addr = inet_addr(remote_ip.c_str());#if COM_TYPE == COM_MACSOCKthis->m_Address.sin_len = sizeof(this->m_Address);#endif/* UDPブロードキャスト送信用ソケットオプションセット */int yes = 1;int set_opt_result = setsockopt(this->m_Socket, SOL_SOCKET, SO_BROADCAST, (char*)&yes, sizeof(yes));/* ソケットオプションセット失敗時のエラー処理 */if (set_opt_result != 0){/* エラーコードセット */SocketAdapterImpl::s_ErrorCode = errno;/* ソケット例外送出 */throw SocketException(SocketAdapterImpl::GetErrorMessage("UDP Broadcast Tx Socket Option Set Failed", SocketAdapterImpl::s_ErrorCode), SocketAdapterImpl::s_ErrorCode);}/* ソケットオープン状態更新 */this->m_IsSocketOpened = true;}/* UDPブロードキャスト受信用ソケットオープン */void OpenUdpBroadRxSocket(const uint16_t local_port){/* UDP用ソケットをオープン */this->m_Socket = socket(AF_INET, SOCK_DGRAM, 0);/* ソケットオープン失敗時のエラー処理 */if (this->m_Socket < 0){/* エラーコードセット */SocketAdapterImpl::s_ErrorCode = errno;/* ソケット例外送出 */throw SocketException(SocketAdapterImpl::GetErrorMessage("UDP Broadcast Rx Socket Open Failed", SocketAdapterImpl::s_ErrorCode), SocketAdapterImpl::s_ErrorCode);}/* UDPブロードキャスト受信用アドレス情報セット */this->m_Address.sin_family = AF_INET;this->m_Address.sin_port = htons(local_port);this->m_Address.sin_addr.s_addr = INADDR_ANY;#if COM_TYPE == COM_MACSOCKthis->m_Address.sin_len = sizeof(this->m_Address);#endif/* ソケットにアドレス情報をバインド */int bind_result = bind(this->m_Socket, (struct sockaddr*)&this->m_Address, sizeof(this->m_Address));/* ソケットバインド失敗時のエラー処理 */if (bind_result != 0){/* エラーコードセット */SocketAdapterImpl::s_ErrorCode = errno;/* ソケット例外送出 */throw SocketException(SocketAdapterImpl::GetErrorMessage("UDP Broadcast Rx Socket Bind Failed", SocketAdapterImpl::s_ErrorCode), SocketAdapterImpl::s_ErrorCode);}/* ソケットオープン状態更新 */this->m_IsSocketOpened = true;}/* 〜省略〜 */private:/* ソケット */int m_Socket;/* ソケットアドレス情報 */struct sockaddr_in m_Address;/* ローカルIPアドレス */in_addr_t m_LocalIpAddress; // ★追加/* マルチキャストTTL */int m_TTL; // ★追加/* マルチキャストリクエスト */ip_mreq m_MulticastRequest; // ★追加/* ソケットオープン状態 */bool m_IsSocketOpened;};
未対応環境
未対応環境にも同様にマルチキャスト / ブロードキャスト通信用インタフェースの実装を行います。 以下に、前回からの差分コードを示します。
【SocketAdapterImpl_Unknown.h】
/* Socket Adapter Implクラス定義 */class SocketAdapterImpl final{public:/* 〜省略〜 *//* UDPマルチキャスト送信用ソケットオープン */void OpenUdpMultiTxSocket(const std::string& multicast_ip, const std::string& local_ip, const uint16_t multicast_port, const int32_t ttl){throw std::runtime_error("Invalid Com Type : " + std::to_string(COM_TYPE));}/* UDPマルチキャスト受信用ソケットオープン */void OpenUdpMultiRxSocket(const std::string& multicast_ip, const uint16_t multicast_port){throw std::runtime_error("Invalid Com Type : " + std::to_string(COM_TYPE));}/* UDPブロードキャスト送信用ソケットオープン */void OpenUdpBroadTxSocket(const std::string& remote_ip, const uint16_t remote_port){throw std::runtime_error("Invalid Com Type : " + std::to_string(COM_TYPE));}/* UDPブロードキャスト受信用ソケットオープン */void OpenUdpBroadRxSocket(const uint16_t local_port){throw std::runtime_error("Invalid Com Type : " + std::to_string(COM_TYPE));}/* 〜省略〜 */};
サンプルコード
機能
SocketAdapter
クラスを使ったUDPマルチキャスト / ブロードキャスト送受信のサンプルコードを実装します。
サンプルコードでは、Sender/Receiverをそれぞれコンソールから起動することで以下のような動作をします。
【Sender】
- UDP送信用ソケットを開く(★)
- キーボードからの送信メッセージ入力を受け付ける
- キーボードから入力された送信メッセージをReceiverに送信する
- キーボードから"exit"が入力されると、ソケットを閉じてプログラムを終了する
【Receiver】
- UDP受信用ソケットを開く(★)
- Senderからのメッセージ受信を待機する
- メッセージを受信したら内容を表示する
- 受信メッセージが"exit"の場合、ソケットを閉じてプログラムを終了する
★ ユニキャスト / マルチキャスト / ブロードキャストの切り替えは、ソケットを開く際のインタフェースと受け渡すパラメータによって行います。
今回のサンプルコードでは、以下の環境を想定しています。
項目 | 値 | 備考 |
---|---|---|
送信側IP | 192.168.1.10 | ★1 |
受信側IP | 192.168.1.11 | ★1 |
マルチキャストIP | 224.0.0.32 | ★2 |
マルチキャストTTL | 1 | ★3 |
ブロードキャストIP | 255.255.255.255 | ★4 |
送受信ポート | 50000 | ★5 |
★1 送信側と受信側はスイッチングハブで接続され、同一セグメントに属しているものとします
★2 マルチキャストで送受信する場合は、決められたアドレスの範囲(224.0.0.0 ~ 239.255.255.255)にパケットを送信する必要があります。
また、マルチキャストアドレスは、送受信する範囲(スコープ)によってアドレス範囲が決まっています。
更に、マルチキャストのアドレス割当は、ICANNにより予約されており、一部のアドレスは静的に割り当てられた「Well-Knownアドレス」となり、それ以外は動的に割り当てが可能なアドレスとなります。
今回は、同一セグメント内の通信なので、リンクローカルで動的割当が可能なアドレスとして、「224.0.0.32」を選択しています。
★3 マルチキャストパケットの配信範囲(スコープ)は、TTL(Time to Live)のしきい値によって制御することが出来ます。
今回は、同一セグメント(リンクローカル)通信なので、TTLは通常「1」として配信されます。
そのため、パラメータとして渡すTTLも明示的に「1」を設定します。
★4 ブロードキャストには、自ネットワークのみにブロードキャストする「リミテッドブロードキャスト」と、ブロードキャスト先ネットワークを指定できる「ディレクティッドブロードキャスト」があります。
今回は、同一セグメント(リンクローカル)通信なので、自ネットワークへのブロードキャストとなる「リミテッドブロードキャストアドレス」となる「255.255.255.255」を選択しています。
★5 TCP/UDPで使われるポート番号はIANAによって管理されており、登録済みのポート番号と、動的・プライベートに使用可能なポート番号があります。
今回は、動的に使用可能な範囲のポート番号である「50000」を設定しています。
実装
Sender / Receiverそれぞれのサンプルコードを以下に示します。
変更点はソケットオープン処理のみになります。
また、ユニキャスト / マルチキャスト / ブロードキャストそれぞれのソケットオープン処理をコンパイルスイッチで切り替えられるようにしています。
【CompileSwitch.h】
#define TEST_MODE_UNICAST (0)#define TEST_MODE_MULTICAST (1)#define TEST_MODE_BROADCAST (2)#define TEST_MODE TEST_MODE_MULTICAST
【Sender.cpp】
int main(){/* 〜省略〜 *//* Socket Adapterインスタンス生成 */SocketAdapter adapter;std::cout << "Socket Adapter Instance Creation Success" << std::endl;#if TEST_MODE == TEST_MODE_UNICAST/* UDPユニキャスト送信用ソケットオープン */adapter.OpenUdpUniTxSocket("192.168.1.11", 50000);std::cout << "UDP Unicast Tx Socket Open Success" << std::endl;#elif TEST_MODE == TEST_MODE_MULTICAST/* UDPマルチキャスト送信用ソケットオープン */adapter.OpenUdpMultiTxSocket("224.0.0.32", "192.168.1.10", 50000, 1);std::cout << "UDP Multicast Tx Socket Open Success" << std::endl;#elif TEST_MODE == TEST_MODE_BROADCAST/* UDPブロードキャスト送信用ソケットオープン */adapter.OpenUdpBroadTxSocket("255.255.255.255", 50000);std::cout << "UDP Broadcast Tx Socket Open Success" << std::endl;#endif/* 〜省略〜 */}
【Receiver.cpp】
int main(){/* 〜省略〜 *//* Socket Adapterインスタンス生成 */SocketAdapter adapter;std::cout << "Socket Adapter Instance Creation Success" << std::endl;#if TEST_MODE == TEST_MODE_UNICAST/* UDPユニキャスト受信用ソケットオープン */adapter.OpenUdpUniRxSocket(50000);std::cout << "UDP Unicast Rx Socket Open Success" << std::endl;#elif TEST_MODE == TEST_MODE_MULTICAST/* UDPマルチキャスト受信用ソケットオープン */adapter.OpenUdpMultiRxSocket("224.0.0.32", 50000);std::cout << "UDP Multicast Rx Socket Open Success" << std::endl;#elif TEST_MODE == TEST_MODE_BROADCAST/* UDPブロードキャスト受信用ソケットオープン */adapter.OpenUdpBroadRxSocket(50000);std::cout << "UDP Broadcast Rx Socket Open Success" << std::endl;#endif/* 〜省略〜 */}
あとがき
今回は、C++でUDPマルチキャスト / ブロードキャスト通信を行う処理を実装しました。
次回は、大きなサイズのデータを送受信するためのパケット分割を実装したいと思います。
C++でソケット通信 (UDPユニキャスト編)
まえがき
ソケットを使ったEthernet通信の実装方法メモです。
今回は、C++でUDPユニキャスト通信を行うための機能を実装します。
ソケット通信は、OSによって実装方法が異なるので、Windows / Mac / Linuxそれぞれの実装を行い、環境によって自動的に切り替わるようにしています。
前提条件
今回実装したソースは以下の環境でビルド、動作確認しています。
OS | Ver | Compiler | Remarks |
---|---|---|---|
Windows | 10 | Visual Studio 2019 | |
macOS | 10.15.6 | Clang 11.0 | |
Ubuntu | 18.04 | GCC 7.5.0 |
また、プロジェクトソース一式は GitHub に置いてあります。
インタフェース
まずは、ソケット通信を行うためのインタフェースを設計します。
コンセプト
まえがきにも書きましたが、ソケット通信は、OSなどの環境によって実装方法が異なるので、Windows / Mac / Linuxそれぞれの実装を行い、環境によって自動的に切り替わるようにします。
ソケット通信機能を使用するユーザが環境の違いを意識しなくても良いように、共通のインタフェースを定義したアダプタクラス(SocketAdapter
)を作成します。
SocketAdapter
クラスでは、以下のインタフェースを提供します。
Interface | Type | Detail | Remarks |
---|---|---|---|
Initialize | Static | ソケット全体の初期化 | |
Finalize | Static | ソケット全体の後始末 | |
GetErrorCode | Static | ソケット関連のエラーコード取得 | |
OpenUdpUniTxSocket | Instance | UDPユニキャスト送信用ソケットオープン | |
OpenUdpUniRxSocket | Instance | UDPユニキャスト受信用ソケットオープン | |
CloseSocket | Instance | ソケットクローズ | |
IsSocketOpened | Instance | ソケットオープン確認 | |
Transmit | Instance | UDPパケット送信 | |
ReceiveSync | Instance | UDPパケット同期受信 |
Initialize / Finalizeメソッドは、ソケット関連処理で全体としての初期化/終了処理です。
これらのメソッドは、プログラムの最初と最後に1度だけ呼び出されることを想定しています。
GetErrorCodeメソッドはソケット関連で何らかのエラー発生時に、エラーコードを取得するためのメソッドです。
SocketAdapter
の各メソッド呼び出しに失敗した場合に、このメソッドを呼び出すとエラーコードを取得できます。
後々、全OS共通のエラーコードを定義したいですが、今回は環境固有のエラーコードを返すようにします。
OpenUdpUniTxSocket / OpenUdpUniRxSocket / CloseSocketメソッドは、UDPユニキャスト用のソケットをオープン/クローズするためのメソッドです。
送信と受信で必要な情報(引数)が異なるため、インタフェースを分けています。
また、IsSocketOpenedメソッドで、ソケットがオープンしているかを確認することが出来ます。
Transmit / ReceiveSyncメソッドは、UDPパケットを送受信するメソッドです。
ReceiveSyncメソッドは、UDPパケットを同期的に受信します。
ReceiveSyncメソッドを呼び出すと、ソケットがUDPパケットを受信するまでブロックし呼び出し元に返らなくなります。
そしてUDPパケットを受信すると、受信データをバッファに書き込み、呼び出し元に制御が返ります。
実装
共通定義
まずは、共通で使用するデータ型や例外などを定義します。
【SocketDataTypes.h】
#pragma once#include <cstdint>#include <string>/* Any Data Pointer */using any_ptr = void*;/* Byte Data */using byte = uint8_t;/* Byte Data Pointer */using byte_ptr = uint8_t*;
【SocketException.h】
#pragma once#include <exception>#include <stdexcept>#include <string>class SocketException : public std::runtime_error{public:SocketException(const char* message, int error_code): runtime_error(message), m_ErrorCode(error_code){/* Nothing to do */}SocketException(const std::string& message, int error_code): runtime_error(message), m_ErrorCode(error_code){/* Nothing to do */}int GetErrorCode(){return this->m_ErrorCode;}private:int m_ErrorCode;};
コンパイルスイッチ
OSやコンパイラによってソケット通信の実装を切り替えられるように、コンパイルスイッチCOM_TYPE
を定義します。
【CompileSwitch.h】
#pragma once#define COM_SOCKET (0)#define COM_WINSOCK (1)#define COM_MACSOCK (2)#define COM_UNKNONW (-1)#if defined(_MSC_VER)#define COM_TYPE COM_WINSOCK /* Visual Studio : WinSock */#elif defined(__MACH__)#define COM_TYPE COM_MACSOCK /* Mac : Mac Socket */#elif defined(__linux__)#define COM_TYPE COM_SOCKET /* Linux : Socket */#else#define COM_TYPE COM_UNKNONW /* Other : Not Support */#endif
ソケットアダプタ
SocketAdapter
クラスは、OSによって実装を切り替えるためにImplパターンで実装します。
【SocketAdapter.h】
#pragma once#include "SocketDataTypes.h"#include <memory>/* Socket Adapter Implクラス前方宣言 */class SocketAdapterImpl;/* Socket Adapterクラス宣言 */class SocketAdapter final{public:/* Socket全体の初期化 */static void Initialize();/* Socket全体の後始末 */static void Finalize();/* エラーコード取得 */static int GetErrorCode();/* コンストラクタ */SocketAdapter();/* デストラクタ */~SocketAdapter();/* UDPユニキャスト送信用ソケットオープン */void OpenUdpUniTxSocket(const std::string& remote_ip, const uint16_t remote_port);/* UDPユニキャスト受信用ソケットオープン */void OpenUdpUniRxSocket(const uint16_t local_port);/* ソケットクローズ */void CloseSocket();/* ソケットオープン確認 */bool IsSocketOpened();/* パケット送信 */void Transmit(const any_ptr data_ptr, size_t tx_size);/* パケット同期受信 */void ReceiveSync(byte_ptr& buffer_ptr, const size_t buffer_size, size_t& rx_size);private:/* Socket Adapter Implクラスインスタンス */std::unique_ptr<SocketAdapterImpl> m_Impl;};
SocketAdapterImpl
クラスは、コンパイルスイッチCOM_TYPE
によって自動的に切り替わるようにし、SocketAdapter
クラスの各メソッドはSocketAdapterImpl
クラスのメソッドを呼び出すだけにします。
【SocketAdapter.cpp】
#include "SocketAdapter.h"#include "CompileSwitch.h"#if COM_TYPE == COM_WINSOCK#include "SocketAdapterImpl_WinSock.h"#elif COM_TYPE == COM_SOCKET || COM_TYPE == COM_MACSOCK#include "SocketAdapterImpl_Socket.h"#else#include "SocketAdapterImpl_Unknown.h"#endif/* Socket全体の初期化 */void SocketAdapter::Initialize(){SocketAdapterImpl::Initialize();}/* Socket全体の後始末 */void SocketAdapter::Finalize(){SocketAdapterImpl::Finalize();}/* エラーコード取得 */int SocketAdapter::GetErrorCode(){return SocketAdapterImpl::GetErrorCode();}/* コンストラクタ */SocketAdapter::SocketAdapter(): m_Impl(nullptr){/* Socket Adapter Implクラスインスタンス生成 */this->m_Impl = std::make_unique<SocketAdapterImpl>();}/* デストラクタ */SocketAdapter::~SocketAdapter(){/* Nothing to do */}/* UDPユニキャスト送信用ソケットオープン */void SocketAdapter::OpenUdpUniTxSocket(const std::string& remote_ip, const uint16_t remote_port){this->m_Impl->OpenUdpUniTxSocket(remote_ip, remote_port);}/* UDPユニキャスト受信用ソケットオープン */void SocketAdapter::OpenUdpUniRxSocket(const uint16_t local_port){this->m_Impl->OpenUdpUniRxSocket(local_port);}/* ソケットクローズ */void SocketAdapter::CloseSocket(){this->m_Impl->CloseSocket();}/* ソケットオープン確認 */bool SocketAdapter::IsSocketOpened(){return this->m_Impl->IsSocketOpened();}/* パケット送信 */void SocketAdapter::Transmit(const any_ptr data_ptr, size_t tx_size){this->m_Impl->Transmit(data_ptr, tx_size);}/* パケット同期受信 */void SocketAdapter::ReceiveSync(byte_ptr& buffer_ptr, const size_t buffer_size, size_t& rx_size){this->m_Impl->ReceiveSync(buffer_ptr, buffer_size, rx_size);}
Windows版(WinSock2)
Windowsでは、WinSock2 APIを使ってソケット通信を実装します。
【SocketAdapterImpl_WinSock.h】
#pragma once#include "CompileSwitch.h"#if COM_TYPE == COM_WINSOCK#include "SocketDataTypes.h"#include "SocketException.h"#include <WinSock2.h>#include <ws2tcpip.h>#include <sstream>#pragma comment(lib, "ws2_32.lib")/* Socket Adapter Implクラス定義 */class SocketAdapterImpl final{public:/* Socket全体の初期化 */static void Initialize(){/* エラーコードクリア */SocketAdapterImpl::s_ErrorCode = 0;/* WinSockサービススタートアップ */int startup_result = WSAStartup(MAKEWORD(2, 0), &SocketAdapterImpl::s_WsaData);/* WinSockサービスススタートアップ失敗時のエラー処理 */if (startup_result != 0){/* エラーコードセット */SocketAdapterImpl::s_ErrorCode = startup_result;/* ソケット例外送出 */throw SocketException(SocketAdapterImpl::GetErrorMessage("WSA Startup Failed", SocketAdapterImpl::s_ErrorCode), SocketAdapterImpl::s_ErrorCode);}}/* Socket全体の後始末 */static void Finalize(){/* WinSockサービスクリーンアップ */int cleanup_result = WSACleanup();/* WinSockサービスクリーンアップ失敗時のエラー処理 */if (cleanup_result == SOCKET_ERROR){SocketAdapterImpl::s_ErrorCode = WSAGetLastError();/* ソケット例外送出 */throw SocketException(SocketAdapterImpl::GetErrorMessage("WSA Cleanup Failed", SocketAdapterImpl::s_ErrorCode), SocketAdapterImpl::s_ErrorCode);}}/* エラーコード取得 */static int GetErrorCode(){return SocketAdapterImpl::s_ErrorCode;}/* コンストラクタ */SocketAdapterImpl(): m_Socket(0), m_Address(), m_IsSocketOpened(false){/* Nothing to do */}/* デストラクタ */~SocketAdapterImpl(){/* ソケットオープン確認 */if (this->IsSocketOpened() == true){/* ソケットがオープンしていたらクローズする */this->CloseSocket();}}/* UDPユニキャスト送信用ソケットオープン */void OpenUdpUniTxSocket(const std::string& remote_ip, const uint16_t remote_port){/* UDP用ソケットをオープン */this->m_Socket = socket(AF_INET, SOCK_DGRAM, 0);/* ソケットオープン失敗時のエラー処理 */if (this->m_Socket == INVALID_SOCKET){/* エラーコードセット */SocketAdapterImpl::s_ErrorCode = WSAGetLastError();/* ソケット例外送出 */throw SocketException(SocketAdapterImpl::GetErrorMessage("UDP Unicast Tx Socket Open Failed", SocketAdapterImpl::s_ErrorCode), SocketAdapterImpl::s_ErrorCode);}/* UDPユニキャスト送信用アドレス情報セット */this->m_Address.sin_family = AF_INET;this->m_Address.sin_port = htons(remote_port);this->m_Address.sin_addr.S_un.S_addr = this->ConvertIpStrToNum(this->m_Address.sin_family, remote_ip);/* ソケットオープン状態更新 */this->m_IsSocketOpened = true;}/* UDPユニキャスト受信用ソケットオープン */void OpenUdpUniRxSocket(const uint16_t local_port){/* UDP用ソケットをオープン */this->m_Socket = socket(AF_INET, SOCK_DGRAM, 0);/* ソケットオープン失敗時のエラー処理 */if (this->m_Socket == INVALID_SOCKET){/* エラーコードセット */SocketAdapterImpl::s_ErrorCode = WSAGetLastError();/* ソケット例外送出 */throw SocketException(SocketAdapterImpl::GetErrorMessage("UDP Unicast Rx Socket Open Failed", SocketAdapterImpl::s_ErrorCode), SocketAdapterImpl::s_ErrorCode);}/* UDPユニキャスト受信用アドレス情報セット */this->m_Address.sin_family = AF_INET;this->m_Address.sin_port = htons(local_port);this->m_Address.sin_addr.S_un.S_addr = INADDR_ANY;/* ソケットにアドレス情報をバインド */int wsa_result = bind(this->m_Socket, (struct sockaddr*)&this->m_Address, sizeof(this->m_Address));/* ソケットバインド失敗時のエラー処理 */if (wsa_result != 0){/* エラーコードセット */SocketAdapterImpl::s_ErrorCode = WSAGetLastError();/* ソケット例外送出 */throw SocketException(SocketAdapterImpl::GetErrorMessage("UDP Unicast Rx Socket Bind Failed", SocketAdapterImpl::s_ErrorCode), SocketAdapterImpl::s_ErrorCode);}/* ソケットオープン状態更新 */this->m_IsSocketOpened = true;}/* ソケットクローズ */void CloseSocket(){/* ソケットオープン確認 */if (this->IsSocketOpened() == true){/* ソケットがオープンしていたらクローズ */int close_result = closesocket(this->m_Socket);/* ソケットクローズ失敗時のエラー処理 */if (close_result == SOCKET_ERROR){/* エラーコードセット */SocketAdapterImpl::s_ErrorCode = WSAGetLastError();/* ソケット例外送出 */throw SocketException(SocketAdapterImpl::GetErrorMessage("Socket Close Failed", SocketAdapterImpl::s_ErrorCode), SocketAdapterImpl::s_ErrorCode);}/* ソケットクローズ成功時 */else{/* ソケットオープン状態をクリア */this->m_IsSocketOpened = false;}}else{/* Nothing to do */}}/* ソケットオープン確認 */bool IsSocketOpened(){return this->m_IsSocketOpened;}/* パケット送信 */void Transmit(const any_ptr data_ptr, size_t tx_size){/* ソケットにパケット送信 */int send_result = sendto(this->m_Socket, (const char*)data_ptr, (int)tx_size, 0, (struct sockaddr*)&this->m_Address, sizeof(this->m_Address));/* パケット送信失敗時のエラー処理 */if (send_result == SOCKET_ERROR){/* エラーコードセット */SocketAdapterImpl::s_ErrorCode = WSAGetLastError();/* ソケット例外送出 */throw SocketException(SocketAdapterImpl::GetErrorMessage("Packet Transmit Failed", SocketAdapterImpl::s_ErrorCode), SocketAdapterImpl::s_ErrorCode);}}/* パケット同期受信 */void ReceiveSync(byte_ptr& buffer_ptr, const size_t buffer_size, size_t& rx_size){/* ソケットからパケット受信(ブロッキング) */int receive_result = recv(this->m_Socket, (char*)buffer_ptr, (int)buffer_size, 0);/* パケット受信失敗時のエラー処理 */if (receive_result == SOCKET_ERROR){/* エラーコードセット */SocketAdapterImpl::s_ErrorCode = WSAGetLastError();/* ソケット例外送出 */throw SocketException(SocketAdapterImpl::GetErrorMessage("Packet Receive Failed", SocketAdapterImpl::s_ErrorCode), SocketAdapterImpl::s_ErrorCode);}/* パケット受信成功時 */else{/* 受信データサイズをセット */rx_size = (size_t)receive_result;}}private:/* エラーメッセージ生成 */static inline std::string GetErrorMessage(const char* message, int error_code){std::stringstream ss;ss << "[Socket Error] " << message << " : ErrorCode = " << error_code;return ss.str();}/* IPアドレスの変換(文字列 -> 数値) */static ULONG ConvertIpStrToNum(const ADDRESS_FAMILY family, const std::string& ip){/* 変換後IPアドレス */ULONG ip_num = 0;/* IPアドレスを文字列から数値に変換 */int pton_result = inet_pton(family, ip.c_str(), &ip_num);/* IPアドレス変換成功時 */if (pton_result == 1){return ip_num;}/* IPアドレス変換失敗時のエラー処理 */else if (pton_result == 0){/* エラーコードセット */SocketAdapterImpl::s_ErrorCode = -1;/* ソケット例外送出 */throw SocketException(SocketAdapterImpl::GetErrorMessage("Convert IP Address Failed", SocketAdapterImpl::s_ErrorCode), SocketAdapterImpl::s_ErrorCode);}else{/* エラーコードセット */SocketAdapterImpl::s_ErrorCode = WSAGetLastError();/* ソケット例外送出 */throw SocketException(SocketAdapterImpl::GetErrorMessage("Convert IP Address Failed", SocketAdapterImpl::s_ErrorCode), SocketAdapterImpl::s_ErrorCode);}}private:/* WinSockサービス情報 */static WSADATA s_WsaData;/* エラーコード */static int s_ErrorCode;private:/* ソケット */SOCKET m_Socket;/* ソケットアドレス情報 */struct sockaddr_in m_Address;/* ソケットオープン状態 */bool m_IsSocketOpened;};/* WinSockサービス情報 */WSADATA SocketAdapterImpl::s_WsaData;/* エラーコード */int SocketAdapterImpl::s_ErrorCode = 0;#else#endif
Mac / Linux版(Socket)
MacおよびLinuxでは、BSD Socket APIを使ってソケット通信を実装します。
MacとLinuxで一部インタフェースが異なる部分があるため、その部分に関してはコンパイルスイッチCOM_TYPE
で切り替える仕様にします。
【SocketAdapterImpl_Socket.h】
#pragma once#include "CompileSwitch.h"#if COM_TYPE == COM_SOCKET || COM_TYPE == COM_MACSOCK#include "SocketDataTypes.h"#include "SocketException.h"#include <stdio.h>#include <unistd.h>#include <sys/types.h>#include <sys/socket.h>#include <netinet/in.h>#include <arpa/inet.h>#include <errno.h>#include <sstream>/* Socket Adapter Implクラス定義 */class SocketAdapterImpl final{public:/* Socket全体の初期化 */static void Initialize(){/* エラーコードクリア */SocketAdapterImpl::s_ErrorCode = 0;}/* Socket全体の後始末 */static void Finalize(){/* Nothing to do */}/* エラーコード取得 */static int GetErrorCode(){return SocketAdapterImpl::s_ErrorCode;}/* コンストラクタ */SocketAdapterImpl(): m_Socket(0), m_Address(), m_IsSocketOpened(false){/* Nothing to do */}/* デストラクタ */~SocketAdapterImpl(){/* ソケットオープン確認 */if (this->IsSocketOpened() == true){/* ソケットがオープンしていたらクローズする */this->CloseSocket();}}/* UDPユニキャスト送信用ソケットオープン */void OpenUdpUniTxSocket(const std::string& remote_ip, const uint16_t remote_port){/* UDP用ソケットをオープン */this->m_Socket = socket(AF_INET, SOCK_DGRAM, 0);/* ソケットオープン失敗時のエラー処理 */if (this->m_Socket < 0){/* エラーコードセット */SocketAdapterImpl::s_ErrorCode = errno;/* ソケット例外送出 */throw SocketException(SocketAdapterImpl::GetErrorMessage("UDP Unicast Tx Socket Open Failed", SocketAdapterImpl::s_ErrorCode), SocketAdapterImpl::s_ErrorCode);}/* UDPユニキャスト送信用アドレス情報セット */this->m_Address.sin_family = AF_INET;this->m_Address.sin_port = htons(remote_port);this->m_Address.sin_addr.s_addr = inet_addr(remote_ip.c_str());#if COM_TYPE == COM_MACSOCKthis->m_Address.sin_len = sizeof(this->m_Address);#endif/* ソケットオープン状態更新 */this->m_IsSocketOpened = true;}/* UDPユニキャスト受信用ソケットオープン */void OpenUdpUniRxSocket(const uint16_t local_port){/* UDP用ソケットをオープン */this->m_Socket = socket(AF_INET, SOCK_DGRAM, 0);/* ソケットオープン失敗時のエラー処理 */if (this->m_Socket < 0){/* エラーコードセット */SocketAdapterImpl::s_ErrorCode = errno;/* ソケット例外送出 */throw SocketException(SocketAdapterImpl::GetErrorMessage("UDP Unicast Rx Socket Open Failed", SocketAdapterImpl::s_ErrorCode), SocketAdapterImpl::s_ErrorCode);}/* UDPユニキャスト受信用アドレス情報セット */this->m_Address.sin_family = AF_INET;this->m_Address.sin_port = htons(local_port);this->m_Address.sin_addr.s_addr = INADDR_ANY;#if COM_TYPE == COM_MACSOCKthis->m_Address.sin_len = sizeof(this->m_Address);#endif/* ソケットにアドレス情報をバインド */int bind_result = bind(this->m_Socket, (struct sockaddr*)&this->m_Address, sizeof(this->m_Address));/* ソケットバインド失敗時のエラー処理 */if (bind_result != 0){/* エラーコードセット */SocketAdapterImpl::s_ErrorCode = errno;/* ソケット例外送出 */throw SocketException(SocketAdapterImpl::GetErrorMessage("UDP Unicast Rx Socket Bind Failed", SocketAdapterImpl::s_ErrorCode), SocketAdapterImpl::s_ErrorCode);}/* ソケットオープン状態更新 */this->m_IsSocketOpened = true;}/* ソケットクローズ */void CloseSocket(){/* ソケットオープン確認 */if (this->IsSocketOpened() == true){/* ソケットがオープンしていたらクローズ */int close_result = close(this->m_Socket);/* ソケットクローズ成功時 */if (close_result == 0){/* ソケットオープン状態をクリア */this->m_IsSocketOpened = false;}/* ソケットクローズ失敗時のエラー処理 */else{/* エラーコードセット */SocketAdapterImpl::s_ErrorCode = errno;/* ソケット例外送出 */throw SocketException(SocketAdapterImpl::GetErrorMessage("Socket Close Failed", SocketAdapterImpl::s_ErrorCode), SocketAdapterImpl::s_ErrorCode);}}else{/* Nothing to do */}}/* ソケットオープン確認 */bool IsSocketOpened(){return this->m_IsSocketOpened;}/* パケット送信 */void Transmit(const any_ptr data_ptr, size_t tx_size){/* ソケットにパケット送信 */int send_result = sendto(this->m_Socket, (const char*)data_ptr, (int)tx_size, 0, (struct sockaddr*)&this->m_Address, sizeof(this->m_Address));/* パケット送信失敗時のエラー処理 */if (send_result < 0){/* エラーコードセット */SocketAdapterImpl::s_ErrorCode = errno;/* ソケット例外送出 */throw SocketException(SocketAdapterImpl::GetErrorMessage("Packet Transmit Failed", SocketAdapterImpl::s_ErrorCode), SocketAdapterImpl::s_ErrorCode);}}/* パケット同期受信 */void ReceiveSync(byte_ptr& buffer_ptr, const size_t buffer_size, size_t& rx_size){/* ソケットからパケット受信(ブロッキング) */int receive_result = recv(this->m_Socket, (char*)buffer_ptr, (int)buffer_size, 0);/* パケット受信成功時 */if (receive_result >= 0){/* 受信データサイズをセット */rx_size = (size_t)receive_result;}/* パケット受信失敗時のエラー処理 */else{/* エラーコードセット */SocketAdapterImpl::s_ErrorCode = errno;/* ソケット例外送出 */throw SocketException(SocketAdapterImpl::GetErrorMessage("Packet Receive Failed", SocketAdapterImpl::s_ErrorCode), SocketAdapterImpl::s_ErrorCode);}}private:/* エラーメッセージ生成 */static inline std::string GetErrorMessage(const char* message, int error_code){std::stringstream ss;ss << "[Socket Error] " << message << " : ErrorCode = " << error_code;return ss.str();}private:/* エラーコード */static int s_ErrorCode;private:/* ソケット */int m_Socket;/* ソケットアドレス情報 */struct sockaddr_in m_Address;/* ソケットオープン状態 */bool m_IsSocketOpened;};/* エラーコード */int SocketAdapterImpl::s_ErrorCode = 0;#else#endif
未対応環境
今回ビルド&動作確認を行っていない環境の場合には、実行時に例外を投げるようなSocketAdapterImpl
クラスを実装します。
【SocketAdapterImpl_Unknown.h】
#pragma once#include "CompileSwitch.h"#if COM_TYPE != COM_WINSOCK && COM_TYPE != COM_SOCKET && COM_TYPE != COM_MACSOCK#include "SocketDataTypes.h"#include "SocketException.h"/* Socket Adapter Implクラス定義 */class SocketAdapterImpl final{public:/* Socket全体の初期化 */static void Initialize(){throw std::runtime_error("Invalid Com Type : " + std::to_string(COM_TYPE));}/* Socket全体の後始末 */static void Finalize(){throw std::runtime_error("Invalid Com Type : " + std::to_string(COM_TYPE));}/* エラーコード取得 */static int GetErrorCode(){throw std::runtime_error("Invalid Com Type : " + std::to_string(COM_TYPE));}/* コンストラクタ */SocketAdapterImpl(){throw std::runtime_error("Invalid Com Type : " + std::to_string(COM_TYPE));}/* デストラクタ */~SocketAdapterImpl(){}/* UDPユニキャスト送信用ソケットオープン */void OpenUdpUniTxSocket(const std::string& remote_ip, const uint16_t remote_port){throw std::runtime_error("Invalid Com Type : " + std::to_string(COM_TYPE));}/* UDPユニキャスト受信用ソケットオープン */void OpenUdpUniRxSocket(const uint16_t local_port){throw std::runtime_error("Invalid Com Type : " + std::to_string(COM_TYPE));}/* UDPマルチキャスト送信用ソケットオープン */void OpenUdpMultiTxSocket(const std::string& multicast_ip, const std::string& local_ip, const uint16_t multicast_port, const int32_t ttl){throw std::runtime_error("Invalid Com Type : " + std::to_string(COM_TYPE));}/* UDPマルチキャスト受信用ソケットオープン */void OpenUdpMultiRxSocket(const std::string& multicast_ip, const uint16_t multicast_port){throw std::runtime_error("Invalid Com Type : " + std::to_string(COM_TYPE));}/* UDPブロードキャスト送信用ソケットオープン */void OpenUdpBroadTxSocket(const std::string& remote_ip, const uint16_t remote_port){throw std::runtime_error("Invalid Com Type : " + std::to_string(COM_TYPE));}/* UDPブロードキャスト受信用ソケットオープン */void OpenUdpBroadRxSocket(const uint16_t local_port){throw std::runtime_error("Invalid Com Type : " + std::to_string(COM_TYPE));}/* ソケットクローズ */void CloseSocket(){throw std::runtime_error("Invalid Com Type : " + std::to_string(COM_TYPE));}/* ソケットオープン確認 */bool IsSocketOpened(){throw std::runtime_error("Invalid Com Type : " + std::to_string(COM_TYPE));}/* パケット送信 */void Transmit(const any_ptr data_ptr, size_t tx_size){throw std::runtime_error("Invalid Com Type : " + std::to_string(COM_TYPE));}/* パケット同期受信 */void ReceiveSync(byte_ptr& buffer_ptr, const size_t buffer_size, size_t& rx_size){throw std::runtime_error("Invalid Com Type : " + std::to_string(COM_TYPE));}};#else#error Invalid Com Type : COM_TYPE#endif
サンプルコード
機能
SocketAdapter
クラスを使ったUDPユニキャスト送受信のサンプルコードを実装します。
サンプルコードでは、Sender/Receiverをそれぞれコンソールから起動することで以下のような動作をします。
【Sender】
- UDPユニキャスト送信用ソケット(IP:localhost Port:5000)を開く
- キーボードからの送信メッセージ入力を受け付ける
- キーボードから入力された送信メッセージをReceiverに送信する
- キーボードから"exit"が入力されると、ソケットを閉じてプログラムを終了する
【Receiver】
- UDPユニキャスト受信用ソケット(Port:5000)を開く
- Senderからのメッセージ受信を待機する
- メッセージを受信したら内容を表示する
- 受信メッセージが"exit"の場合、ソケットを閉じてプログラムを終了する
実装
Sender / Receiverそれぞれのサンプルコードを以下に示します。
【Sender.cpp】
#include "SocketAdapter.h"#include <iostream>#include <cstring>#include <thread>#include <chrono>/* 受信メッセージのエンコード */static void EncodeTxMessage(const std::string& message, byte_ptr& buffer_ptr, size_t& buffer_size);int main(){try{/* Socket全体の初期化 */SocketAdapter::Initialize();std::cout << "Socket Adapter Initialize Success" << std::endl;/* Socket Adapterインスタンス生成 */SocketAdapter adapter;std::cout << "Socket Adapter Instance Creation Success" << std::endl;/* UDPユニキャスト送信用ソケットオープン */adapter.OpenUdpUniTxSocket("127.0.0.1", 5000);std::cout << "UDP Unicast Tx Socket Open Success" << std::endl;while (true){/* 送信メッセージ */std::string tx_msg = "";/* 送信データバッファ */byte_ptr buffer_ptr = nullptr;/* 送信データバッファサイズ */size_t buffer_size = 0;/* 送信メッセージを標準入力から入力 */std::cout << ">";std::cin >> tx_msg;try{/* 送信メッセージをエンコード(送信データバッファ動的確保) */EncodeTxMessage(tx_msg, buffer_ptr, buffer_size);/* パケット送信 */adapter.Transmit((any_ptr)buffer_ptr, buffer_size);std::cout << "Transmit UDP Unicast Message : [" << tx_msg << "]" << std::endl;/* 送信データバッファ解放 */free(buffer_ptr);/* 送信終了判定 */if (tx_msg == "exit"){break;}}catch(const std::exception& ex){std::cerr << "[ERROR] " << ex.what() << std::endl;/* 送信エラーの場合は、一定時間待機して送信を再開する */std::this_thread::sleep_for(std::chrono::microseconds(100));}}/* ソケットクローズ */adapter.CloseSocket();std::cout << "Socket Close Success" << std::endl;/* Socket全体の後始末 */SocketAdapter::Finalize();std::cout << "Socket Adapter Finalize Success" << std::endl;return 0;}catch(const std::exception& ex){std::cerr << "[ERROR] " << ex.what() << std::endl;/* ソケット初期化/終了/オープン/クローズ失敗時はエラー終了 */return -1;}}/* 受信メッセージのエンコード */static void EncodeTxMessage(const std::string& message, byte_ptr& buffer_ptr, size_t& buffer_size){/* 文字列の長さ(null文字を含まない)を取得 */size_t tx_msg_len = message.length();/* 送信データサイズ(null文字を含む文字数)を算出 */size_t tx_data_size = tx_msg_len + 1;/* 送信データサイズ文のメモリを動的確保 */byte_ptr tx_data_ptr = (byte_ptr)malloc(tx_data_size);/* メモリ確保成功 */if (tx_data_ptr != nullptr){/* 送信メッセージを送信データバッファにコピー */memcpy(tx_data_ptr, message.c_str(), tx_msg_len);/* 送信データバッファの終端にnull文字をセット */tx_data_ptr[tx_msg_len] = '\0';/* 送信バッファ/送信バッファサイズをセット */buffer_ptr = tx_data_ptr;buffer_size = tx_data_size;}else{/* 送信バッファ/送信バッファサイズをクリア */buffer_ptr = nullptr;buffer_size = 0;/* Bad Allocation例外送出 */throw std::bad_alloc();}}
【Receiver.cpp】
#include "SocketAdapter.h"#include <iostream>#include <sstream>#include <thread>#include <chrono>#include <exception>/* 受信メッセージのデコード */static std::string DecodeRxMessage(byte_ptr buffer_ptr, size_t data_size);int main(){try{/* Socket全体の初期化 */SocketAdapter::Initialize();std::cout << "Socket Adapter Initialize Success" << std::endl;/* Socket Adapterインスタンス生成 */SocketAdapter adapter;std::cout << "Socket Adapter Instance Creation Success" << std::endl;/* UDPユニキャスト受信用ソケットオープン */adapter.OpenUdpUniRxSocket(5000);std::cout << "UDP Unicast Rx Socket Open Success" << std::endl;while (true){/* 受信バッファ */uint8_t buffer[1024];byte_ptr buffer_ptr = buffer;/* 受信バッファサイズ */size_t buffer_size = sizeof(buffer);/* 受信データサイズ */size_t rx_size = 0;try{/* パケット同期受信 */adapter.ReceiveSync(buffer_ptr, buffer_size, rx_size);/* 受信メッセージをデコード */std::string rx_msg = DecodeRxMessage(buffer_ptr, rx_size);std::cout << "UDP Unicast Message Received : [" << rx_msg << "]" << std::endl;/* 受信終了判定 */if (rx_msg == "exit"){break;}}catch(const std::exception& ex){std::cerr << "[ERROR] " << ex.what() << std::endl;/* 受信エラーの場合は、一定時間待機して受信を再開する */std::this_thread::sleep_for(std::chrono::microseconds(100));}}/* ソケットクローズ */adapter.CloseSocket();std::cout << "Socket Close Success" << std::endl;/* Socket全体の後始末 */SocketAdapter::Finalize();std::cout << "Socket Adapter Finalize Success" << std::endl;return 0;}catch(const std::exception& ex){std::cerr << "[ERROR] " << ex.what() << std::endl;/* ソケット初期化/終了/オープン/クローズ失敗時はエラー終了 */return -1;}}/* 受信メッセージのデコード */static std::string DecodeRxMessage(byte_ptr buffer_ptr, size_t data_size){/* 文字配列として受け取るのでそのまま文字列に変換 */std::stringstream ss;ss << buffer_ptr;std::string rx_msg = ss.str();return rx_msg;}
あとがき
今回は、C++でUDPユニキャスト通信を行う処理を実装しました。
次回は、ユニキャスト/ブロードキャストに対応させたいと思います。
ClangNet(libclang)で構文解析サンプル - その1 (関数コールツリー、関数クロスリファレンス)
まえがき
今回はClangNet(libclangの.NET(C#) Binding)を使った実用的な構文解析のサンプルを作成してみます。
今回作成するサンプルでは、以下の2つの情報を出力する機能を実装します。
- 関数コールツリー
- 関数クロスリファレンス(宣言/定義/コール)情報
ClangNetのインストールや使用方法については過去記事をご参照ください。
an-embedded-engineer.hateblo.jp
解析対象ソース
まずは、今回解析対象にするC/C++用ソースコードを用意します。
今回は以下の種類の関数を解析対象にします。
解析対象にするソースのサンプルを以下に示します。
解析対象ソースの詳しい説明は割愛します。
[TestClass.h]
#pragma oncenamespace root{namespace sub1{namespace sub2{class TestClass{public:TestClass();~TestClass();void PublicMethod(int a, int b);int GetA() { return this->m_a; }int GetB() { return this->m_b; }int GetResult() { return this->m_result; }void SetA(int a) { this->m_a = a; }void SetB(int b) { this->m_b = b; }void SetResult(int result) { this->m_result = result; }private:int PrivateMethod(int a, int b);private:int m_a;int m_b;int m_result;};}}}
[TestClass.cpp]
#include "TestClass.h"namespace root{namespace sub1{namespace sub2{TestClass::TestClass(): m_a(0), m_b(0), m_result(0){}TestClass::~TestClass(){}void TestClass::PublicMethod(int a, int b){int result = this->PrivateMethod(a, b);this->SetResult(result);}int TestClass::PrivateMethod(int a, int b){this->SetA(a);this->SetB(b);return this->m_a + this->m_b;}}}}
#include <iostream>#include "TestClass.h"void RecursiveCallTest(int i);int main(){root::sub1::sub2::TestClass test_instance;int a = 10;int b = 5;int result = 0;test_instance.SetA(a);test_instance.SetB(b);std::cout << "a : " << test_instance.GetA() << std::endl;std::cout << "b : " << test_instance.GetB() << std::endl;test_instance.PublicMethod(a, b);result = test_instance.GetResult();std::cout << "result : " << result << std::endl;RecursiveCallTest(10);}void RecursiveCallTest(int i){if (i < 0){return;}else{std::cout << "i : " << i << std::endl;RecursiveCallTest(i - 1);}}
構文解析サンプル
上記解析対象ソースに対して、関数コールツリー(Invokation Tree)と関数クロスリファレンス(Cross Reference)情報を生成して出力するサンプルを実装します。
実現方法
実現方法を簡単に説明します。
大きく分けると次の3ステップで実現します。
- Translation Unit(ソースファイル)解析
- Invokation Tree(関数コールツリー)生成
- Cross Reference(関数参照情報)生成
1.のTranslation Unit(ソースファイル)解析では、ClangNetの構文解析機能を使って各ソースファイルのAST(抽象構文木)を辿っていき、以下のような情報を収集します。
種別 | 意味 | 保持する情報 | 備考 |
---|---|---|---|
Translation Unit情報 | 変換対象(ソースファイル)ごとに保持する情報 | ソースファイルパス、宣言・定義されているBehavior情報マップ | |
Behavior情報マップ | Behavior(関数)の宣言・定義位置をキーにした、Behavior情報のハッシュマップ | Key : 関数宣言・定義位置、Value : Behavior情報 | |
Behavior情報 | 種別(宣言/定義)、関数宣言、名前空間、ソース位置、呼び出している関数のInvokation情報リスト | ||
Invokation情報 | 呼び出している関数名、ソース位置、参照している関数宣言のBehavior情報 |
のInvokation Tree(関数コールツリー)生成では、1.で収集した情報をもとに、関数定義内で呼び出している関数を再帰的に辿っていきます。
の Cross Reference(関数参照情報)生成では、1.で収集した情報をもとに、関数ごとに宣言箇所、定義箇所、呼び出し箇所のテーブルを生成します。
1. Translation Unit(ソースファイル)解析
ソースファイルからClangNetを使ってASTを解析し、必要な情報を収集します。
1-1. データ構造定義
まずは収集して構築するデータ構造を定義して行きます。
型名 | ベース型 | 意味 | 保持する情報 | 備考 |
---|---|---|---|---|
AstNodeInfo | - | ASTノードベース型 | ソース位置情報 | |
TranslationUnitInfo | AstNodeInfo | 変換対象ソース情報 | ファイルパス、BehaviorLocationMap | |
BehaviorInfo | AstNodeInfo | 関数ノードベース型 | 種別(宣言/定義)、関数名、パラメータ、名前空間、定義、ID、InvokationInfoリスト | |
ClassBehaviorInfo | BehaviorInfo | クラスに所属する関数ノードベース型 | クラス名 | |
ConstructorInfo | ClassBehaviorInfo | コンストラクタ情報 | - | |
DestructorInfo | ClassBehaviorInfo | デストラクタ情報 | - | |
CppMethodInfo | ClassBehaviorInfo | クラスメンバ関数情報 | 返り値 | |
FunctionInfo | BehaviorInfo | Cスタイル関数情報 | 返り値 | |
InvokationInfo | AstNodeInfo | 関数呼び出し情報 | 関数名、ID、参照先関数BehaviorInfo | |
BehaviorLocationMap | Dictionary<string,BehaviorInfo> | ソース位置をキーとしたBehaviorInfoのDictionary | Key : ソース位置 / Value : BehaviorInfo | |
TranslationUnitMap | Dictionary<string,TranslationUnitInfo> | ソースパスをキーとしたTranslationUnitInfoのDictionary | Key : ソースパス / Value : TranslationUnitInfo |
それぞれのソースを示します。
[AstNodeInfo.cs]
public abstract class AstNodeInfo{public string Location { get; }public AstNodeInfo(ClangCursor cursor){/* ソース上の位置情報を取得 */this.Location = this.GetLocation(cursor);}public string GetLocation(ClangCursor cursor){/* カーソル位置を取得 */var loc = cursor.Location;/* カーソル位置がnullでない */if (loc != null){/* ファイル位置を取得 */var floc = loc.FileLocation;/* ファイル位置がnullでない */if (floc != null){/* ファイル情報がnullでない */if(floc.File != null){/* ファイルパスを取得 */var path = Path.GetFullPath(floc.File.FileName);/* ファイル上の行番号を取得 */var line = floc.Line;/* ファイル上の列番号を取得 */var col = floc.Column;/* 位置情報文字列を生成 */var location = $"{path}[L.{line},C.{col}]";return location;}else{/* ファイル上の行番号を取得 */var line = floc.Line;/* ファイル上の列番号を取得 */var col = floc.Column;/* 位置情報文字列を生成 */var location = $"[L.{line},C.{col}]";return location;}}else{/* 位置情報なし */return string.Empty;}}else{/* 位置情報なし */return string.Empty;}}}
[TranslationUnitInfo.cs]
public class TranslationUnitInfo : AstNodeInfo{public string Path { get; }public BehaviorLocationMap BehaviorMap { get; } = new BehaviorLocationMap();public TranslationUnitInfo(ClangCursor cursor) : base(cursor){/* ソースファイルのフルパスを取得 */this.Path = System.IO.Path.GetFullPath(cursor.Spelling);}public void AddBehavior(BehaviorInfo behavior){this.BehaviorMap.Add(behavior);}public override string ToString(){return $"[TranslationUnit]{this.Path}";}}
[BehaviorInfo.cs]
public abstract class BehaviorInfo : AstNodeInfo{public string Type { get; }public string Name { get; }public string Parameters { get; }public string NameSpace { get; }/* 関数定義は継承先で生成 */public string Definition => this.GetDefinition();/* 関数のユニークなIDを生成 */public string ID => this.GenerateBehaviorID();public List<InvokationInfo> Invokations { get; } = new List<InvokationInfo>();protected abstract string GetDefinition();public BehaviorInfo(ClangCursor cursor) : base(cursor){/* 関数の種別を判定 */this.Type = cursor.IsDefinition ? "Definition" : "Declaration";/* 関数名を取得 */this.Name = cursor.Spelling;/* 関数パラメータを取得 */this.Parameters = cursor.DisplayName.Replace(cursor.Spelling, "");/* 名前空間を取得 */this.NameSpace = this.GetNamespace(cursor);}public void AddInvokation(InvokationInfo invokation_info){this.Invokations.Add(invokation_info);}private string GenerateBehaviorID(){/* 関数のIDには関数定義を使用 */return this.Definition;}/* 名前空間を取得 */private string GetNamespace(ClangCursor cursor){/* 名前空間保持用スタック */var namespace_stack = new Stack<string>();/* 名前空間を再帰的にパース */this.ParseNamespace(cursor, namespace_stack);/* スタックの要素数が0 */if (namespace_stack.Count == 0){/* 名前空間なし */return string.Empty;}else{var sb = new StringBuilder();/* 一番上の名前空間を追加 */sb.Append(namespace_stack.Pop());/* スタックが空になるまで */while(namespace_stack.Count != 0){/* 階層化された名前空間を連結 */sb.Append("::");sb.Append(namespace_stack.Pop());}return sb.ToString();}}/* 名前空間を再帰的にパース */private void ParseNamespace(ClangCursor cursor, Stack<string> namespace_stack){/* 関数の意味的な親を示すカーソルを取得 */var parent = cursor.SemanticParent;/* 親カーソルがnullでない */if (parent != null){/* 親カーソルがTranslation Unit */if (parent.Kind == CursorKind.TranslationUnit){/* 最上位のためパース終了 */}/* 親カーソルがNamespace */else if(parent.Kind == CursorKind.Namespace){/* 名前空間保持用スタックに追加 */namespace_stack.Push(parent.Spelling);/* 親カーソルの名前空間を再帰的にパース */this.ParseNamespace(parent, namespace_stack);}/* それ以外(クラスなど) */else{/* 親カーソルの名前空間を再帰的にパース */this.ParseNamespace(parent, namespace_stack);}}}public override string ToString(){return $"[Behavior][{this.Type}] {this.Definition}";}}
[ClassBehaviorInfo.cs]
public abstract class ClassBehaviorInfo : BehaviorInfo{public string ClassName { get; }public ClassBehaviorInfo(ClangCursor cursor) : base(cursor){/* 意味的な親カーソル(クラス)を取得 */var parent_class = cursor.SemanticParent;/* クラス名を取得 */this.ClassName = parent_class.Spelling;}public override string ToString(){return $"[ClassBehavior][{this.Type}] {this.Definition}";}}
[ConstructorInfo.cs]
public class ConstructorInfo : ClassBehaviorInfo{public ConstructorInfo(ClangCursor cursor) : base(cursor){}protected override string GetDefinition(){var ns = this.NameSpace == string.Empty ? "" : $"{this.NameSpace}::";/* コンストラクタ定義 : [<名前空間>::]<クラス名>::<コンストラクタ名>(<パラメータ>) */return $"{ns}{this.ClassName}::{this.Name}{this.Parameters}";}public override string ToString(){return $"[Constructor][{this.Type}] {this.Definition}";}}
[DestructorInfo.cs]
public class DestructorInfo : ClassBehaviorInfo{public DestructorInfo(ClangCursor cursor) : base(cursor){}protected override string GetDefinition(){var ns = this.NameSpace == string.Empty ? "" : $"{this.NameSpace}::";/* デストラクタ定義 : [<名前空間>::]<クラス名>::<デストラクタ名>(<パラメータ>) */return $"{ns}{this.ClassName}::{this.Name}{this.Parameters}";}public override string ToString(){return $"[Destructor][{this.Type}] {this.Definition}";}}
[CppMethodInfo.cs]
public class CppMethodInfo : ClassBehaviorInfo{public string ReturnType { get; }public CppMethodInfo(ClangCursor cursor) : base(cursor){var rtype = cursor.ResultType;this.ReturnType = rtype.Spelling;}protected override string GetDefinition(){var ns = this.NameSpace == string.Empty ? "" : $"{this.NameSpace}::";/* C++クラスメンバ関数定義 : <返り値型> [<名前空間>::]<クラス名>::<関数名>(<パラメータ>) */return $"{this.ReturnType} {ns}{this.ClassName}::{this.Name}{this.Parameters}";}public override string ToString(){return $"[Function][{this.Type}] {this.Definition}";}}
[FunctionInfo.cs]
public class FunctionInfo : BehaviorInfo{public string ReturnType { get; }public FunctionInfo(ClangCursor cursor) : base(cursor){var rtype = cursor.ResultType;this.ReturnType = rtype.Spelling;}protected override string GetDefinition(){var ns = this.NameSpace == string.Empty ? "" : $"{this.NameSpace}::";/* Cスタイル関数定義 : <返り値型> [<名前空間>::]<関数名>(<パラメータ>) */return $"{this.ReturnType} {ns}{this.Name}{this.Parameters}";}public override string ToString(){return $"[Function][{this.Type}] {this.Definition}";}}
[InvokationInfo.cs]
public class InvokationInfo : AstNodeInfo{public string ID { get; }public string Definition { get; }public string Name { get; }public BehaviorInfo Declaration { get; }public InvokationInfo(ClangCursor cursor) : base(cursor){/* コール関数名を取得 */this.Name = cursor.Spelling;/* コール関数の参照先がnullでない */if (cursor.Referenced != null){/* 参照先カーソルから関数宣言のBehavior Infoを生成 */this.Declaration = BehaviorInfoFactory.Create(cursor.Referenced);/* 関数宣言のBehavior Infoから関数IDを取得 */this.ID = this.Declaration.ID;/* 関数宣言のBehavior Infoから関数定義を取得 */this.Definition = this.Declaration.Definition;}else{throw new FieldAccessException($"Behavior Declaration Not Found : {this.Name}");}}public override string ToString(){return $"[Invokation]{this.Name}";}}
[BehaviorlocationMap.cs]
public class BehaviorLocationMap : Dictionary<string, BehaviorInfo>{public void Add(BehaviorInfo behavior){if (this.ContainsKey(behavior.Location) == false){this.Add(behavior.Location, behavior);}else{throw new InvalidOperationException($"Behavior Already Registered : {behavior.ID}");}}public void AddIfNotRegistered(BehaviorInfo behavior){if (this.ContainsKey(behavior.Location) == false){this.Add(behavior.Location, behavior);}else{/* Nothing to do */}}}
[BehaviorlocationMap.cs]
public class TranslationUnitMap : Dictionary<string, TranslationUnitInfo>{public void Add(TranslationUnitInfo tu){if (this.ContainsKey(tu.Path) == false){this.Add(tu.Path, tu);}else{throw new InvalidOperationException($"Translation Unit Already Registered : {tu.Path}");}}}
1-2. AST解析
続いて、ASTを解析してTranslationUnitMapを生成するロジックを実現するクラスを定義します。
また、ASTを解析する部分は、様々なソースファイルの入力に対応できるように設定情報を保持するクラスを定義し、それをパラメータとして渡すようにします。
さらに、関数情報(BehaviorInfo)はカーソルの種別に対応したクラスのインスタンスを生成できるようにFactoryクラスを定義します。
それぞれのソースを以下に示します。
[TranslationUnitParseSetting.cs]
public class TranslationUnitParseSetting{public List<string> Sources { get; set; } = new List<string>();public List<string> CommandLineArgs { get; set; } = new List<string>();public TranslationUnitFlags ParseOptions { get; set; } = TranslationUnitFlags.None;public bool DisplayDiag { get; set; } = true;public bool DumpAST { get; set; } = true;}
[BehaviorInfoFactory.cs]
public static class BehaviorInfoFactory{public static BehaviorInfo Create(ClangCursor cursor){switch(cursor.Kind){/* コンストラクタ */case CursorKind.Constructor:return new ConstructorInfo(cursor);/* デストラクタ */case CursorKind.Destructor:return new DestructorInfo(cursor);/* Cスタイル関数 */case CursorKind.FunctionDeclaration:return new FunctionInfo(cursor);/* C++クラスメンバ関数 */case CursorKind.CXXMethod:return new CppMethodInfo(cursor);default:throw new ArgumentException($"Not Behavior Cursor");}}}
[TranslationUnitsParser.cs]
public static class TranslationUnitsParser{public static TranslationUnitMap Parse(TranslationUnitParseSetting setting){var impl = new TranslationUnitsParserImpl();var map = impl.Execute(setting);return map;}public class TranslationUnitsParserImpl : AMessageable{private TranslationUnitParseSetting Setting { get; set; }private TranslationUnitInfo CurrentTranslationUnit { get; set; }private BehaviorInfo CurrentBehavior { get; set; }private TranslationUnitMap TranslationUnitMap { get; set; } = new TranslationUnitMap();public TranslationUnitMap Execute(TranslationUnitParseSetting setting){this.Setting = setting;var display_diag = setting.DisplayDiag;var src_path_list = setting.Sources;var command_line_args = setting.CommandLineArgs.ToArray();var options = setting.ParseOptions;/* Indexを生成 */using (var index = Clang.CreateIndex(false, display_diag)){/* 解析対象のソースファイルパスリストを走査 */foreach (var src_path in src_path_list){/* ソースファイルパスのファイルが存在するかを確認 */if (File.Exists(src_path)){/* ソースファイルをパースし、Translation Unitを生成 */using (var tu = index.ParseTranslationUnit(src_path, command_line_args, new ClangUnsavedFile[0], options)){/* 解析実行 */this.ExecuteCore(index, tu);}}else{throw new InvalidOperationException($"Source File Not Found :{src_path}");}}}return this.TranslationUnitMap;}private void ExecuteCore(ClangIndex index, ClangTranslationUnit tu){if (this.Setting.DumpAST){this.SendMessage($"AST Dump:");}/* ASTを再帰的にパース */this.VisitChild(tu.Cursor, 0);}private ChildVisitResult Visitor(ClangCursor cursor, ClangCursor parent, int depth){/* カーソル位置を取得 */var loc = cursor.Location;/* 解析対象ソースファイル内 */if (loc.IsFromMainFile == true){/* 子要素をパース */this.VisitChild(cursor, depth);}else{/* システムヘッダ内 */if (loc.IsInSystemHeader == true){/* Nothing to do */}/* ユーザヘッダ内 */else{/* 子要素をパース */this.VisitChild(cursor, depth);}}return ChildVisitResult.Continue;}private void VisitChild(ClangCursor cursor, int depth){/* デバッグ用にASTをダンプ */if (this.Setting.DumpAST){this.DumpAstInfo(cursor, depth);}/* 関数コール情報を解析 */this.AnalyseInvokationInfo(cursor);cursor.VisitChildren(this.Visitor, depth + 1);}private void DumpAstInfo(ClangCursor cursor, int depth){var indent = new string(' ', depth * 2);var kind = cursor.Kind;var name = cursor.Spelling;var loc = cursor.Location.ToStringEx();this.SendMessage($"{indent}[{kind}] {name} @ {loc}");}private void AnalyseInvokationInfo(ClangCursor cursor){switch (cursor.Kind){/* 解析対象ソースファイル */case CursorKind.TranslationUnit:/* Translation Unit Infoを生成し、Translation Unit Mapに追加 */this.CurrentTranslationUnit = new TranslationUnitInfo(cursor);this.TranslationUnitMap.Add(this.CurrentTranslationUnit);break;/* コンストラクタ */case CursorKind.Constructor:/* Constructor Infoを生成し、Translation Unit Infoに追加 */this.CurrentBehavior = BehaviorInfoFactory.Create(cursor);this.CurrentTranslationUnit.AddBehavior(this.CurrentBehavior);break;/* デストラクタ */case CursorKind.Destructor:/* Destructor Infoを生成し、Translation Unit Infoに追加 */this.CurrentBehavior = BehaviorInfoFactory.Create(cursor);this.CurrentTranslationUnit.AddBehavior(this.CurrentBehavior);break;/* Cスタイル関数*/case CursorKind.FunctionDeclaration:/* Function Infoを生成し、Translation Unit Infoに追加 */this.CurrentBehavior = BehaviorInfoFactory.Create(cursor);this.CurrentTranslationUnit.AddBehavior(this.CurrentBehavior);break;/* C++メンバ関数 */case CursorKind.CXXMethod:/* Cpp Method Infoを生成し、Translation Unit Infoに追加 */this.CurrentBehavior = BehaviorInfoFactory.Create(cursor);this.CurrentTranslationUnit.AddBehavior(this.CurrentBehavior);break;/* 関数コール */case CursorKind.CallExpression:/* Invokation Infoを生成し、Translation Unit Infoに追加 */var invokation_info = new InvokationInfo(cursor);this.CurrentBehavior.AddInvokation(invokation_info);break;default:break;}}}}
2. Invokation Tree(関数コールツリー)生成
次は、1.で生成したTranslationUnitMapの情報から関数コールツリー情報を生成し、出力する機能を持つクラスを実装します。
関数コールツリーを出力するために、TranslationUnitMapに登録されている各TranslationUnitInfoのBehaviorMapから、種別がDefinition(関数定義)のBehaviorInfoを抽出し、Behavior IDをキーとしたハッシュマップを生成します。
生成するハッシュマップ(BehaviorDefinitionMapクラス)の定義と、ハッシュマップ生成処理の実装コードを以下に示します。
[BehaviorDefinitionMap.cs]
public class BehaviorDefinitionMap : Dictionary<string, BehaviorInfo>{public void Add(BehaviorInfo behavior){if (this.ContainsKey(behavior.ID) == false){this.Add(behavior.ID, behavior);}else{throw new InvalidOperationException($"Behavior Already Registered : {behavior.ID}");}}public void AddIfNotRegistered(BehaviorInfo behavior){if (this.ContainsKey(behavior.ID) == false){this.Add(behavior.ID, behavior);}else{/* Nothing to do */}}}
[ハッシュマップ生成処理]
private void CreateBehaviorDefinitionMap(){/* Translation Unit Infoを走査 */foreach (var tu in this.TranslationUnitMap.Values){/* Translation Unit InfoのBehavior MapからTypeがDefinitionのBehavior Infoを抽出 */var behavior_definitions = tu.BehaviorMap.Values.Where(b => b.Type == "Definition").ToList();/* 抽出したBehavior Infoを走査 */foreach (var behavior_definition in behavior_definitions){/* Behavior Infoが登録されていなければ、Behavior Definition Mapに登録 */this.BehaviorDefinitionMap.AddIfNotRegistered(behavior_definition);}}}
生成したBehaviorDefinitionMapを用いて、関数が呼び出している関数情報を辿っていき、関数のコールツリーを出力します。
実装コードを以下に示します。
[関数コールツリー出力]
private void DumpInvokationTrees(){/* Behavior Definition MapのValue(BehaviorInfo)リストを取得 */var behavior_defnitions = this.BehaviorDefinitionMap.Values;/* Behavior Definitionリストを走査 */foreach (var behavior in behavior_defnitions){/* 関数定義と定義位置を出力 */this.SendMessage($"{behavior.Definition} @ {behavior.Location}");/* 関数のコールツリーを出力 */this.DumpInvokationTree(behavior);/* 改行 */this.SendMessage();}}private void DumpInvokationTree(BehaviorInfo behavior, int depth = 1){/* Behavior InfoのInvokation Infoリストを走査 */foreach (var invokation in behavior.Invokations){/* インデント(1段階)を生成 */var indent = new string(' ', depth * 2);/* インデント(2段階)を生成 */var indent2 = new string(' ', (depth + 1) * 2);/* コール関数定義とコール位置を出力 */this.SendMessage($"{indent}{invokation.Definition} @ {invokation.Location}");/* 親関数とコール関数のIDが同一(再帰呼び出し) */if (behavior.ID == invokation.ID){/* 再帰呼び出しの旨を出力 */this.SendMessage($"{indent2} <Recursive Call...>");}else{/* Behavior Definition Mapにコール関数と同一IDのBehavior Infoがあるかを確認 */if (this.BehaviorDefinitionMap.ContainsKey(invokation.ID)){/* コール関数のBehavior Infoを取得 */var child_behavior = this.BehaviorDefinitionMap[invokation.ID];/* コール関数を親にしてコールツリーを出力 */this.DumpInvokationTree(child_behavior, depth + 1);}else{/* コール関数の関数定義が見つからない旨を出力 */this.SendMessage($"{indent2} <Behavior Definition Not Found...>");}}}}
ソース全文を以下に示します。
[InvokationTreeDumper.cs]
public static class InvokationTreeDumper{public static void Dump(TranslationUnitMap map){var impl = new InvokationTreeDumperImpl();impl.Execute(map);}public class InvokationTreeDumperImpl : AMessageable{private TranslationUnitMap TranslationUnitMap { get; set; } = new TranslationUnitMap();private BehaviorDefinitionMap BehaviorDefinitionMap { get; set; } = new BehaviorDefinitionMap();public void Execute(TranslationUnitMap map){this.TranslationUnitMap = map;this.CreateBehaviorDefinitionMap();this.SendMessage();this.SendMessage("--------------------------------------");this.SendMessage("Invokation Tree:");this.DumpInvokationTrees();}private void CreateBehaviorDefinitionMap(){foreach (var tu in this.TranslationUnitMap.Values){var behavior_definitions = tu.BehaviorMap.Values.Where(b => b.Type == "Definition").ToList();foreach (var behavior_definition in behavior_definitions){this.BehaviorDefinitionMap.AddIfNotRegistered(behavior_definition);}}}private void DumpInvokationTrees(){var behavior_defnitions = this.BehaviorDefinitionMap.Values;foreach (var behavior in behavior_defnitions){this.SendMessage($"{behavior.Definition} @ {behavior.Location}");this.DumpInvokationTree(behavior);this.SendMessage();}}private void DumpInvokationTree(BehaviorInfo behavior, int depth = 1){foreach (var invokation in behavior.Invokations){var indent = new string(' ', depth * 2);var indent2 = new string(' ', (depth + 1) * 2);this.SendMessage($"{indent}{invokation.Definition} @ {invokation.Location}");if (behavior.ID == invokation.ID){this.SendMessage($"{indent2} <Recursive Call...>");}else{if (this.BehaviorDefinitionMap.ContainsKey(invokation.ID)){var child_behavior = this.BehaviorDefinitionMap[invokation.ID];this.DumpInvokationTree(child_behavior, depth + 1);}else{this.SendMessage($"{indent2} <Behavior Definition Not Found...>");}}}}}}
3. Cross Reference(関数参照情報)生成
続いて1.で生成したTranslationUnitMapの情報から関数クロスリファレンス(宣言/定義/コール)情報を生成し、出力する機能を持つクラスを実装します。
クロスリファレンス情報を保持するためのクラス(BehaviorCrossReferenceInfo)と、それを関数ごとにまとめるコンテナ(BehaviorCrossReferenceMap)を定義します。
なお、BehaviorCrossReferenceInfoは、重複を排除するためにValueObjectパターンで実装します。
それぞれの実装コードを以下に示します。
[ValueObject.cs]
public abstract class ValueObject<T> where T : ValueObject<T>{/* データの同一性を判定する関数 -> 継承先で実装 */protected abstract bool EqualsCore(T other);/* ハッシュコード生成に使用するパラメータを取得する関数 -> 継承先で実装 */protected abstract IEnumerable<object> GetHashCodeParameters();public static bool operator ==(ValueObject<T> left, ValueObject<T> right){return Equals(left, right);}public static bool operator !=(ValueObject<T> left, ValueObject<T> right){return !Equals(left, right);}public override bool Equals(object obj){var other = obj as T;if (other == null){return false;}else{return this.EqualsCore(other);}}public override int GetHashCode(){var objs = this.GetHashCodeParameters();var hash = 0;foreach(var obj in objs){hash ^= obj.GetHashCode();}return hash;}}
[BehaviorCrossReferenceInfo.cs]
public class BehaviorCrossReferenceInfo : ValueObject<BehaviorCrossReferenceInfo>{public string ID { get; }public string Type { get; }public string Definition { get; }public string Location { get; }/* Behavior Infoからクロスリファレンス情報を生成 */public BehaviorCrossReferenceInfo(BehaviorInfo info){this.ID = info.ID;this.Type = info.Type;this.Definition = info.Definition;this.Location = info.Location;}/* Invokation Infoからクロスリファレンス情報を生成 */public BehaviorCrossReferenceInfo(InvokationInfo info){this.ID = info.ID;this.Type = "Call";this.Definition = info.Definition;this.Location = info.Location;}public override string ToString(){return $"[{this.Type}]{this.Definition} @ {this.Location}";}/* 各プロパティの値が全て一致する場合、同一のオブジェクトとみなす */protected override bool EqualsCore(BehaviorCrossReferenceInfo other){return (this.ID == other.ID && this.Type == other.Type && this.Definition == other.Definition && this.Location == other.Location);}/* 各プロパティの値をもとにハッシュコードを生成 */protected override IEnumerable<object> GetHashCodeParameters(){var objs = new List<object>(){this.ID,this.Definition,this.Type,this.Location,};return objs;}}
[BehaviorCrossReferenceMap.cs]
/* 同一ID(同一関数)ごとにクロスリファレンス情報をまとめるコンテナ(重複は排除) */public class BehaviorCrossReferenceMap : Dictionary<string, HashSet<BehaviorCrossReferenceInfo>>{public void Add(BehaviorCrossReferenceInfo info){if (this.ContainsKey(info.ID) == false){this.Add(info.ID, new HashSet<BehaviorCrossReferenceInfo>());}this[info.ID].Add(info);}}
上記で示したクロスリファレンス情報を生成するコードを実装します。
[クロスリファレンス情報生成]
private void AnalyseCrossReferences(){var map1 = new BehaviorCrossReferenceMap();/* Translation Unit Infoを走査 */foreach (var tu in this.TranslationUnitMap.Values){/* Translation Unit InfoのBehavior Mapの値(Behavior Info)を走査 */foreach (var behavior in tu.BehaviorMap.Values){/* Behavior InfoからBehavior Cross Reference Infoを生成 */var xref_behavior = new BehaviorCrossReferenceInfo(behavior);/* Cross Reference Mapに登録 */map1.Add(xref_behavior);/* Behavior InfoのInvokation Infoリストを走査 */foreach (var invoke in behavior.Invokations){/* Invokation InfoからBehavior Cross Reference Infoを生成 */var xref_invoke = new BehaviorCrossReferenceInfo(invoke);/* Cross Reference Mapに登録 */map1.Add(xref_invoke);}}}/* 新規Behavior Cross Reference Mapを生成 */var map2 = new BehaviorCrossReferenceMap();/* 前段で生成したBehavior Cross Reference Mapの各キーを走査 */foreach (var key in map1.Keys){/* キーを指定してハッシュセットを取得 */var set = map1[key];/* Behavior Cross Reference InfoのTypeでソート */var sorted_set = set.OrderBy(x => x.Type).ToHashSet();/* ソート後のハッシュセットを登録 */map2.Add(key, sorted_set);}this.CrossReferenceMap = map2;}
次に、生成したクロスリファレンス情報を順番に出力します。
実装コードを以下に示します。
[クロスリファレンス情報出力]
private void DumpCrossReferences(){/* Behavior Cross Reference Mapの各キー(Behavior ID)を走査 */foreach (var xref_id in this.CrossReferenceMap.Keys){/* Behavior IDを出力 */this.SendMessage($"{xref_id}");/* キーを指定してBehavior Cross Reference Infoのハッシュセットを取得 */var xref_hash_set = this.CrossReferenceMap[xref_id];/* ハッシュセットの要素(Behavior Cross Reference Info)を走査 */foreach (var xref_info in xref_hash_set){var type = xref_info.Type;var loc = xref_info.Location;this.SendMessage($" [{type}] @ {loc}");}this.SendMessage();}}
ソース全文を以下に示します。
[CrossReferencesDumper.cs]
public static class CrossReferencesDumper{public static void Dump(TranslationUnitMap map){var impl = new CrossReferencesDumperImpl();impl.Execute(map);}public class CrossReferencesDumperImpl : AMessageable{private TranslationUnitMap TranslationUnitMap { get; set; } = new TranslationUnitMap();private BehaviorCrossReferenceMap CrossReferenceMap { get; set; } = new BehaviorCrossReferenceMap();public void Execute(TranslationUnitMap map){this.TranslationUnitMap = map;this.AnalyseCrossReferences();this.SendMessage();this.SendMessage("--------------------------------------");this.SendMessage("Cross References:");this.DumpCrossReferences();}private void AnalyseCrossReferences(){var map1 = new BehaviorCrossReferenceMap();foreach (var tu in this.TranslationUnitMap.Values){foreach (var behavior in tu.BehaviorMap.Values){var xref_behavior = new BehaviorCrossReferenceInfo(behavior);map1.Add(xref_behavior);foreach (var invoke in behavior.Invokations){var xref_invoke = new BehaviorCrossReferenceInfo(invoke);map1.Add(xref_invoke);}}}var map2 = new BehaviorCrossReferenceMap();foreach (var key in map1.Keys){var set = map1[key];var sorted_set = set.OrderBy(x => x.Type).ToHashSet();map2.Add(key, sorted_set);}this.CrossReferenceMap = map2;}private void DumpCrossReferences(){foreach (var xref_id in this.CrossReferenceMap.Keys){this.SendMessage($"{xref_id}");var xref_hash_set = this.CrossReferenceMap[xref_id];foreach (var xref_info in xref_hash_set){var type = xref_info.Type;var loc = xref_info.Location;this.SendMessage($" [{type}] @ {loc}");}this.SendMessage();}}}}
4. 各処理呼び出し
最後に、1-3.で実装した各機能を呼び出す処理を実装します。
public static class BehaviorRelationsAnalyser{public static void Execute(){/* Translation Unitのパース設定情報を生成 */var setting = new TranslationUnitParseSetting(){/* パース対象ソースパスの指定 */Sources = new List<string>(){"./Code2/src/main.cpp","./Code2/src/TestClass.cpp",},/* libclangに渡すコマンドライン引数(インクルードパスなど)の指定 */CommandLineArgs = new List<string>(){"-I./Code2/inc",},/* libclangのTranslation Unitパースオプション */ParseOptions = TranslationUnitFlags.None,/* ダイアグ情報(構文エラーなど)を表示 */DisplayDiag = true,/* AST情報を出力(デバッグ用) */DumpAST = true,};/* Translation Unitをパースし、Translation Unit Mapを生成 */var map = TranslationUnitsParser.Parse(setting);/* Translation Unit Mapから関数コールツリーを生成して出力 */InvokationTreeDumper.Dump(map);/* Translation Unit Mapから関数クロスリファレンス情報を生成して出力 */CrossReferencesDumper.Dump(map);}}
解析対象サンプルソースを入力して実行した結果を示します。
上から、ASTのダンプ結果、関数コールツリー、関数クロスリファレンス情報が出力されています。
[実行結果]
AST Dump: [TranslationUnit] ./Code2/src/main.cpp @ [Namespace] root @ ./Code2/inc/TestClass.h[L.3,C.11] [Namespace] sub1 @ ./Code2/inc/TestClass.h[L.5,C.15] [Namespace] sub2 @ ./Code2/inc/TestClass.h[L.7,C.19] [ClassDeclaration] TestClass @ ./Code2/inc/TestClass.h[L.9,C.19] [LastDeclaration] @ ./Code2/inc/TestClass.h[L.11,C.13] [Constructor] TestClass @ ./Code2/inc/TestClass.h[L.12,C.17] [Destructor] ~TestClass @ ./Code2/inc/TestClass.h[L.13,C.17] [CXXMethod] PublicMethod @ ./Code2/inc/TestClass.h[L.15,C.22] [ParmDeclaration] a @ ./Code2/inc/TestClass.h[L.15,C.39] [ParmDeclaration] b @ ./Code2/inc/TestClass.h[L.15,C.46] [CXXMethod] GetA @ ./Code2/inc/TestClass.h[L.17,C.21] [CompoundStatement] @ ./Code2/inc/TestClass.h[L.17,C.28] [ReturnStatement] @ ./Code2/inc/TestClass.h[L.17,C.30] [UnexposedExpression] m_a @ ./Code2/inc/TestClass.h[L.17,C.43] [MemberReferenceExpression] m_a @ ./Code2/inc/TestClass.h[L.17,C.43] [CXXThisExpression] @ ./Code2/inc/TestClass.h[L.17,C.37] [CXXMethod] GetB @ ./Code2/inc/TestClass.h[L.18,C.21] [CompoundStatement] @ ./Code2/inc/TestClass.h[L.18,C.28] [ReturnStatement] @ ./Code2/inc/TestClass.h[L.18,C.30] [UnexposedExpression] m_b @ ./Code2/inc/TestClass.h[L.18,C.43] [MemberReferenceExpression] m_b @ ./Code2/inc/TestClass.h[L.18,C.43] [CXXThisExpression] @ ./Code2/inc/TestClass.h[L.18,C.37] [CXXMethod] GetResult @ ./Code2/inc/TestClass.h[L.19,C.21] [CompoundStatement] @ ./Code2/inc/TestClass.h[L.19,C.33] [ReturnStatement] @ ./Code2/inc/TestClass.h[L.19,C.35] [UnexposedExpression] m_result @ ./Code2/inc/TestClass.h[L.19,C.48] [MemberReferenceExpression] m_result @ ./Code2/inc/TestClass.h[L.19,C.48] [CXXThisExpression] @ ./Code2/inc/TestClass.h[L.19,C.42] [CXXMethod] SetA @ ./Code2/inc/TestClass.h[L.21,C.22] [ParmDeclaration] a @ ./Code2/inc/TestClass.h[L.21,C.31] [CompoundStatement] @ ./Code2/inc/TestClass.h[L.21,C.34] [BinaryOperator] @ ./Code2/inc/TestClass.h[L.21,C.36] [MemberReferenceExpression] m_a @ ./Code2/inc/TestClass.h[L.21,C.42] [CXXThisExpression] @ ./Code2/inc/TestClass.h[L.21,C.36] [UnexposedExpression] a @ ./Code2/inc/TestClass.h[L.21,C.48] [DeclarationReferenceExpression] a @ ./Code2/inc/TestClass.h[L.21,C.48] [CXXMethod] SetB @ ./Code2/inc/TestClass.h[L.22,C.22] [ParmDeclaration] b @ ./Code2/inc/TestClass.h[L.22,C.31] [CompoundStatement] @ ./Code2/inc/TestClass.h[L.22,C.34] [BinaryOperator] @ ./Code2/inc/TestClass.h[L.22,C.36] [MemberReferenceExpression] m_b @ ./Code2/inc/TestClass.h[L.22,C.42] [CXXThisExpression] @ ./Code2/inc/TestClass.h[L.22,C.36] [UnexposedExpression] b @ ./Code2/inc/TestClass.h[L.22,C.48] [DeclarationReferenceExpression] b @ ./Code2/inc/TestClass.h[L.22,C.48] [CXXMethod] SetResult @ ./Code2/inc/TestClass.h[L.23,C.22] [ParmDeclaration] result @ ./Code2/inc/TestClass.h[L.23,C.36] [CompoundStatement] @ ./Code2/inc/TestClass.h[L.23,C.44] [BinaryOperator] @ ./Code2/inc/TestClass.h[L.23,C.46] [MemberReferenceExpression] m_result @ ./Code2/inc/TestClass.h[L.23,C.52] [CXXThisExpression] @ ./Code2/inc/TestClass.h[L.23,C.46] [UnexposedExpression] result @ ./Code2/inc/TestClass.h[L.23,C.63] [DeclarationReferenceExpression] result @ ./Code2/inc/TestClass.h[L.23,C.63] [LastDeclaration] @ ./Code2/inc/TestClass.h[L.25,C.13] [CXXMethod] PrivateMethod @ ./Code2/inc/TestClass.h[L.26,C.21] [ParmDeclaration] a @ ./Code2/inc/TestClass.h[L.26,C.39] [ParmDeclaration] b @ ./Code2/inc/TestClass.h[L.26,C.46] [LastDeclaration] @ ./Code2/inc/TestClass.h[L.28,C.13] [FieldDeclaration] m_a @ ./Code2/inc/TestClass.h[L.29,C.21] [FieldDeclaration] m_b @ ./Code2/inc/TestClass.h[L.30,C.21] [FieldDeclaration] m_result @ ./Code2/inc/TestClass.h[L.31,C.21] [FunctionDeclaration] RecursiveCallTest @ ./Code2/src/main.cpp[L.5,C.6] [ParmDeclaration] i @ ./Code2/src/main.cpp[L.5,C.28] [FunctionDeclaration] main @ ./Code2/src/main.cpp[L.7,C.5] [CompoundStatement] @ ./Code2/src/main.cpp[L.8,C.1] [DeclarationStatement] @ ./Code2/src/main.cpp[L.9,C.5] [VarDeclaration] test_instance @ ./Code2/src/main.cpp[L.9,C.33] [NamespaceReference] root @ ./Code2/src/main.cpp[L.9,C.5] [NamespaceReference] sub1 @ ./Code2/src/main.cpp[L.9,C.11] [NamespaceReference] sub2 @ ./Code2/src/main.cpp[L.9,C.17] [TypeReference] class root::sub1::sub2::TestClass @ ./Code2/src/main.cpp[L.9,C.23] [CallExpression] TestClass @ ./Code2/src/main.cpp[L.9,C.33] [DeclarationStatement] @ ./Code2/src/main.cpp[L.10,C.5] [VarDeclaration] a @ ./Code2/src/main.cpp[L.10,C.9] [IntegerLiteral] @ ./Code2/src/main.cpp[L.10,C.13] [DeclarationStatement] @ ./Code2/src/main.cpp[L.11,C.5] [VarDeclaration] b @ ./Code2/src/main.cpp[L.11,C.9] [IntegerLiteral] @ ./Code2/src/main.cpp[L.11,C.13] [DeclarationStatement] @ ./Code2/src/main.cpp[L.12,C.5] [VarDeclaration] result @ ./Code2/src/main.cpp[L.12,C.9] [IntegerLiteral] @ ./Code2/src/main.cpp[L.12,C.18] [CallExpression] SetA @ ./Code2/src/main.cpp[L.14,C.5] [MemberReferenceExpression] SetA @ ./Code2/src/main.cpp[L.14,C.19] [DeclarationReferenceExpression] test_instance @ ./Code2/src/main.cpp[L.14,C.5] [UnexposedExpression] a @ ./Code2/src/main.cpp[L.14,C.24] [DeclarationReferenceExpression] a @ ./Code2/src/main.cpp[L.14,C.24] [CallExpression] SetB @ ./Code2/src/main.cpp[L.16,C.5] [MemberReferenceExpression] SetB @ ./Code2/src/main.cpp[L.16,C.19] [DeclarationReferenceExpression] test_instance @ ./Code2/src/main.cpp[L.16,C.5] [UnexposedExpression] b @ ./Code2/src/main.cpp[L.16,C.24] [DeclarationReferenceExpression] b @ ./Code2/src/main.cpp[L.16,C.24] [CallExpression] operator<< @ ./Code2/src/main.cpp[L.18,C.5] [CallExpression] operator<< @ ./Code2/src/main.cpp[L.18,C.5] [CallExpression] operator<< @ ./Code2/src/main.cpp[L.18,C.5] [DeclarationReferenceExpression] cout @ ./Code2/src/main.cpp[L.18,C.10] [NamespaceReference] std @ ./Code2/src/main.cpp[L.18,C.5] [UnexposedExpression] operator<< @ ./Code2/src/main.cpp[L.18,C.15] [DeclarationReferenceExpression] operator<< @ ./Code2/src/main.cpp[L.18,C.15] [UnexposedExpression] @ ./Code2/src/main.cpp[L.18,C.18] [StringLiteral] "a : " @ ./Code2/src/main.cpp[L.18,C.18] [UnexposedExpression] operator<< @ ./Code2/src/main.cpp[L.18,C.25] [DeclarationReferenceExpression] operator<< @ ./Code2/src/main.cpp[L.18,C.25] [CallExpression] GetA @ ./Code2/src/main.cpp[L.18,C.28] [MemberReferenceExpression] GetA @ ./Code2/src/main.cpp[L.18,C.42] [DeclarationReferenceExpression] test_instance @ ./Code2/src/main.cpp[L.18,C.28] [UnexposedExpression] operator<< @ ./Code2/src/main.cpp[L.18,C.49] [DeclarationReferenceExpression] operator<< @ ./Code2/src/main.cpp[L.18,C.49] [UnexposedExpression] endl @ ./Code2/src/main.cpp[L.18,C.57] [DeclarationReferenceExpression] endl @ ./Code2/src/main.cpp[L.18,C.57] [NamespaceReference] std @ ./Code2/src/main.cpp[L.18,C.52] [CallExpression] operator<< @ ./Code2/src/main.cpp[L.20,C.5] [CallExpression] operator<< @ ./Code2/src/main.cpp[L.20,C.5] [CallExpression] operator<< @ ./Code2/src/main.cpp[L.20,C.5] [DeclarationReferenceExpression] cout @ ./Code2/src/main.cpp[L.20,C.10] [NamespaceReference] std @ ./Code2/src/main.cpp[L.20,C.5] [UnexposedExpression] operator<< @ ./Code2/src/main.cpp[L.20,C.15] [DeclarationReferenceExpression] operator<< @ ./Code2/src/main.cpp[L.20,C.15] [UnexposedExpression] @ ./Code2/src/main.cpp[L.20,C.18] [StringLiteral] "b : " @ ./Code2/src/main.cpp[L.20,C.18] [UnexposedExpression] operator<< @ ./Code2/src/main.cpp[L.20,C.25] [DeclarationReferenceExpression] operator<< @ ./Code2/src/main.cpp[L.20,C.25] [CallExpression] GetB @ ./Code2/src/main.cpp[L.20,C.28] [MemberReferenceExpression] GetB @ ./Code2/src/main.cpp[L.20,C.42] [DeclarationReferenceExpression] test_instance @ ./Code2/src/main.cpp[L.20,C.28] [UnexposedExpression] operator<< @ ./Code2/src/main.cpp[L.20,C.49] [DeclarationReferenceExpression] operator<< @ ./Code2/src/main.cpp[L.20,C.49] [UnexposedExpression] endl @ ./Code2/src/main.cpp[L.20,C.57] [DeclarationReferenceExpression] endl @ ./Code2/src/main.cpp[L.20,C.57] [NamespaceReference] std @ ./Code2/src/main.cpp[L.20,C.52] [CallExpression] PublicMethod @ ./Code2/src/main.cpp[L.22,C.5] [MemberReferenceExpression] PublicMethod @ ./Code2/src/main.cpp[L.22,C.19] [DeclarationReferenceExpression] test_instance @ ./Code2/src/main.cpp[L.22,C.5] [UnexposedExpression] a @ ./Code2/src/main.cpp[L.22,C.32] [DeclarationReferenceExpression] a @ ./Code2/src/main.cpp[L.22,C.32] [UnexposedExpression] b @ ./Code2/src/main.cpp[L.22,C.35] [DeclarationReferenceExpression] b @ ./Code2/src/main.cpp[L.22,C.35] [BinaryOperator] @ ./Code2/src/main.cpp[L.24,C.5] [DeclarationReferenceExpression] result @ ./Code2/src/main.cpp[L.24,C.5] [CallExpression] GetResult @ ./Code2/src/main.cpp[L.24,C.14] [MemberReferenceExpression] GetResult @ ./Code2/src/main.cpp[L.24,C.28] [DeclarationReferenceExpression] test_instance @ ./Code2/src/main.cpp[L.24,C.14] [CallExpression] operator<< @ ./Code2/src/main.cpp[L.26,C.5] [CallExpression] operator<< @ ./Code2/src/main.cpp[L.26,C.5] [CallExpression] operator<< @ ./Code2/src/main.cpp[L.26,C.5] [DeclarationReferenceExpression] cout @ ./Code2/src/main.cpp[L.26,C.10] [NamespaceReference] std @ ./Code2/src/main.cpp[L.26,C.5] [UnexposedExpression] operator<< @ ./Code2/src/main.cpp[L.26,C.15] [DeclarationReferenceExpression] operator<< @ ./Code2/src/main.cpp[L.26,C.15] [UnexposedExpression] @ ./Code2/src/main.cpp[L.26,C.18] [StringLiteral] "result : " @ ./Code2/src/main.cpp[L.26,C.18] [UnexposedExpression] operator<< @ ./Code2/src/main.cpp[L.26,C.30] [DeclarationReferenceExpression] operator<< @ ./Code2/src/main.cpp[L.26,C.30] [UnexposedExpression] result @ ./Code2/src/main.cpp[L.26,C.33] [DeclarationReferenceExpression] result @ ./Code2/src/main.cpp[L.26,C.33] [UnexposedExpression] operator<< @ ./Code2/src/main.cpp[L.26,C.40] [DeclarationReferenceExpression] operator<< @ ./Code2/src/main.cpp[L.26,C.40] [UnexposedExpression] endl @ ./Code2/src/main.cpp[L.26,C.48] [DeclarationReferenceExpression] endl @ ./Code2/src/main.cpp[L.26,C.48] [NamespaceReference] std @ ./Code2/src/main.cpp[L.26,C.43] [CallExpression] RecursiveCallTest @ ./Code2/src/main.cpp[L.28,C.5] [UnexposedExpression] RecursiveCallTest @ ./Code2/src/main.cpp[L.28,C.5] [DeclarationReferenceExpression] RecursiveCallTest @ ./Code2/src/main.cpp[L.28,C.5] [IntegerLiteral] @ ./Code2/src/main.cpp[L.28,C.23] [FunctionDeclaration] RecursiveCallTest @ ./Code2/src/main.cpp[L.31,C.6] [ParmDeclaration] i @ ./Code2/src/main.cpp[L.31,C.28] [CompoundStatement] @ ./Code2/src/main.cpp[L.32,C.1] [IfStatement] @ ./Code2/src/main.cpp[L.33,C.5] [BinaryOperator] @ ./Code2/src/main.cpp[L.33,C.9] [UnexposedExpression] i @ ./Code2/src/main.cpp[L.33,C.9] [DeclarationReferenceExpression] i @ ./Code2/src/main.cpp[L.33,C.9] [IntegerLiteral] @ ./Code2/src/main.cpp[L.33,C.13] [CompoundStatement] @ ./Code2/src/main.cpp[L.34,C.5] [ReturnStatement] @ ./Code2/src/main.cpp[L.35,C.9] [CompoundStatement] @ ./Code2/src/main.cpp[L.38,C.5] [CallExpression] operator<< @ ./Code2/src/main.cpp[L.39,C.9] [CallExpression] operator<< @ ./Code2/src/main.cpp[L.39,C.9] [CallExpression] operator<< @ ./Code2/src/main.cpp[L.39,C.9] [DeclarationReferenceExpression] cout @ ./Code2/src/main.cpp[L.39,C.14] [NamespaceReference] std @ ./Code2/src/main.cpp[L.39,C.9] [UnexposedExpression] operator<< @ ./Code2/src/main.cpp[L.39,C.19] [DeclarationReferenceExpression] operator<< @ ./Code2/src/main.cpp[L.39,C.19] [UnexposedExpression] @ ./Code2/src/main.cpp[L.39,C.22] [StringLiteral] "i : " @ ./Code2/src/main.cpp[L.39,C.22] [UnexposedExpression] operator<< @ ./Code2/src/main.cpp[L.39,C.29] [DeclarationReferenceExpression] operator<< @ ./Code2/src/main.cpp[L.39,C.29] [UnexposedExpression] i @ ./Code2/src/main.cpp[L.39,C.32] [DeclarationReferenceExpression] i @ ./Code2/src/main.cpp[L.39,C.32] [UnexposedExpression] operator<< @ ./Code2/src/main.cpp[L.39,C.34] [DeclarationReferenceExpression] operator<< @ ./Code2/src/main.cpp[L.39,C.34] [UnexposedExpression] endl @ ./Code2/src/main.cpp[L.39,C.42] [DeclarationReferenceExpression] endl @ ./Code2/src/main.cpp[L.39,C.42] [NamespaceReference] std @ ./Code2/src/main.cpp[L.39,C.37] [CallExpression] RecursiveCallTest @ ./Code2/src/main.cpp[L.41,C.9] [UnexposedExpression] RecursiveCallTest @ ./Code2/src/main.cpp[L.41,C.9] [DeclarationReferenceExpression] RecursiveCallTest @ ./Code2/src/main.cpp[L.41,C.9] [BinaryOperator] @ ./Code2/src/main.cpp[L.41,C.27] [UnexposedExpression] i @ ./Code2/src/main.cpp[L.41,C.27] [DeclarationReferenceExpression] i @ ./Code2/src/main.cpp[L.41,C.27] [IntegerLiteral] @ ./Code2/src/main.cpp[L.41,C.31] AST Dump: [TranslationUnit] ./Code2/src/TestClass.cpp @ [Namespace] root @ ./Code2/inc/TestClass.h[L.3,C.11] [Namespace] sub1 @ ./Code2/inc/TestClass.h[L.5,C.15] [Namespace] sub2 @ ./Code2/inc/TestClass.h[L.7,C.19] [ClassDeclaration] TestClass @ ./Code2/inc/TestClass.h[L.9,C.19] [LastDeclaration] @ ./Code2/inc/TestClass.h[L.11,C.13] [Constructor] TestClass @ ./Code2/inc/TestClass.h[L.12,C.17] [Destructor] ~TestClass @ ./Code2/inc/TestClass.h[L.13,C.17] [CXXMethod] PublicMethod @ ./Code2/inc/TestClass.h[L.15,C.22] [ParmDeclaration] a @ ./Code2/inc/TestClass.h[L.15,C.39] [ParmDeclaration] b @ ./Code2/inc/TestClass.h[L.15,C.46] [CXXMethod] GetA @ ./Code2/inc/TestClass.h[L.17,C.21] [CompoundStatement] @ ./Code2/inc/TestClass.h[L.17,C.28] [ReturnStatement] @ ./Code2/inc/TestClass.h[L.17,C.30] [UnexposedExpression] m_a @ ./Code2/inc/TestClass.h[L.17,C.43] [MemberReferenceExpression] m_a @ ./Code2/inc/TestClass.h[L.17,C.43] [CXXThisExpression] @ ./Code2/inc/TestClass.h[L.17,C.37] [CXXMethod] GetB @ ./Code2/inc/TestClass.h[L.18,C.21] [CompoundStatement] @ ./Code2/inc/TestClass.h[L.18,C.28] [ReturnStatement] @ ./Code2/inc/TestClass.h[L.18,C.30] [UnexposedExpression] m_b @ ./Code2/inc/TestClass.h[L.18,C.43] [MemberReferenceExpression] m_b @ ./Code2/inc/TestClass.h[L.18,C.43] [CXXThisExpression] @ ./Code2/inc/TestClass.h[L.18,C.37] [CXXMethod] GetResult @ ./Code2/inc/TestClass.h[L.19,C.21] [CompoundStatement] @ ./Code2/inc/TestClass.h[L.19,C.33] [ReturnStatement] @ ./Code2/inc/TestClass.h[L.19,C.35] [UnexposedExpression] m_result @ ./Code2/inc/TestClass.h[L.19,C.48] [MemberReferenceExpression] m_result @ ./Code2/inc/TestClass.h[L.19,C.48] [CXXThisExpression] @ ./Code2/inc/TestClass.h[L.19,C.42] [CXXMethod] SetA @ ./Code2/inc/TestClass.h[L.21,C.22] [ParmDeclaration] a @ ./Code2/inc/TestClass.h[L.21,C.31] [CompoundStatement] @ ./Code2/inc/TestClass.h[L.21,C.34] [BinaryOperator] @ ./Code2/inc/TestClass.h[L.21,C.36] [MemberReferenceExpression] m_a @ ./Code2/inc/TestClass.h[L.21,C.42] [CXXThisExpression] @ ./Code2/inc/TestClass.h[L.21,C.36] [UnexposedExpression] a @ ./Code2/inc/TestClass.h[L.21,C.48] [DeclarationReferenceExpression] a @ ./Code2/inc/TestClass.h[L.21,C.48] [CXXMethod] SetB @ ./Code2/inc/TestClass.h[L.22,C.22] [ParmDeclaration] b @ ./Code2/inc/TestClass.h[L.22,C.31] [CompoundStatement] @ ./Code2/inc/TestClass.h[L.22,C.34] [BinaryOperator] @ ./Code2/inc/TestClass.h[L.22,C.36] [MemberReferenceExpression] m_b @ ./Code2/inc/TestClass.h[L.22,C.42] [CXXThisExpression] @ ./Code2/inc/TestClass.h[L.22,C.36] [UnexposedExpression] b @ ./Code2/inc/TestClass.h[L.22,C.48] [DeclarationReferenceExpression] b @ ./Code2/inc/TestClass.h[L.22,C.48] [CXXMethod] SetResult @ ./Code2/inc/TestClass.h[L.23,C.22] [ParmDeclaration] result @ ./Code2/inc/TestClass.h[L.23,C.36] [CompoundStatement] @ ./Code2/inc/TestClass.h[L.23,C.44] [BinaryOperator] @ ./Code2/inc/TestClass.h[L.23,C.46] [MemberReferenceExpression] m_result @ ./Code2/inc/TestClass.h[L.23,C.52] [CXXThisExpression] @ ./Code2/inc/TestClass.h[L.23,C.46] [UnexposedExpression] result @ ./Code2/inc/TestClass.h[L.23,C.63] [DeclarationReferenceExpression] result @ ./Code2/inc/TestClass.h[L.23,C.63] [LastDeclaration] @ ./Code2/inc/TestClass.h[L.25,C.13] [CXXMethod] PrivateMethod @ ./Code2/inc/TestClass.h[L.26,C.21] [ParmDeclaration] a @ ./Code2/inc/TestClass.h[L.26,C.39] [ParmDeclaration] b @ ./Code2/inc/TestClass.h[L.26,C.46] [LastDeclaration] @ ./Code2/inc/TestClass.h[L.28,C.13] [FieldDeclaration] m_a @ ./Code2/inc/TestClass.h[L.29,C.21] [FieldDeclaration] m_b @ ./Code2/inc/TestClass.h[L.30,C.21] [FieldDeclaration] m_result @ ./Code2/inc/TestClass.h[L.31,C.21] [Namespace] root @ ./Code2/src/TestClass.cpp[L.3,C.11] [Namespace] sub1 @ ./Code2/src/TestClass.cpp[L.5,C.15] [Namespace] sub2 @ ./Code2/src/TestClass.cpp[L.7,C.19] [Constructor] TestClass @ ./Code2/src/TestClass.cpp[L.9,C.24] [TypeReference] class root::sub1::sub2::TestClass @ ./Code2/src/TestClass.cpp[L.9,C.13] [MemberReference] m_a @ ./Code2/src/TestClass.cpp[L.10,C.19] [IntegerLiteral] @ ./Code2/src/TestClass.cpp[L.10,C.23] [MemberReference] m_b @ ./Code2/src/TestClass.cpp[L.11,C.19] [IntegerLiteral] @ ./Code2/src/TestClass.cpp[L.11,C.23] [MemberReference] m_result @ ./Code2/src/TestClass.cpp[L.12,C.19] [IntegerLiteral] @ ./Code2/src/TestClass.cpp[L.12,C.28] [CompoundStatement] @ ./Code2/src/TestClass.cpp[L.13,C.13] [Destructor] ~TestClass @ ./Code2/src/TestClass.cpp[L.16,C.24] [TypeReference] class root::sub1::sub2::TestClass @ ./Code2/src/TestClass.cpp[L.16,C.13] [CompoundStatement] @ ./Code2/src/TestClass.cpp[L.17,C.13] [CXXMethod] PublicMethod @ ./Code2/src/TestClass.cpp[L.21,C.29] [TypeReference] class root::sub1::sub2::TestClass @ ./Code2/src/TestClass.cpp[L.21,C.18] [ParmDeclaration] a @ ./Code2/src/TestClass.cpp[L.21,C.46] [ParmDeclaration] b @ ./Code2/src/TestClass.cpp[L.21,C.53] [CompoundStatement] @ ./Code2/src/TestClass.cpp[L.22,C.13] [DeclarationStatement] @ ./Code2/src/TestClass.cpp[L.23,C.17] [VarDeclaration] result @ ./Code2/src/TestClass.cpp[L.23,C.21] [CallExpression] PrivateMethod @ ./Code2/src/TestClass.cpp[L.23,C.30] [MemberReferenceExpression] PrivateMethod @ ./Code2/src/TestClass.cpp[L.23,C.36] [CXXThisExpression] @ ./Code2/src/TestClass.cpp[L.23,C.30] [UnexposedExpression] a @ ./Code2/src/TestClass.cpp[L.23,C.50] [DeclarationReferenceExpression] a @ ./Code2/src/TestClass.cpp[L.23,C.50] [UnexposedExpression] b @ ./Code2/src/TestClass.cpp[L.23,C.53] [DeclarationReferenceExpression] b @ ./Code2/src/TestClass.cpp[L.23,C.53] [CallExpression] SetResult @ ./Code2/src/TestClass.cpp[L.25,C.17] [MemberReferenceExpression] SetResult @ ./Code2/src/TestClass.cpp[L.25,C.23] [CXXThisExpression] @ ./Code2/src/TestClass.cpp[L.25,C.17] [UnexposedExpression] result @ ./Code2/src/TestClass.cpp[L.25,C.33] [DeclarationReferenceExpression] result @ ./Code2/src/TestClass.cpp[L.25,C.33] [CXXMethod] PrivateMethod @ ./Code2/src/TestClass.cpp[L.28,C.28] [TypeReference] class root::sub1::sub2::TestClass @ ./Code2/src/TestClass.cpp[L.28,C.17] [ParmDeclaration] a @ ./Code2/src/TestClass.cpp[L.28,C.46] [ParmDeclaration] b @ ./Code2/src/TestClass.cpp[L.28,C.53] [CompoundStatement] @ ./Code2/src/TestClass.cpp[L.29,C.13] [CallExpression] SetA @ ./Code2/src/TestClass.cpp[L.30,C.17] [MemberReferenceExpression] SetA @ ./Code2/src/TestClass.cpp[L.30,C.23] [CXXThisExpression] @ ./Code2/src/TestClass.cpp[L.30,C.17] [UnexposedExpression] a @ ./Code2/src/TestClass.cpp[L.30,C.28] [DeclarationReferenceExpression] a @ ./Code2/src/TestClass.cpp[L.30,C.28] [CallExpression] SetB @ ./Code2/src/TestClass.cpp[L.31,C.17] [MemberReferenceExpression] SetB @ ./Code2/src/TestClass.cpp[L.31,C.23] [CXXThisExpression] @ ./Code2/src/TestClass.cpp[L.31,C.17] [UnexposedExpression] b @ ./Code2/src/TestClass.cpp[L.31,C.28] [DeclarationReferenceExpression] b @ ./Code2/src/TestClass.cpp[L.31,C.28] [ReturnStatement] @ ./Code2/src/TestClass.cpp[L.33,C.17] [BinaryOperator] @ ./Code2/src/TestClass.cpp[L.33,C.24] [UnexposedExpression] m_a @ ./Code2/src/TestClass.cpp[L.33,C.30] [MemberReferenceExpression] m_a @ ./Code2/src/TestClass.cpp[L.33,C.30] [CXXThisExpression] @ ./Code2/src/TestClass.cpp[L.33,C.24] [UnexposedExpression] m_b @ ./Code2/src/TestClass.cpp[L.33,C.42] [MemberReferenceExpression] m_b @ ./Code2/src/TestClass.cpp[L.33,C.42] [CXXThisExpression] @ ./Code2/src/TestClass.cpp[L.33,C.36] -------------------------------------- Invokation Tree: int root::sub1::sub2::TestClass::GetA() @ C:\Works\Test\Code2\inc\TestClass.h[L.17,C.21] int root::sub1::sub2::TestClass::GetB() @ C:\Works\Test\Code2\inc\TestClass.h[L.18,C.21] int root::sub1::sub2::TestClass::GetResult() @ C:\Works\Test\Code2\inc\TestClass.h[L.19,C.21] void root::sub1::sub2::TestClass::SetA(int) @ C:\Works\Test\Code2\inc\TestClass.h[L.21,C.22] void root::sub1::sub2::TestClass::SetB(int) @ C:\Works\Test\Code2\inc\TestClass.h[L.22,C.22] void root::sub1::sub2::TestClass::SetResult(int) @ C:\Works\Test\Code2\inc\TestClass.h[L.23,C.22] main() @ C:\Works\Test\Code2\src\main.cpp[L.7,C.5] root::sub1::sub2::TestClass::TestClass() @ C:\Works\Test\Code2\src\main.cpp[L.9,C.33] void root::sub1::sub2::TestClass::SetA(int) @ C:\Works\Test\Code2\src\main.cpp[L.14,C.5] void root::sub1::sub2::TestClass::SetB(int) @ C:\Works\Test\Code2\src\main.cpp[L.16,C.5] std::basic_ostream<char, std::char_traits<char> > & std::basic_ostream::operator<<(std::basic_ostream<char, std::char_traits<char> > &(*)(std::basic_ostream<char, std::char_traits<char> > &) __attribute__((cdecl))) @ C:\Works\Test\Code2\src\main.cpp[L.18,C.5] <Behavior Definition Not Found...> std::basic_ostream<char, std::char_traits<char> > & std::basic_ostream::operator<<(int) @ C:\Works\Test\Code2\src\main.cpp[L.18,C.5] <Behavior Definition Not Found...> std::operator<<<>(basic_ostream<char, std::char_traits<char> > &, const char *) @ C:\Works\Test\Code2\src\main.cpp[L.18,C.5] <Behavior Definition Not Found...> int root::sub1::sub2::TestClass::GetA() @ C:\Works\Test\Code2\src\main.cpp[L.18,C.28] std::basic_ostream<char, std::char_traits<char> > & std::basic_ostream::operator<<(std::basic_ostream<char, std::char_traits<char> > &(*)(std::basic_ostream<char, std::char_traits<char> > &) __attribute__((cdecl))) @ C:\Works\Test\Code2\src\main.cpp[L.20,C.5] <Behavior Definition Not Found...> std::basic_ostream<char, std::char_traits<char> > & std::basic_ostream::operator<<(int) @ C:\Works\Test\Code2\src\main.cpp[L.20,C.5] <Behavior Definition Not Found...> std::operator<<<>(basic_ostream<char, std::char_traits<char> > &, const char *) @ C:\Works\Test\Code2\src\main.cpp[L.20,C.5] <Behavior Definition Not Found...> int root::sub1::sub2::TestClass::GetB() @ C:\Works\Test\Code2\src\main.cpp[L.20,C.28] void root::sub1::sub2::TestClass::PublicMethod(int, int) @ C:\Works\Test\Code2\src\main.cpp[L.22,C.5] int root::sub1::sub2::TestClass::PrivateMethod(int, int) @ C:\Works\Test\Code2\src\TestClass.cpp[L.23,C.30] void root::sub1::sub2::TestClass::SetA(int) @ C:\Works\Test\Code2\src\TestClass.cpp[L.30,C.17] void root::sub1::sub2::TestClass::SetB(int) @ C:\Works\Test\Code2\src\TestClass.cpp[L.31,C.17] void root::sub1::sub2::TestClass::SetResult(int) @ C:\Works\Test\Code2\src\TestClass.cpp[L.25,C.17] int root::sub1::sub2::TestClass::GetResult() @ C:\Works\Test\Code2\src\main.cpp[L.24,C.14] std::basic_ostream<char, std::char_traits<char> > & std::basic_ostream::operator<<(std::basic_ostream<char, std::char_traits<char> > &(*)(std::basic_ostream<char, std::char_traits<char> > &) __attribute__((cdecl))) @ C:\Works\Test\Code2\src\main.cpp[L.26,C.5] <Behavior Definition Not Found...> std::basic_ostream<char, std::char_traits<char> > & std::basic_ostream::operator<<(int) @ C:\Works\Test\Code2\src\main.cpp[L.26,C.5] <Behavior Definition Not Found...> std::operator<<<>(basic_ostream<char, std::char_traits<char> > &, const char *) @ C:\Works\Test\Code2\src\main.cpp[L.26,C.5] <Behavior Definition Not Found...> RecursiveCallTest(int) @ C:\Works\Test\Code2\src\main.cpp[L.28,C.5] std::basic_ostream<char, std::char_traits<char> > & std::basic_ostream::operator<<(std::basic_ostream<char, std::char_traits<char> > &(*)(std::basic_ostream<char, std::char_traits<char> > &) __attribute__((cdecl))) @ C:\Works\Test\Code2\src\main.cpp[L.39,C.9] <Behavior Definition Not Found...> std::basic_ostream<char, std::char_traits<char> > & std::basic_ostream::operator<<(int) @ C:\Works\Test\Code2\src\main.cpp[L.39,C.9] <Behavior Definition Not Found...> std::operator<<<>(basic_ostream<char, std::char_traits<char> > &, const char *) @ C:\Works\Test\Code2\src\main.cpp[L.39,C.9] <Behavior Definition Not Found...> RecursiveCallTest(int) @ C:\Works\Test\Code2\src\main.cpp[L.41,C.9] <Recursive Call...> RecursiveCallTest(int) @ C:\Works\Test\Code2\src\main.cpp[L.31,C.6] std::basic_ostream<char, std::char_traits<char> > & std::basic_ostream::operator<<(std::basic_ostream<char, std::char_traits<char> > &(*)(std::basic_ostream<char, std::char_traits<char> > &) __attribute__((cdecl))) @ C:\Works\Test\Code2\src\main.cpp[L.39,C.9] <Behavior Definition Not Found...> std::basic_ostream<char, std::char_traits<char> > & std::basic_ostream::operator<<(int) @ C:\Works\Test\Code2\src\main.cpp[L.39,C.9] <Behavior Definition Not Found...> std::operator<<<>(basic_ostream<char, std::char_traits<char> > &, const char *) @ C:\Works\Test\Code2\src\main.cpp[L.39,C.9] <Behavior Definition Not Found...> RecursiveCallTest(int) @ C:\Works\Test\Code2\src\main.cpp[L.41,C.9] <Recursive Call...> root::sub1::sub2::TestClass::TestClass() @ C:\Works\Test\Code2\src\TestClass.cpp[L.9,C.24] root::sub1::sub2::TestClass::~TestClass() @ C:\Works\Test\Code2\src\TestClass.cpp[L.16,C.24] void root::sub1::sub2::TestClass::PublicMethod(int, int) @ C:\Works\Test\Code2\src\TestClass.cpp[L.21,C.29] int root::sub1::sub2::TestClass::PrivateMethod(int, int) @ C:\Works\Test\Code2\src\TestClass.cpp[L.23,C.30] void root::sub1::sub2::TestClass::SetA(int) @ C:\Works\Test\Code2\src\TestClass.cpp[L.30,C.17] void root::sub1::sub2::TestClass::SetB(int) @ C:\Works\Test\Code2\src\TestClass.cpp[L.31,C.17] void root::sub1::sub2::TestClass::SetResult(int) @ C:\Works\Test\Code2\src\TestClass.cpp[L.25,C.17] int root::sub1::sub2::TestClass::PrivateMethod(int, int) @ C:\Works\Test\Code2\src\TestClass.cpp[L.28,C.28] void root::sub1::sub2::TestClass::SetA(int) @ C:\Works\Test\Code2\src\TestClass.cpp[L.30,C.17] void root::sub1::sub2::TestClass::SetB(int) @ C:\Works\Test\Code2\src\TestClass.cpp[L.31,C.17] -------------------------------------- Cross References: root::sub1::sub2::TestClass::TestClass() [Call] @ C:\Works\Test\Code2\src\main.cpp[L.9,C.33] [Declaration] @ C:\Works\Test\Code2\inc\TestClass.h[L.12,C.17] [Definition] @ C:\Works\Test\Code2\src\TestClass.cpp[L.9,C.24] root::sub1::sub2::TestClass::~TestClass() [Declaration] @ C:\Works\Test\Code2\inc\TestClass.h[L.13,C.17] [Definition] @ C:\Works\Test\Code2\src\TestClass.cpp[L.16,C.24] void root::sub1::sub2::TestClass::PublicMethod(int, int) [Call] @ C:\Works\Test\Code2\src\main.cpp[L.22,C.5] [Declaration] @ C:\Works\Test\Code2\inc\TestClass.h[L.15,C.22] [Definition] @ C:\Works\Test\Code2\src\TestClass.cpp[L.21,C.29] int root::sub1::sub2::TestClass::GetA() [Call] @ C:\Works\Test\Code2\src\main.cpp[L.18,C.28] [Definition] @ C:\Works\Test\Code2\inc\TestClass.h[L.17,C.21] int root::sub1::sub2::TestClass::GetB() [Call] @ C:\Works\Test\Code2\src\main.cpp[L.20,C.28] [Definition] @ C:\Works\Test\Code2\inc\TestClass.h[L.18,C.21] int root::sub1::sub2::TestClass::GetResult() [Call] @ C:\Works\Test\Code2\src\main.cpp[L.24,C.14] [Definition] @ C:\Works\Test\Code2\inc\TestClass.h[L.19,C.21] void root::sub1::sub2::TestClass::SetA(int) [Call] @ C:\Works\Test\Code2\src\main.cpp[L.14,C.5] [Call] @ C:\Works\Test\Code2\src\TestClass.cpp[L.30,C.17] [Definition] @ C:\Works\Test\Code2\inc\TestClass.h[L.21,C.22] void root::sub1::sub2::TestClass::SetB(int) [Call] @ C:\Works\Test\Code2\src\main.cpp[L.16,C.5] [Call] @ C:\Works\Test\Code2\src\TestClass.cpp[L.31,C.17] [Definition] @ C:\Works\Test\Code2\inc\TestClass.h[L.22,C.22] void root::sub1::sub2::TestClass::SetResult(int) [Call] @ C:\Works\Test\Code2\src\TestClass.cpp[L.25,C.17] [Definition] @ C:\Works\Test\Code2\inc\TestClass.h[L.23,C.22] int root::sub1::sub2::TestClass::PrivateMethod(int, int) [Call] @ C:\Works\Test\Code2\src\TestClass.cpp[L.23,C.30] [Declaration] @ C:\Works\Test\Code2\inc\TestClass.h[L.26,C.21] [Definition] @ C:\Works\Test\Code2\src\TestClass.cpp[L.28,C.28] RecursiveCallTest(int) [Call] @ C:\Works\Test\Code2\src\main.cpp[L.28,C.5] [Call] @ C:\Works\Test\Code2\src\main.cpp[L.41,C.9] [Declaration] @ C:\Works\Test\Code2\src\main.cpp[L.5,C.6] [Definition] @ C:\Works\Test\Code2\src\main.cpp[L.31,C.6] main() [Definition] @ C:\Works\Test\Code2\src\main.cpp[L.7,C.5] std::basic_ostream<char, std::char_traits<char> > & std::basic_ostream::operator<<(std::basic_ostream<char, std::char_traits<char> > &(*)(std::basic_ostream<char, std::char_traits<char> > &) __attribute__((cdecl))) [Call] @ C:\Works\Test\Code2\src\main.cpp[L.18,C.5] [Call] @ C:\Works\Test\Code2\src\main.cpp[L.20,C.5] [Call] @ C:\Works\Test\Code2\src\main.cpp[L.26,C.5] [Call] @ C:\Works\Test\Code2\src\main.cpp[L.39,C.9] std::basic_ostream<char, std::char_traits<char> > & std::basic_ostream::operator<<(int) [Call] @ C:\Works\Test\Code2\src\main.cpp[L.18,C.5] [Call] @ C:\Works\Test\Code2\src\main.cpp[L.20,C.5] [Call] @ C:\Works\Test\Code2\src\main.cpp[L.26,C.5] [Call] @ C:\Works\Test\Code2\src\main.cpp[L.39,C.9] std::operator<<<>(basic_ostream<char, std::char_traits<char> > &, const char *) [Call] @ C:\Works\Test\Code2\src\main.cpp[L.18,C.5] [Call] @ C:\Works\Test\Code2\src\main.cpp[L.20,C.5] [Call] @ C:\Works\Test\Code2\src\main.cpp[L.26,C.5] [Call] @ C:\Works\Test\Code2\src\main.cpp[L.39,C.9]
あとがき
ClangNetを使って以下の情報を出力する機能を実現するサンプルを紹介しました。
- 関数コールツリー
- 関数クロスリファレンス(宣言/定義/コール)情報
作成したサンプルの全ソースはGitHubにおいてあります。
GitHub : Link
今回作成したサンプルには、テンプレート関数など対応していないケースがあるので、次回はそれらに対応できるように拡張していきたいと思います。
ClangNet(libclangの.NET(C#) Binding)によるC/C++ソースコードの解析 - その1
まえがき
LLVM/Clangに同梱されているlibclangの.NET(C#) Bindingライブラリ(ClangNet)を作成しました。
libclangはC/C++/Objective-Cソースコードの解析や自動補完機能を提供する高レベルのAPIで、オリジナルはC言語ベースのAPIが提供されています。
C言語のままだとかなり使い勝手が悪いため、.NETのP/Invoke機能(マネージドコードから動的ライブラリ内のネイティブコードを呼び出す仕組み)を用いてlibclangのWrapperを作成し、C#扱いやすいようにクラスベースのライブラリ化しました。
プロジェクト一式はGitHubにアップロードしています。
なお、以下の環境でビルドおよび簡易的な動作確認を行っています。
- Visual Studio 2019 Community
- Visual Studio for Mac
※ すべての機能に対するテストは出来ていませんので、不具合等ありましたらご連絡いただければと思います。
準備
ClangNetを使用するためには、LLVM/Clang内に同梱されているlibclang(.dllや.dylib)を取得する必要があります。
以下はWindowsの場合の例を記載します。
- 公式からWindows 64bit用のインストーラをダウンロード (現在はVer.8.0.1に対応しています)
- ダウンロードしたインストーラからLLVM/Clangをインストール
- LLVM/Clangインストール先からlibclang.dllを取得 (デフォルでは、"C:\Program Files\LLVM\bin\llvmclang.dll)
- ClangNetを使ったプログラムの実行ファイルと同じ場所にlibclang.dllを配置 (GitHubのサンプルでは、「ClangNet.CUI」プロジェクトをビルドして実行ファイルが生成されたディレクトリ内に配置します)
- ClangNetを使ったプログラムを実行
MacやLinuxの場合も同様にして、libclangのライブラリファイルを実行ファイルディレクトリ内に配置することで対応できると思います (拡張子は、Macの場合は.dylib、Linuxの場合は.soとなります)。
サンプル
今回は、基本的な使い方として、以下の2種類のサンプルを用意しました。
※ 下記に記載しているサンプルコードは、必要な部分のみに絞って記載していますので、GitHubのコードとは異なります。 動作可能なサンプルはGitHubから取得してください。
解析対象ソース
今回は解析対象ソースとして以下のような簡単なヘッダとソースファイルを用意しました。
[test.h]
int main(void);int Add(int a, int b);class TestClass{public:TestClass(){this->m_a = 0;this->m_b = 0;}TestClass(int a, int b);~TestClass(){}int Add();private:int m_a;int m_b;};
[test.cpp]
#include <iostream>#include "test.h"using namespace std;int Add(int a, int b){return a + b;}TestClass::TestClass(int a, int b){this->m_a = a;this->m_b = b;}int TestClass::Add(){return this->m_a + this->m_b;}int main(void){cout << "Hello, World" << endl;int a = 1;int b = 2;int c = Add(a, b);return 0;}
AST(Abstract Syntax Tree)の解析
using ClangNet;public void Parse(string src_path, // ソースファイルパスstring[] command_line_args, // clang実行時に指定するオプションTranslationUnitFlags options, // Translation Unit解析オプションbool display_diag) // ダイアグメッセージ表示フラグ{// インデックスを作成using(var index = Clang.CreateIndex(false, display_diag)){// ソースファイルをパースし、Translation Unit Objectを作成using(var tu = index.ParseTranslationUnit(src_path, command_line_args, new ClangUnsavedFile[0], options)){// Translation Unitのカーソルを取得var cursor = tu.Cursor;// Cursor VisitorとASTの深さ(Depth)を指定して、カーソルの子要素を探索cursor.VisitChildren(this.Visitor, 0);}}}private ChildVisitResult Visitor(ClangCursor cursor, // 現在のカーソルClangCursor parent, // 親カーソルint depth) // 現在の深さ{// カーソル位置を取得var loc = cursor.Location;// カーソルが解析対象ソースファイルの場合if (loc.IsFromMainFile == true){// 解析処理を呼び出しthis.VisitChild(cursor, depth);}else{/* システムインクルードヘッダ内 */if (loc.IsInSystemHeader == true){/* 無視 */}else{// 解析処理を呼び出しthis.VisitChild(cursor, depth);}}return ChildVisitResult.Continue;}private void VisitChild(ClangCursor cursor, int depth){// インデント用空白文字var indent = new string(' ', depth * 2);// カーソルの種別var kind = cursor.Kind;// カーソルが指す文字列var name = cursor.Spelling;// カーソル位置var loc = cursor.Location;// カーソルの実ファイル位置var floc = loc.FileLocation;// ファイル名var file = floc.File.FileName;// ファイル行番号var line = floc.Line;// ファイル列番号var col = floc.Column;// カーソル情報を出力Console.WriteLine($"{indent}[{kind}] {name} @ {file}[L.{line},C.{col}]");// Cursor VisitorとASTの深さ(現在のDepth + 1)を指定して、カーソルの子要素を探索cursor.VisitChildren(this.Visitor, depth + 1);}
実行結果は以下のようになります。
[FunctionDeclaration] main @ ./Code/inc/test.h[L.1,C.5] [FunctionDeclaration] Add @ ./Code/inc/test.h[L.2,C.5] [ParmDeclaration] a @ ./Code/inc/test.h[L.2,C.13] [ParmDeclaration] b @ ./Code/inc/test.h[L.2,C.20] [ClassDeclaration] TestClass @ ./Code/inc/test.h[L.4,C.7] [CXXAccessSpecifier] @ ./Code/inc/test.h[L.6,C.1] [Constructor] TestClass @ ./Code/inc/test.h[L.7,C.5] [CompoundStatement] @ ./Code/inc/test.h[L.8,C.5] [BinaryOperator] @ ./Code/inc/test.h[L.9,C.9] [MemberReferenceExpression] m_a @ ./Code/inc/test.h[L.9,C.15] [CXXThisExpression] @ ./Code/inc/test.h[L.9,C.9] [IntegerLiteral] @ ./Code/inc/test.h[L.9,C.21] [BinaryOperator] @ ./Code/inc/test.h[L.10,C.9] [MemberReferenceExpression] m_b @ ./Code/inc/test.h[L.10,C.15] [CXXThisExpression] @ ./Code/inc/test.h[L.10,C.9] [IntegerLiteral] @ ./Code/inc/test.h[L.10,C.21] [Constructor] TestClass @ ./Code/inc/test.h[L.13,C.5] [ParmDeclaration] a @ ./Code/inc/test.h[L.13,C.19] [ParmDeclaration] b @ ./Code/inc/test.h[L.13,C.26] [Destructor] ~TestClass @ ./Code/inc/test.h[L.15,C.5] [CompoundStatement] @ ./Code/inc/test.h[L.15,C.17] [CXXMethod] Add @ ./Code/inc/test.h[L.17,C.9] [CXXAccessSpecifier] @ ./Code/inc/test.h[L.19,C.1] [FieldDeclaration] m_a @ ./Code/inc/test.h[L.20,C.9] [FieldDeclaration] m_b @ ./Code/inc/test.h[L.21,C.9] [UsingDirective] @ ./Code/src/test.cpp[L.4,C.17] [NamespaceReference] std @ ./Code/src/test.cpp[L.4,C.17] [FunctionDeclaration] Add @ ./Code/src/test.cpp[L.6,C.5] [ParmDeclaration] a @ ./Code/src/test.cpp[L.6,C.13] [ParmDeclaration] b @ ./Code/src/test.cpp[L.6,C.20] [CompoundStatement] @ ./Code/src/test.cpp[L.7,C.1] [ReturnStatement] @ ./Code/src/test.cpp[L.8,C.5] [BinaryOperator] @ ./Code/src/test.cpp[L.8,C.12] [FirstExpression] a @ ./Code/src/test.cpp[L.8,C.12] [DeclarationReferenceExpression] a @ ./Code/src/test.cpp[L.8,C.12] [FirstExpression] b @ ./Code/src/test.cpp[L.8,C.16] [DeclarationReferenceExpression] b @ ./Code/src/test.cpp[L.8,C.16] [Constructor] TestClass @ ./Code/src/test.cpp[L.11,C.12] [TypeReference] class TestClass @ ./Code/src/test.cpp[L.11,C.1] [ParmDeclaration] a @ ./Code/src/test.cpp[L.11,C.26] [ParmDeclaration] b @ ./Code/src/test.cpp[L.11,C.33] [CompoundStatement] @ ./Code/src/test.cpp[L.12,C.1] [BinaryOperator] @ ./Code/src/test.cpp[L.13,C.5] [MemberReferenceExpression] m_a @ ./Code/src/test.cpp[L.13,C.11] [CXXThisExpression] @ ./Code/src/test.cpp[L.13,C.5] [FirstExpression] a @ ./Code/src/test.cpp[L.13,C.17] [DeclarationReferenceExpression] a @ ./Code/src/test.cpp[L.13,C.17] [BinaryOperator] @ ./Code/src/test.cpp[L.14,C.5] [MemberReferenceExpression] m_b @ ./Code/src/test.cpp[L.14,C.11] [CXXThisExpression] @ ./Code/src/test.cpp[L.14,C.5] [FirstExpression] b @ ./Code/src/test.cpp[L.14,C.17] [DeclarationReferenceExpression] b @ ./Code/src/test.cpp[L.14,C.17] [CXXMethod] Add @ ./Code/src/test.cpp[L.17,C.16] [TypeReference] class TestClass @ ./Code/src/test.cpp[L.17,C.5] [CompoundStatement] @ ./Code/src/test.cpp[L.18,C.1] [ReturnStatement] @ ./Code/src/test.cpp[L.19,C.5] [BinaryOperator] @ ./Code/src/test.cpp[L.19,C.12] [FirstExpression] m_a @ ./Code/src/test.cpp[L.19,C.18] [MemberReferenceExpression] m_a @ ./Code/src/test.cpp[L.19,C.18] [CXXThisExpression] @ ./Code/src/test.cpp[L.19,C.12] [FirstExpression] m_b @ ./Code/src/test.cpp[L.19,C.30] [MemberReferenceExpression] m_b @ ./Code/src/test.cpp[L.19,C.30] [CXXThisExpression] @ ./Code/src/test.cpp[L.19,C.24] [FunctionDeclaration] main @ ./Code/src/test.cpp[L.22,C.5] [CompoundStatement] @ ./Code/src/test.cpp[L.23,C.1] [CallExpression] operator<< @ ./Code/src/test.cpp[L.24,C.5] [CallExpression] operator<< @ ./Code/src/test.cpp[L.24,C.5] [DeclarationReferenceExpression] cout @ ./Code/src/test.cpp[L.24,C.5] [FirstExpression] operator<< @ ./Code/src/test.cpp[L.24,C.10] [DeclarationReferenceExpression] operator<< @ ./Code/src/test.cpp[L.24,C.10] [FirstExpression] @ ./Code/src/test.cpp[L.24,C.13] [StringLiteral] "Hello, World" @ ./Code/src/test.cpp[L.24,C.13] [FirstExpression] operator<< @ ./Code/src/test.cpp[L.24,C.28] [DeclarationReferenceExpression] operator<< @ ./Code/src/test.cpp[L.24,C.28] [FirstExpression] endl @ ./Code/src/test.cpp[L.24,C.31] [DeclarationReferenceExpression] endl @ ./Code/src/test.cpp[L.24,C.31] [DeclarationStatement] @ ./Code/src/test.cpp[L.26,C.5] [VarDeclaration] a @ ./Code/src/test.cpp[L.26,C.9] [IntegerLiteral] @ ./Code/src/test.cpp[L.26,C.13] [DeclarationStatement] @ ./Code/src/test.cpp[L.27,C.5] [VarDeclaration] b @ ./Code/src/test.cpp[L.27,C.9] [IntegerLiteral] @ ./Code/src/test.cpp[L.27,C.13] [DeclarationStatement] @ ./Code/src/test.cpp[L.29,C.5] [VarDeclaration] c @ ./Code/src/test.cpp[L.29,C.9] [CallExpression] Add @ ./Code/src/test.cpp[L.29,C.13] [FirstExpression] Add @ ./Code/src/test.cpp[L.29,C.13] [DeclarationReferenceExpression] Add @ ./Code/src/test.cpp[L.29,C.13] [FirstExpression] a @ ./Code/src/test.cpp[L.29,C.17] [DeclarationReferenceExpression] a @ ./Code/src/test.cpp[L.29,C.17] [FirstExpression] b @ ./Code/src/test.cpp[L.29,C.20] [DeclarationReferenceExpression] b @ ./Code/src/test.cpp[L.29,C.20] [ReturnStatement] @ ./Code/src/test.cpp[L.31,C.5] [IntegerLiteral] @ ./Code/src/test.cpp[L.31,C.12]
インクルードファイルの依存関係解析
using ClangNet;public void Parse(string src_path, // ソースファイルパスstring[] command_line_args, // clang実行時に指定するオプションTranslationUnitFlags options, // Translation Unit解析オプションbool display_diag) // ダイアグメッセージ表示フラグ{// インデックスを作成using(var index = Clang.CreateIndex(false, display_diag)){// ソースファイルをパースし、Translation Unit Objectを作成using(var tu = index.ParseTranslationUnit(src_path, command_line_args, new ClangUnsavedFile[0], options)){// Translation Unitのカーソルを取得var cursor = tu.Cursor;// Visitorを指定してインクルードファイルを再帰的に走査tu.GetInclusions(this.Visitor, IntPtr.Zero);}}}private void Visitor(ClangFile file, // 現在のファイルClangSourceLocation[] location_stack, // インクルード位置のスタックIntPtr client_data) // クライアントデータへのポインタ{// インクルードの深さvar depth = location_stack.Length;// インデント用空白文字var indent = new string(' ', depth * 2);// インクルードファイルのフルパスvar filename = Path.GetFullPath(file.FileName());// インクルードファイルパスの出力Console.WriteLine($"{indent}{filename}");}
実行結果は以下のようになります。
C:/Users/shin/source/repos/ClangNet/ClangNet.CUI/bin/Debug/netcoreapp2.1/Code/src/test.cpp C:/Program Files (x86)/Microsoft Visual Studio/2019/Community/VC/Tools/MSVC/14.22.27905/include/iostream C:/Program Files (x86)/Microsoft Visual Studio/2019/Community/VC/Tools/MSVC/14.22.27905/include/istream C:/Program Files (x86)/Microsoft Visual Studio/2019/Community/VC/Tools/MSVC/14.22.27905/include/ostream C:/Program Files (x86)/Microsoft Visual Studio/2019/Community/VC/Tools/MSVC/14.22.27905/include/ios C:/Program Files (x86)/Microsoft Visual Studio/2019/Community/VC/Tools/MSVC/14.22.27905/include/xlocnum C:/Program Files (x86)/Microsoft Visual Studio/2019/Community/VC/Tools/MSVC/14.22.27905/include/climits C:/Program Files (x86)/Microsoft Visual Studio/2019/Community/VC/Tools/MSVC/14.22.27905/include/yvals_core.h C:/Program Files (x86)/Microsoft Visual Studio/2019/Community/VC/Tools/MSVC/14.22.27905/include/crtdefs.h C:/Program Files (x86)/Microsoft Visual Studio/2019/Community/VC/Tools/MSVC/14.22.27905/include/vcruntime.h C:/Program Files (x86)/Microsoft Visual Studio/2019/Community/VC/Tools/MSVC/14.22.27905/include/sal.h C:/Program Files (x86)/Microsoft Visual Studio/2019/Community/VC/Tools/MSVC/14.22.27905/include/concurrencysal.h C:/Program Files (x86)/Microsoft Visual Studio/2019/Community/VC/Tools/MSVC/14.22.27905/include/vadefs.h C:/Program Files (x86)/Windows Kits/10/Include/10.0.18362.0/ucrt/corecrt.h C:/Program Files (x86)/Microsoft Visual Studio/2019/Community/VC/Tools/MSVC/14.22.27905/include/xkeycheck.h C:/Program Files (x86)/Microsoft Visual Studio/2019/Community/VC/Tools/MSVC/14.22.27905/include/limits.h C:/Program Files (x86)/Microsoft Visual Studio/2019/Community/VC/Tools/MSVC/14.22.27905/include/cmath C:/Program Files (x86)/Microsoft Visual Studio/2019/Community/VC/Tools/MSVC/14.22.27905/include/yvals.h C:/Program Files (x86)/Windows Kits/10/Include/10.0.18362.0/ucrt/crtdbg.h C:/Program Files (x86)/Microsoft Visual Studio/2019/Community/VC/Tools/MSVC/14.22.27905/include/vcruntime_new_debug.h C:/Program Files (x86)/Microsoft Visual Studio/2019/Community/VC/Tools/MSVC/14.22.27905/include/vcruntime_new.h C:/Program Files (x86)/Microsoft Visual Studio/2019/Community/VC/Tools/MSVC/14.22.27905/include/use_ansi.h C:/Program Files (x86)/Microsoft Visual Studio/2019/Community/VC/Tools/MSVC/14.22.27905/include/cstdlib C:/Program Files (x86)/Windows Kits/10/Include/10.0.18362.0/ucrt/math.h C:/Program Files (x86)/Windows Kits/10/Include/10.0.18362.0/ucrt/corecrt_math.h C:/Program Files (x86)/Windows Kits/10/Include/10.0.18362.0/ucrt/stdlib.h C:/Program Files (x86)/Windows Kits/10/Include/10.0.18362.0/ucrt/corecrt_malloc.h C:/Program Files (x86)/Windows Kits/10/Include/10.0.18362.0/ucrt/corecrt_search.h C:/Program Files (x86)/Windows Kits/10/Include/10.0.18362.0/ucrt/stddef.h C:/Program Files (x86)/Windows Kits/10/Include/10.0.18362.0/ucrt/corecrt_wstdlib.h C:/Program Files (x86)/Microsoft Visual Studio/2019/Community/VC/Tools/MSVC/14.22.27905/include/xtgmath.h C:/Program Files (x86)/Microsoft Visual Studio/2019/Community/VC/Tools/MSVC/14.22.27905/include/xtr1common C:/Program Files (x86)/Microsoft Visual Studio/2019/Community/VC/Tools/MSVC/14.22.27905/include/cstdio C:/Program Files (x86)/Windows Kits/10/Include/10.0.18362.0/ucrt/stdio.h C:/Program Files (x86)/Windows Kits/10/Include/10.0.18362.0/ucrt/corecrt_wstdio.h C:/Program Files (x86)/Windows Kits/10/Include/10.0.18362.0/ucrt/corecrt_stdio_config.h C:/Program Files (x86)/Microsoft Visual Studio/2019/Community/VC/Tools/MSVC/14.22.27905/include/streambuf C:/Program Files (x86)/Microsoft Visual Studio/2019/Community/VC/Tools/MSVC/14.22.27905/include/xiosbase C:/Program Files (x86)/Windows Kits/10/Include/10.0.18362.0/ucrt/share.h C:/Program Files (x86)/Windows Kits/10/Include/10.0.18362.0/ucrt/corecrt_share.h C:/Program Files (x86)/Microsoft Visual Studio/2019/Community/VC/Tools/MSVC/14.22.27905/include/system_error C:/Program Files (x86)/Microsoft Visual Studio/2019/Community/VC/Tools/MSVC/14.22.27905/include/cerrno C:/Program Files (x86)/Windows Kits/10/Include/10.0.18362.0/ucrt/errno.h C:/Program Files (x86)/Microsoft Visual Studio/2019/Community/VC/Tools/MSVC/14.22.27905/include/stdexcept C:/Program Files (x86)/Microsoft Visual Studio/2019/Community/VC/Tools/MSVC/14.22.27905/include/exception C:/Program Files (x86)/Microsoft Visual Studio/2019/Community/VC/Tools/MSVC/14.22.27905/include/type_traits C:/Program Files (x86)/Microsoft Visual Studio/2019/Community/VC/Tools/MSVC/14.22.27905/include/xstddef C:/Program Files (x86)/Microsoft Visual Studio/2019/Community/VC/Tools/MSVC/14.22.27905/include/cstddef C:/Program Files (x86)/Microsoft Visual Studio/2019/Community/VC/Tools/MSVC/14.22.27905/include/initializer_list C:/Program Files (x86)/Windows Kits/10/Include/10.0.18362.0/ucrt/malloc.h C:/Program Files (x86)/Microsoft Visual Studio/2019/Community/VC/Tools/MSVC/14.22.27905/include/vcruntime_exception.h C:/Program Files (x86)/Microsoft Visual Studio/2019/Community/VC/Tools/MSVC/14.22.27905/include/eh.h C:/Program Files (x86)/Windows Kits/10/Include/10.0.18362.0/ucrt/corecrt_terminate.h C:/Program Files (x86)/Microsoft Visual Studio/2019/Community/VC/Tools/MSVC/14.22.27905/include/xstring C:/Program Files (x86)/Microsoft Visual Studio/2019/Community/VC/Tools/MSVC/14.22.27905/include/iosfwd C:/Program Files (x86)/Microsoft Visual Studio/2019/Community/VC/Tools/MSVC/14.22.27905/include/cstring C:/Program Files (x86)/Windows Kits/10/Include/10.0.18362.0/ucrt/string.h C:/Program Files (x86)/Windows Kits/10/Include/10.0.18362.0/ucrt/corecrt_memory.h C:/Program Files (x86)/Windows Kits/10/Include/10.0.18362.0/ucrt/corecrt_memcpy_s.h C:/Program Files (x86)/Microsoft Visual Studio/2019/Community/VC/Tools/MSVC/14.22.27905/include/vcruntime_string.h C:/Program Files (x86)/Windows Kits/10/Include/10.0.18362.0/ucrt/corecrt_wstring.h C:/Program Files (x86)/Microsoft Visual Studio/2019/Community/VC/Tools/MSVC/14.22.27905/include/cwchar C:/Program Files (x86)/Windows Kits/10/Include/10.0.18362.0/ucrt/wchar.h C:/Program Files (x86)/Windows Kits/10/Include/10.0.18362.0/ucrt/corecrt_wconio.h C:/Program Files (x86)/Windows Kits/10/Include/10.0.18362.0/ucrt/corecrt_wctype.h C:/Program Files (x86)/Windows Kits/10/Include/10.0.18362.0/ucrt/corecrt_wdirect.h C:/Program Files (x86)/Windows Kits/10/Include/10.0.18362.0/ucrt/corecrt_wio.h C:/Program Files (x86)/Windows Kits/10/Include/10.0.18362.0/ucrt/corecrt_wprocess.h C:/Program Files (x86)/Windows Kits/10/Include/10.0.18362.0/ucrt/corecrt_wtime.h C:/Program Files (x86)/Windows Kits/10/Include/10.0.18362.0/ucrt/sys/stat.h C:/Program Files (x86)/Windows Kits/10/Include/10.0.18362.0/ucrt/sys/types.h C:/Program Files (x86)/Microsoft Visual Studio/2019/Community/VC/Tools/MSVC/14.22.27905/include/xmemory C:/Program Files (x86)/Microsoft Visual Studio/2019/Community/VC/Tools/MSVC/14.22.27905/include/cstdint C:/Program Files (x86)/Microsoft Visual Studio/2019/Community/VC/Tools/MSVC/14.22.27905/include/stdint.h C:/Program Files (x86)/Microsoft Visual Studio/2019/Community/VC/Tools/MSVC/14.22.27905/include/limits C:/Program Files (x86)/Microsoft Visual Studio/2019/Community/VC/Tools/MSVC/14.22.27905/include/cfloat C:/Program Files (x86)/Windows Kits/10/Include/10.0.18362.0/ucrt/float.h C:/Program Files (x86)/Microsoft Visual Studio/2019/Community/VC/Tools/MSVC/14.22.27905/include/new C:/Program Files (x86)/Microsoft Visual Studio/2019/Community/VC/Tools/MSVC/14.22.27905/include/xatomic.h C:/Program Files (x86)/Microsoft Visual Studio/2019/Community/VC/Tools/MSVC/14.22.27905/include/intrin0.h C:/Program Files (x86)/Microsoft Visual Studio/2019/Community/VC/Tools/MSVC/14.22.27905/include/xutility C:/Program Files (x86)/Microsoft Visual Studio/2019/Community/VC/Tools/MSVC/14.22.27905/include/utility C:/Program Files (x86)/Microsoft Visual Studio/2019/Community/VC/Tools/MSVC/14.22.27905/include/xcall_once.h C:/Program Files (x86)/Microsoft Visual Studio/2019/Community/VC/Tools/MSVC/14.22.27905/include/xerrc.h C:/Program Files (x86)/Microsoft Visual Studio/2019/Community/VC/Tools/MSVC/14.22.27905/include/xlocale C:/Program Files (x86)/Microsoft Visual Studio/2019/Community/VC/Tools/MSVC/14.22.27905/include/memory C:/Program Files (x86)/Microsoft Visual Studio/2019/Community/VC/Tools/MSVC/14.22.27905/include/typeinfo C:/Program Files (x86)/Microsoft Visual Studio/2019/Community/VC/Tools/MSVC/14.22.27905/include/vcruntime_typeinfo.h C:/Program Files (x86)/Microsoft Visual Studio/2019/Community/VC/Tools/MSVC/14.22.27905/include/xfacet C:/Program Files (x86)/Microsoft Visual Studio/2019/Community/VC/Tools/MSVC/14.22.27905/include/xlocinfo C:/Program Files (x86)/Microsoft Visual Studio/2019/Community/VC/Tools/MSVC/14.22.27905/include/xlocinfo.h C:/Program Files (x86)/Windows Kits/10/Include/10.0.18362.0/ucrt/ctype.h C:/Program Files (x86)/Windows Kits/10/Include/10.0.18362.0/ucrt/locale.h C:/Users/<user>/source/repos/ClangNet/ClangNet.CUI/bin/Debug/netcoreapp2.1/Code/inc/test.h
Windows用libclangでは、システムインクルードディレクトリとして、デフォルトでVisual StudioとWindows Kitsが設定されるようです。
同じコードをMacで動かすと、システムインクルードの方は何も出力されないため、command_line_argsに-I オプションでインクルードパスを指定する必要があります。
あとがき
今後は、もう少し詳しい使い方を紹介していきます。