概要
Qt 6.7以降、Qt OPC UAには qopcuaxmldatatypes2cpp というデータ型ジェネレータが付属している。
このツールは、OPC UA仕様で定義されたデータ構造を記述したBSDファイル (.bsd形式) から、Qt OPC UAと完全に互換性のあるC++のコードを自動生成する強力なコマンドラインツールである。
このジェネレータの主要な機能は、BSDファイルに含まれる列挙型(enum)と構造化型(structured)から、対応するC++の列挙型とデータクラスを生成することである。
これにより、OPC UAサーバやクライアントアプリケーションの開発において、データ型定義を手動でコーディングする手間を大幅に削減することができる。
また、副次的な機能として、CSVファイルに記述されたノードID情報から、型安全なC++の列挙型を生成することもできる。
これにより、OPC UAのノード識別子をプログラム内で扱う時に、文字列リテラルではなく型チェックされた列挙値を使用できるようになる。
コマンドライン引数
このジェネレータはコマンドラインから実行するツールであり、様々な引数を通じて動作を細かく制御できる。
--input (-i)
このオプションは、データ型を生成する元となるBSDファイルを指定する。
指定されたBSDファイルに含まれる全ての列挙型と構造体型について、対応するC++コードが生成される。
複数のOPC UAモデルから同時にコードを生成したい場合は、このオプションを複数回使用することができる。
例えば、独自のカスタムモデルと標準的な業界モデルの両方を扱うアプリケーションを開発する場合、それぞれのBSDファイルに対して個別に-iオプションを指定する。
--dependencyinput (-d)
このオプションは、依存関係にあるBSDファイルを指定するために使用する。
OPC UAのモデル設計では、あるモデルが別のモデルで定義された型を参照することがよくある。
重要な点として、このオプションで指定されたBSDファイルからは、--inputで指定された主要なBSDファイルの構造体によって実際に必要とされる型のみが生成される。
つまり、依存関係ファイル内の全ての型ではなく、実際に使用されている型だけが選択的に生成されるため、不要なコードの生成を避けることができる。
複数のモデルへの依存関係がある複雑なプロジェクトでは、このオプションを複数回使用して、必要な全ての依存モデルを指定できる。
--nodeids (-n)
このオプションは、ノードID列挙型を生成するためのCSVファイルを指定する。
使用方法は、<モデル名>:<CSVファイルのパス> という形式で記述する。
例えば、MyModel:/path/to/nodeids.csv と指定すると、enum class MyModelNodeId という名前の列挙型が生成される。
この列挙型には、CSVファイルに記述された各ノードIDが列挙値として含まれる。
複数のモデルに対してノードID列挙型を生成したい場合は、このオプションを複数回指定することができる。
これにより、各モデルごとに専用の列挙型が作成され、名前空間の衝突を避けながら型安全にノードIDを扱うことができる。
--output (-o)
このオプションは、生成されたC++ファイル (ヘッダファイルと実装ファイル) を配置する出力ディレクトリを指定する。
指定されたディレクトリが存在しない場合は、自動的に作成される。
通常、ビルドディレクトリ内の専用フォルダを出力先として指定することで、ソースコードと生成コードを明確に分離できる。
--prefix (-p)
このオプションは、生成される全てのファイル名、列挙型名、クラス名の先頭に付加される接頭辞を指定する。
デフォルト値は、GeneratedOpcUa である。
接頭辞を適切に設定することで、生成されたコードと手書きのコードを容易に区別できるようになる。
また、複数のモデルから異なる接頭辞でコードを生成することで、名前の衝突を防ぐこともできる。
--bundle (-b)
このオプションを指定すると、バンドルファイルが生成される。
<接頭辞>datatypes.h と <接頭辞>datatypes.cpp という2つのファイルが作成される。
これらのバンドルファイルは、生成された全ての個別ファイルを #include する便利なラッパとして機能する。
これにより、アプリケーションコードからは、個々の生成ファイルを個別にインクルードする代わりに、
このバンドルファイル1つをインクルードするだけで、全ての生成された型にアクセスできるようになる。
生成されるファイルの詳細
ジェネレータを実行すると、入力ファイルと指定されたオプションに応じて、以下のファイル群が生成される。
列挙型ヘッダーファイル
全ての列挙型を含む1つのヘッダーファイルが生成される。
このファイル内では、列挙型は専用の名前空間内に定義され、他のコードとの名前衝突を防ぐ。
OPC UAで定義された列挙型は、C++11以降のenum classとして実装され、型安全性が保証される。
構造化型のファイル
各構造化型 (OPC UAのstructured) ごとに、個別のヘッダファイルと実装ファイルのペアが生成される。
ヘッダファイルにはクラス定義が、実装ファイルにはメンバ関数の実装が記述される。
これらのクラスは、Qt OPC UAのフレームワークと統合されており、シリアライゼーションやデシリアライゼーションを自動的にサポートする。
エンコード・デコードファイル
OPC UAのバイナリプロトコルでデータを送受信するためのエンコードメソッドとデコードメソッドを含む、1つのヘッダファイルと1つの実装ファイルが生成される。
これらのメソッドにより、生成されたC++データ構造とOPC UAのワイヤーフォーマット(ネットワーク上のバイナリ形式)間の変換が可能になる。
ノードID列挙型ファイル
--nodeids (-n) オプションが少なくとも1回指定された場合、ノードID列挙型を定義する1つのヘッダファイルが生成される。
このファイルには、CSVファイルで指定された各ノードIDに対応する列挙値が含まれ、プログラム内でノードを参照する際に文字列の代わりに型安全な列挙値を使用できる。
バンドルファイル
--bundle (-b) オプションが指定された場合、「<接頭辞>datatypes.h」と「<接頭辞>datatypes.cpp」という2つのバンドルファイルが生成される。
これらのファイルは、上記の全ての生成ファイルを #include するため、
アプリケーション開発者はこのバンドルファイル1つをインクルードするだけで、生成された全ての型と機能にアクセスできる。
CMakeへの統合
Qt OPC UAのデータ型ジェネレータは、CMakeビルドシステムと緊密に統合されている。
qt_opcua_generate_datatypes() という専用のCMake関数を使用することで、ビルドプロセスの一部としてコード生成を自動化できる。
この関数は、1つまたは複数の入力BSDファイルと、必要に応じて依存関係BSDファイルを引数として受け取り、内部的にqopcuaxmldatatypes2cppツールを呼び出す。
生成されたソースファイルとヘッダファイルは、指定された OUTPUT_DIR ディレクトリに書き込まれ、自動的に関数の第1引数として指定されたCMakeターゲットに追加される。
※注意
OUTPUT_DIRディレクトリ、またはその親ディレクトリを、ターゲットのインクルードディレクトリに明示的に追加する必要がある。
これを行わないと、コンパイラが生成されたヘッダファイルを見つけることができず、ビルドエラーが発生する。
CMake統合の実例
以下のCMakeLists.txtのコード例は、実際のプロジェクトでコードジェネレータを統合する方法を段階的に示している。
まず、Qt6のCoreモジュールとOpcUaモジュールを必須コンポーネントとして検索する。
これらのモジュールは、生成されたコードが依存する基本的な機能を提供する。
find_package(Qt6 REQUIRED COMPONENTS Core OpcUa)
qt_standard_project_setup()
次に、実行可能ファイルのターゲットを作成する。
この例では my_codegen という名前のターゲットが、mycodegen.cpp というソースファイルから作成される。
qt_add_executable(my_codegen
mycodegen.cpp
)
ここで、qt_opcua_generate_datatypes() 関数を呼び出してコード生成を設定する。
この関数には複数のパラメータを指定する必要がある。
qt_opcua_generate_datatypes(
my_codegen # 生成されたファイルを追加するターゲット
INPUT_BSD "${CMAKE_CURRENT_SOURCE_DIR}/mymodel.bsd"
INPUT_BSD "${CMAKE_CURRENT_SOURCE_DIR}/myothermodel.bsd"
DEPENDENCY_BSD "${CMAKE_CURRENT_SOURCE_DIR}/mydependency.bsd"
INPUT_CSV_MAP "MyModel:${CMAKE_CURRENT_SOURCE_DIR}/mymodel.csv"
INPUT_CSV_MAP "MyOtherModel:${CMAKE_CURRENT_SOURCE_DIR}/myothermodel.csv"
PREFIX "GeneratedOpcUa"
OUTPUT_DIR "${CMAKE_CURRENT_BINARY_DIR}/generated"
)
このコードの最初の引数 my_codegen は、生成されたファイルを追加する対象のターゲットを指定する。
- INPUT_BSD パラメータ
- 複数回使用することで複数のBSDファイルからコードを生成できる。
- この例では、mymodel.bsd と myothermodel.bsd という2つのモデルファイルが指定されている。
- DEPENDENCY_BSD パラメータ
- 任意(オプション)で、依存関係のあるモデルを指定する。
- この例では、mydependency.bsd という依存モデルファイルが指定されている。
- INPUT_CSV_MAP パラメータ
- ノードID列挙型を生成するためのCSVファイルをマッピングする。
- 形式は
<モデル名>:<ファイルパス>で、この例では2つの異なるモデルに対してCSVファイルが指定されている。
- PREFIX パラメータ
- 必須項目であり、生成されるファイルの接頭辞を指定する。
- この例では GeneratedOpcUa という接頭辞が使用されている。
- OUTPUT_DIR パラメータ
- 必須項目であり、生成されたファイルの出力先ディレクトリを指定する。
- ビルドディレクトリ内の generated というサブディレクトリが指定されている。
重要な点として、INPUT_BSD と INPUT_CSV_MAP のうち、少なくともどちらか一方は1回以上指定する必要がある。
両方を省略するとエラーになる。
最後に、生成されたヘッダファイルをコンパイラが見つけられるように、インクルードディレクトリを設定し、必要なライブラリをリンクする。
target_include_directories(my_codegen PRIVATE
${CMAKE_CURRENT_BINARY_DIR}
)
target_link_libraries(my_codegen PRIVATE
Qt6::Core
Qt6::OpcUa
)
target_include_directories() では、ビルドディレクトリをインクルードパスに追加している。
これにより、OUTPUT_DIR で指定した generated ディレクトリ内のヘッダファイルがインクルード可能になる。
target_link_libraries() では、Qt6::Core と Qt6::OpcUa ライブラリをリンクしている。
これらのライブラリは、生成されたコードが正しく機能するために必要な基盤を提供する。
生成されたファイルの使用方法
コード生成が完了し、プロジェクトがビルドされると、生成されたC++コードを自分のソースファイルから使用できるようになる。
使用方法は非常にシンプルで、データ型とエンコーダ・デコーダクラス用のトップレベルヘッダファイルをインクルードするだけである。
インクルード方法
生成されたコードを使用するには、目的に応じて以下のヘッダファイルをインクルードする。
#include <generated/generatedopcuabinarydeencoder.h> // INPUT_BSDが設定された場合に使用
#include <generated/generatedopcuadatatypes.h> // INPUT_BSDが設定された場合に使用
#include <generated/generatedopcuanodeids.h> // INPUT_CSV_MAPが設定された場合に使用
最初のヘッダファイル generatedopcuabinarydeencoder.h は、OPC UAのバイナリプロトコルでデータをエンコード・デコードするための機能を提供する。
このファイルは、INPUT_BSD パラメータを使用してBSDファイルからコードを生成した場合に生成される。
2番目のヘッダファイル generatedopcuadatatypes.h は、生成された全てのデータ型(列挙型とクラス)の定義を含む。
これも INPUT_BSD パラメータが使用された場合に生成される。
3番目のヘッダファイル generatedopcuanodeids.h は、ノードID列挙型の定義を含む。
このファイルは、INPUT_CSV_MAP パラメータを使用してCSVファイルからノードID列挙型を生成した場合にのみ生成される。
これらのヘッダファイルをインクルードすることで、OPC UA仕様に準拠した型安全なC++コードとして、データ型を扱うことができる。
手動でシリアライゼーションコードを書く必要はなく、生成されたエンコーダ・デコーダが自動的にデータ変換を処理してくれる。
利点と活用シーン
このコード生成ツールを使用することで、OPC UAアプリケーション開発における多くの利点が得られる。
第1に、開発時間の大幅な短縮である。
OPC UAのデータ型定義から手動でC++クラスを作成する作業は、時間がかかるだけでなく、エラーが発生しやすい作業である。
このツールを使用すれば、仕様書に基づいて正確なコードが自動的に生成される。
第2に、メンテナンス性の向上である。
OPC UAモデルが更新された場合、BSDファイルを更新してコード生成を再実行するだけで、C++コードも自動的に更新される。
手動での変更箇所を探す必要がない。
第3に、型安全性の保証である。
生成されたコードは、C++の型システムを最大限に活用しており、コンパイル時に多くのエラーを検出できる。
特にノードID列挙型により、文字列リテラルの代わりに型チェックされた値を使用できるため、実行時エラーのリスクが減少する。