An Embedded Engineer’s Blog

とある組み込みエンジニアの備忘録的なブログです。

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_MACSOCK
        this->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_MACSOCK
        this->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_MACSOCK
        this->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_MACSOCK
        this->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】

  1. UDP送信用ソケットを開く(★)
  2. キーボードからの送信メッセージ入力を受け付ける
  3. キーボードから入力された送信メッセージをReceiverに送信する
  4. キーボードから"exit"が入力されると、ソケットを閉じてプログラムを終了する


【Receiver】

  1. UDP受信用ソケットを開く(★)
  2. Senderからのメッセージ受信を待機する
  3. メッセージを受信したら内容を表示する
  4. 受信メッセージが"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を使ってソケット通信を実装します。
MacLinuxで一部インタフェースが異なる部分があるため、その部分に関してはコンパイルスイッチ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_MACSOCK
        this->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_MACSOCK
        this->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】

  1. UDPユニキャスト送信用ソケット(IP:localhost Port:5000)を開く
  2. キーボードからの送信メッセージ入力を受け付ける
  3. キーボードから入力された送信メッセージをReceiverに送信する
  4. キーボードから"exit"が入力されると、ソケットを閉じてプログラムを終了する


【Receiver】

  1. UDPユニキャスト受信用ソケット(Port:5000)を開く
  2. Senderからのメッセージ受信を待機する
  3. メッセージを受信したら内容を表示する
  4. 受信メッセージが"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つの情報を出力する機能を実装します。

  1. 関数コールツリー
  2. 関数クロスリファレンス(宣言/定義/コール)情報

ClangNetのインストールや使用方法については過去記事をご参照ください。

an-embedded-engineer.hateblo.jp


解析対象ソース

まずは、今回解析対象にするC/C++ソースコードを用意します。
今回は以下の種類の関数を解析対象にします。

解析対象にするソースのサンプルを以下に示します。
解析対象ソースの詳しい説明は割愛します。


[TestClass.h]

#pragma once

namespace 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ステップで実現します。

  1. Translation Unit(ソースファイル)解析
  2. Invokation Tree(関数コールツリー)生成
  3. Cross Reference(関数参照情報)生成

1.のTranslation Unit(ソースファイル)解析では、ClangNetの構文解析機能を使って各ソースファイルのAST(抽象構文木)を辿っていき、以下のような情報を収集します。

種別 意味 保持する情報 備考
Translation Unit情報 変換対象(ソースファイル)ごとに保持する情報 ソースファイルパス、宣言・定義されているBehavior情報マップ
Behavior情報マップ Behavior(関数)の宣言・定義位置をキーにした、Behavior情報のハッシュマップ Key : 関数宣言・定義位置、Value : Behavior情報
Behavior情報 種別(宣言/定義)、関数宣言、名前空間、ソース位置、呼び出している関数のInvokation情報リスト
Invokation情報 呼び出している関数名、ソース位置、参照している関数宣言のBehavior情報
  1. のInvokation Tree(関数コールツリー)生成では、1.で収集した情報をもとに、関数定義内で呼び出している関数を再帰的に辿っていきます。

  2. の 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を使って以下の情報を出力する機能を実現するサンプルを紹介しました。

  1. 関数コールツリー
  2. 関数クロスリファレンス(宣言/定義/コール)情報

作成したサンプルの全ソースはGitHubにおいてあります。
GitHub : Link

今回作成したサンプルには、テンプレート関数など対応していないケースがあるので、次回はそれらに対応できるように拡張していきたいと思います。

ClangNet(libclangの.NET(C#) Binding) Ver.1.1.0リリース

リリースノート

ClangNetのVer1.1.0をリリースしました。

変更点: 1. LLVM/Clang Ver.9.0.0に対応 2. いくつかの不具合を修正


ダウンロード先&インストール方法

libclang.dllやClangNetは以下からダウンロードしてください。


LLVM/Clang Ver.9.0.0 : Link

GitHub : Link

NuGet : Link


インストール方法や使い方は以下の記事を参照してください。

an-embedded-engineer.hateblo.jp

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にアップロードしています。


なお、以下の環境でビルドおよび簡易的な動作確認を行っています。

※ すべての機能に対するテストは出来ていませんので、不具合等ありましたらご連絡いただければと思います。


準備

ClangNetを使用するためには、LLVM/Clang内に同梱されているlibclang(.dllや.dylib)を取得する必要があります。
以下はWindowsの場合の例を記載します。

  1. 公式からWindows 64bit用のインストーラをダウンロード (現在はVer.8.0.1に対応しています)
  2. ダウンロードしたインストーラからLLVM/Clangをインストール
  3. LLVM/Clangインストール先からlibclang.dllを取得 (デフォルでは、"C:\Program Files\LLVM\bin\llvmclang.dll)
  4. ClangNetを使ったプログラムの実行ファイルと同じ場所にlibclang.dllを配置 (GitHubのサンプルでは、「ClangNet.CUI」プロジェクトをビルドして実行ファイルが生成されたディレクトリ内に配置します)
  5. ClangNetを使ったプログラムを実行

MacLinuxの場合も同様にして、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 StudioWindows Kitsが設定されるようです。
同じコードをMacで動かすと、システムインクルードの方は何も出力されないため、command_line_argsに-I オプションでインクルードパスを指定する必要があります。


あとがき

今後は、もう少し詳しい使い方を紹介していきます。

Python学習メモ - その8

まえがき

最近、Python を勉強し始めたので、その学習メモです。

今回はディクショナリ(Dictionary)についてです。


ディクショナリ(Dictionary)

ディクショナリはユニーク(一意)なキーとそれに対応する値を格納するコレクションです。
例えば、IDとそれに対応する顧客名など、重複する可能性のある値(顧客名)を別の重複しないキー(ID)で管理したい場合などに使用します。


ディクショナリ(単一型)

ディクショナリはキーと値のセット(要素)をコロン(:)で表し、複数の要素をカンマ( , )で区切り、前後に中かっこ「{ }」をつけることでディクショナリとして認識されます。

dict_values = {"A": 1, "B": 2, "C": 3, "D": 4}
print(dict_values)

実行結果

{'A': 1, 'B': 2, 'C': 3, 'D': 4}


ディクショナリ(複数型)

一意の値であれば、異なる型のデータをキーに使用することができます。
また、キーに対する値にも異なる型を格納することができます。

dict_values = {"A": 1, "B": 3.14, 3.5: (1 + 2j), "4": 4}
print(dict_values)

実行結果

{'A': 1, 'B': 3.14, 3.5: (1+2j), '4': 4}


重複定義

ディクショナリの定義時に、重複したキーで初期化した場合、最後にセットされたキーの値が有効となります(最後の値で上書きされる)。

dict_values = {"A": 1, "B": 2, "C": 3, "D": 4, "A": 5}
print(dict_values)

実行結果

{'A': 5, 'B': 2, 'C': 3, 'D': 4}


値の取得(エラーあり)

ディクショナリに対して、キーを指定することで値を取得することができます。
この方法では、存在しないキーを指定すると実行エラーとなります。

dict_values = {"A": 1, "B": 2, "C": 3}
print(dict_values)
print(dict_values["A"])
print(dict_values["B"])
print(dict_values["C"])
# print(dict_values["D"])  # 実行エラー

実行結果

{'A': 1, 'B': 2, 'C': 3}
1
2
3


値の取得(エラーなし or デフォルト値)

getメソッドを使用することでも、キーを指定した値の取得が可能です。
この場合、存在しないキーを指定するとNoneが返されます。
また、存在しないキーを指定した場合に返されるデフォルト値を指定することもできます。

dict_values = {"A": 1, "B": 2, "C": 3}
print(dict_values)
print(dict_values.get("A"))
print(dict_values.get("B"))
print(dict_values.get("C"))
print(dict_values.get("D"))
print(dict_values.get("D", "Unknown Key"))

実行結果

{'A': 1, 'B': 2, 'C': 3}
1
2
3
None
Unknown Key


値の変更

キーを指定して、値を格納することで、値を変更することができます。

dict_values = {"A": 1, "B": 2, "C": 3}
print(dict_values)
dict_values["B"] = 4
print(dict_values)

実行結果

{'A': 1, 'B': 2, 'C': 3}
{'A': 1, 'B': 4, 'C': 3}


空ディクショナリ

空(要素数0)のディクショナリを作成したい場合には、「{}」のみを記述します。

dict_values = {}
print(dict_values)

実行結果

{}


追加

存在しないキーを指定して、値を格納することで要素を追加することができます。

dict_values = {}
dict_values["A"] = 1
dict_values["B"] = 2
dict_values["C"] = 3

print(dict_values)

実行結果

{'A': 1, 'B': 2, 'C': 3}


値の削除

キーを指定してdel演算子を使用することで指定したキーの要素を削除することができます。

dict_values = {"A": 1, "B": 2, "C": 3}

del dict_values["B"]

print(dict_values)

実行結果

{'A': 1, 'C': 3}


値の取り出し(キー指定)

popメソッドを使用することで、指定したキーに対応する値を取り出し(取得 & 削除)することができます。
また、存在しないキーを指定した場合に返されるデフォルト値を指定することもできます。
デフォルト値を指定しないで存在しないキーを指定した場合は、実行エラーとなります。

dict_values = {"A": 1, "B": 2, "C": 3}

pop_value = dict_values.pop("B")
print(pop_value)

pop_value = dict_values.pop("D", "Not Found")
print(pop_value)

# pop_value = dict_values.pop("D")  # 実行エラー

print(dict_values)

実行結果

2
Not Found
{'A': 1, 'C': 3}


キー&値の取り出し

popitemメソッドを使用することで、最後に追加された要素から順番に要素(キー&値)を取り出し(取得&削除)することができます。
空のディクショナリに対してpopitemメソッドを使用すると実行エラーとなります。

dict_values = {"A": 1, "B": 2, "C": 3}
pop_value = dict_values.popitem()
print(pop_value)
pop_value = dict_values.popitem()
print(pop_value)
pop_value = dict_values.popitem()
print(pop_value)
# pop_value = dict_values.popitem() # 実行エラー

実行結果

('C', 3)
('B', 2)
('A', 1)


クリア

ディクショナリをクリアする場合には、clearメソッドを使用します。

dict_values = {"A": 1, "B": 2, "C": 3}
dict_values.clear()
print(dict_values)

実行結果

{}


デフォルト指定セット

setdefaultメソッドを指定すると、以下のような処理を行うことができます。
1. すでに存在するキーを指定した場合は、そのキーに対応する値を返す 2. 存在しないキーとデフォルト値を指定した場合は、そのキーとデフォルト値で要素を追加する 3. 存在しないキーのみを指定した場合は、そのキーとNoneで要素を追加する

dict_values = {"A": 1, "B": 2, "C": 3}
set_value = dict_values.setdefault("A", 4)
print(set_value)
set_value = dict_values.setdefault("D", 4)
print(set_value)
set_value = dict_values.setdefault("E")
print(set_value)
print(dict_values)

実行結果

1
4
None
{'A': 1, 'B': 2, 'C': 3, 'D': 4, 'E': None}


更新

updateメソッドを使用することで、ディクショナリを更新することができます。
複数の要素を一気に変更したい場合に便利です。

dict_values = {"A": 1, "B": 2, "C": 3}
dict_values.update([("B", 4), ("D", 4)])
dict_values.update(D=5, E=6)
print(dict_values)

実行結果

{'A': 1, 'B': 4, 'C': 3, 'D': 5, 'E': 6}


全要素数取得

ディクショナリ全体の要素数を取得したい場合はlen関数を使用します。

dict_values = {"A": 1, "B": 2, "C": 3}
print(len(dict_values))

実行結果

3


コピー

ディクショナリの複製を作成したい場合は、copyメソッドを使用します。

src_dict_values = {"A": 1, "B": 2, "C": 3}
dst_dict_values1 = src_dict_values
dst_dict_values2 = src_dict_values.copy()

print(src_dict_values)
print(dst_dict_values1)
print(dst_dict_values2)

print()

src_dict_values["B"] = 4
dst_dict_values2["D"] = 4

print(src_dict_values)
print(dst_dict_values1)
print(dst_dict_values2)

実行結果

{'A': 1, 'B': 2, 'C': 3}
{'A': 1, 'B': 2, 'C': 3}
{'A': 1, 'B': 2, 'C': 3}

{'A': 1, 'B': 4, 'C': 3}
{'A': 1, 'B': 4, 'C': 3}
{'A': 1, 'B': 2, 'C': 3, 'D': 4}


キーの存在確認

ディクショナリ内に特定のキーを持つ要素が存在しているかを確認する場合には、in演算子を使用します。

dict_values = {"A": 1, "B": 2, "C": 3}
print("B" in dict_values)

実行結果

True


逆に、ディクショナリの中に特定のキーを持つ要素が含まれていないことを確認したい場合は、not in 演算子を使用します。

dict_values = {"A": 1, "B": 2, "C": 3}
print("D" not in dict_values)

実行結果

True


キー一覧(View Object)の取得

ディクショナリに格納されている要素のキー一覧を取得したい場合は、keysメソッドを使用します。
keysメソッドから返されるオブジェクトはView Objectとなります。
View Objectは動的なビューを提供し、ディクショナリの要素が変更された場合にその変更内容が自動的に反映されます。

dict_values = {"A": 1, "B": 2, "C": 3}
keys = dict_values.keys()
print(keys)
dict_values["D"] = 4
print(keys)
print(list(keys))

実行結果

dict_keys(['A', 'B', 'C'])
dict_keys(['A', 'B', 'C', 'D'])
['A', 'B', 'C', 'D']


値一覧(View Object)の取得

ディクショナリに格納されている要素の値一覧を取得したい場合は、valuesメソッドを使用します。
valuesメソッドから返されるオブジェクトもkeysメソッドと同様View Objectとなります。

dict_values = {"A": 1, "B": 2, "C": 3}
values = dict_values.values()
print(values)
dict_values["D"] = 4
print(values)
print(list(values))

実行結果

dict_values([1, 2, 3])
dict_values([1, 2, 3, 4])
[1, 2, 3, 4]


要素一覧(View Object)の取得

ディクショナリに格納されている要素一覧を取得したい場合は、itemsメソッドを使用します。
itemsメソッドから返されるオブジェクトもkeysメソッドやvaluesメソッドと同様View Objectとなります。

dict_values = {"A": 1, "B": 2, "C": 3}
items = dict_values.items()
print(items)
dict_values["D"] = 4
print(items)
print(list(items))

実行結果

dict_items([('A', 1), ('B', 2), ('C', 3)])
dict_items([('A', 1), ('B', 2), ('C', 3), ('D', 4)])
[('A', 1), ('B', 2), ('C', 3), ('D', 4)]


参考文献

Python 公式リファレンス

https://docs.python.org/ja/3.7/index.html

Python-izm

https://www.python-izm.com/

ゲームを作りながら楽しく学べるPythonプログラミング

エキスパートPythonプログラミング

エキスパートPythonプログラミング 改訂2版 (アスキードワンゴ)

エキスパートPythonプログラミング 改訂2版 (アスキードワンゴ)