用語集
こちらは [USD/C++] ArPathmapResolverを作ろう [Github] の記事の補助として用語の説明をまとめた記事になります
CMakeやUSDでのマクロや、オプションについてできるだけ説明しています
Cmake
Environment
CMAKE_GENERATOR
プロジェクトのビルドに使用するジェネレータを指定
コマンドラインの -G
とほぼ同等
// コマンドライン
cmake -G "Visual Studio 16 2019"
cmake -G "Unix Makefiles"
//環境変数の場合
set CMAKE_GENERATOR="Visual Studio 16 2019"
export CMAKE_GENERATOR="Unix Makefiles"
変数の方にも CMAKE_GENERATOR
はあるがそちらは 非推奨
Variables
CMAKE_<LANG>_COMPILER
<LANG> 用のコンパイラをフルパスで指定します
set(CMAKE_CXX_COMPILER "/usr/local/bin/gcc")
set(CMAKE_CXX_COMPILER "C:\MinGW\bin\gcc.exe")
set(CMAKE_CXX_COMPILER "C:\Program Files (x86)\Microsoft Visual Studio\2019\Community\VC\Tools\MSVC\14.29.30133\bin\Hostx64\x64\cl.exe")
CMAKE_BUILD_TYPE
ジェネレーターのビルドタイプを指定します
set(CMAKE_BUILD_TYPE Debug) # または Release
Debug : 最適化なし、デバッグ情報あり
Release : 最適化あり、デバッグ情報なし
RelWithDebInfo : 最適化あり、デバッグ情報あり
MinSizeRel : 最小サイズで最適化、デバッグ情報なし
CMAKE_BUILD_TYPE
は project()
よりも前に記述する必要があります
CMAKE_VERBOSE_MAKEFILE
ビルドプロセス中に実行されるコマンドを詳細に表示させる変数
set(CMAKE_VERBOSE_MAKEFILE ON)
CMAKE_BINARY_DIR / CMAKE_CURRENT_BINARY_DIR
CMakeでビルドしているディレクトリを返す変数
CMAKE_BINARY_DIR
はトップレベルのディレクトのパスを返します
add_subdirectory()
によって追加されたディレクトリを取得する場合は CMAKE_CURRENT_BINARY_DIR
を使用します
CMAKE_INSTALL_PREFIX
install()
コマンドを使用した時のインストールディレクトリを指定
set(CMAKE_INSTALL_PREFIX "/path/to/install")
CMAKE_EXPORT_COMPILE_COMMANDS
コンパイルコマンドを compile_commands.json
というファイルにエクスポートするためのオプションです
set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
今回はVisual Studio Codeでのコード補完やインテリセンスを使用したいのでONにしています
CMAKE_MESSAGE_LOG_LEVEL
message()
コマンドで使用されるロギング・レベルを指定します
STATUS
がデフォルトとして設定されています
set(CMAKE_MESSAGE_LOG_LEVEL "DEBUG")
ERROR
WARNING
NOTICE
STATUS
VERBOSE
DEBUG
TRACE
CMAKE_CXX_STANDARD
CMakeで使用するC++コンパイラの標準を指定するための変数
# C++17を使用する設定
set(CMAKE_CXX_STANDARD 17)
CMAKE_CXX_STANDARD_REQUIRED
指定した C++ がサポートされていない場合に、ビルドを継続するか、エラーを発生させるかを決定します。
True
に設定すると C++ がサポートしていない場合にエラーが発生します
set(CMAKE_CXX_STANDARD_REQUIRED TRUE)
WIN32 / LINUX
OSを識別するための変数
他にもあるので、こちらも確認
LINUX
はCMake 3.25から追加されました
if(WIN32)
# Windows向けの設定
message("Building on Windows")
elseif(LINUX)
# Linux向けの設定
message("Building on Linux")
else()
# その他のプラットフォーム向けの設定
message("Building on an unknown platform")
endif()
BUILD_SHARED_LIBS
共有ライブラリ(.dll .soなど)をビルドするか、静的ライブラリ(.aや.libなど)をビルドするかを制御するためのオプション
set(BUILD_SHARED_LIBS ON)
Commnads
cmake_minimum_required
プロジェクトに必要な cmake の最小バージョンを設定します
FATAL_ERROR
オプションを使用すると、指定したバージョン未満の場合はビルドを中止します
cmake_minimum_required(VERSION 3.25 FATAL_ERROR)
cmake_minimum_required
は project()
よりも前に記述する必要があります
project
プロジェクトの名前を設定します
プロジェクト名や言語設定を明確にするためにも必ず設定
# LANGUAGES : 言語を明示的に指定
# VERSION : プロジェクトにバージョン情報を付ける
# DESCRIPTION : プロジェクトの簡単な説明を指定
# HOMEPAGE_URL : プロジェクトのホームページやリポジトリのURLを指定
# e.g.
project(Hoge VERSION 1.0.0 LANGUAGE C++)
message
指定されたメッセージテキストをログに表示します
DEBUG
などを表示する場合は CMAKE_MESSAGE_LOG_LEVEL
を設定します
# STATUS : 通常の情報を表示
# NOTICE : STATUSよりも強調したい場合に使用
# VERBOSE : より詳細な情報を表示するために使用
# DEBUG : 開発者向けのメッセージを表示
# TRACE : 細かいメッセージ
# WARNING : 警告を表示
# AUTHOR_WARNING : 開発者向けの警告メッセージを表示
# SEND_ERROR : エラーメッセージを表示。処理を続行
# FATAL_ERROR : エラーメッセージを表示。処理を停止
# DEPRECATED : 非推奨の機能や方法を使っている場合に警告を表示
message(DEBUG "This is a debug message.")
set
変数に値を設定したり、リストや環境変数なども設定できます
# 通常の変数
set(MY_VAR "Hello!")
# リストの設定
set(MY_LIST "list1" "list2" "list3")
# 環境変数の設定
set(ENV{MY_ENV_VAR} "env")
# 子階層から上位のスコープ(親階層など)に変数を設定
set(MY_VAR "value" PARENT_SCOPE)
# CMakeキャッシュに値を設定する
# これで設定した場合はコマンドラインなどから上書きできる
set(MY_VAR "default_value" CACHE STRING "custom variable")
# cmake -D MY_VAR="new_value"
add_executable
指定されたソースファイルを使ってプロジェクトに実行ファイルを追加するコマンド
add_executable(my_program main.cpp)
add_library
指定されたソースファイルを使ってプロジェクトにライブラリを追加するコマンド
下記オプションの指定ができます
add_library(mylibrary SHARED src/hoge.cpp src/fuga.cpp)
STATIC : 静的ライブラリ(.a や.lib)
SHARED : 動的ライブラリ(.soや.dll)
MODULE : 動的ライブラリに近いが、dlopenのような機能を使ってロードされる事を前提としたもの
add_subdirectory
CMakeでサブディレクトリを追加するためのコマンド
このコマンドを使うことで、サブディレクトリ内で定義された CMakeLists.txt
をメインのビルドに組み込むことができます
# hoge
# ├── CMakeLists.txt #1
# └── src
# └── CMakeLists.txt #2
add_subdirectory("src") #2を#1に組み込む事ができる
add_dependencies
ターゲット間の依存関係を設定するために使用するコマンド
設定をする事で、別のターゲットのビルドが完了するまで自身のビルドを行わないようにする事ができます
add_executable(MyApp main.cpp)
add_library(MyLibrary STATIC my_library.cpp)
# MyApp に MyLibrary をリンク
target_link_libraries(MyApp MyLibrary)
# 依存関係の追加
# MyLibraryに依存しているので、MyAppをビルドする前にMyLibraryが先にビルドされます
add_dependencies(MyApp MyLibrary)
add_compile_options / target_compile_options
CMakeでコンパイラのオプションを追加するためのコマンド
特定のターゲットに対して設定する場合は
target_compile_options
を使用する
add_compile_definitions / target_compile_definitions
CMakeでコンパイラのマクロ定義を設定するためのコマンド
特定のターゲットに対して設定する場合は
target_compile_definitions
を使用する
link_directories / target_link_directories
CMakeでリンクするライブラリの検索パスを指定するためのコマンド
特定のターゲットに対して設定する場合は
target_link_directories
を使用する
link_directories
は指定されたディレクトリ内のすべてのライブラリをリンクする可能性があるので 非推奨
link_libraries / target_link_libraries
CMakeでリンクするライブラリを指定するためのコマンド
特定のターゲットに対して設定する場合は
target_link_libraries
を使用する
target_link_directories
でディレクトリを指定している場合は lib
と 拡張子を除いた名前で記述できます、フルパスの場合はそのまま記述
# libhoge.so (target_link_directoriesでディレクトリを指定済み)
# libfuga.so
target_link_libraries(<TARGET>
PRIVATE
hoge
/path/to/lib/libfuga.so
)
link_libraries
は全てのターゲットに影響するので 非推奨
include_directories / target_include_directories
CMakeでヘッダーファイルの検索パスを指定するためのコマンド
特定のターゲットに対して設定する場合は
target_include_directories
を使用する
include_directories
は全てのターゲットに影響するので 非推奨
target_〜の影響範囲
- PRIVATE : ターゲット内部でのみ有効
- PUBLIC : ターゲットとそのリンク先のターゲットに有効
- INTERFACE : ターゲット自体には影響しないが、リンクした別のターゲットに影響
set_target_properties
特定のターゲットに対して、プロパティを指定するために使用するコマンド
例えば、ライブラリのプリフィックスを上書きする場合は下記で指定できる
set (MY_LIB "hoge")
add_library(${MY_LIB} SHARED fuga.cpp)
set_target_properties(${MY_LIB} PROPERTIES PREFIX "")
# libhoge.so -> hoge.so
プロパティの種類は他にもあるので、こちらを確認
configure_file
入力ファイルの内容を変換しながら input
ファイルを output
ファイルにコピーするコマンド
{
"var1" : "@FOO@",
"var2" : "@BAR@"
}
cmake_minimum_required(VERSION 3.10)
project(MyProject VERSION 1.0)
# 変数のセット
set(FOO "hoge")
set(BAR "fuga" )
# configure_file を使って config.json.in を処理
configure_file(
${CMAKE_SOURCE_DIR}/config.json.in # 入力ファイル(テンプレート)
${CMAKE_BINARY_DIR}/config.json # 出力ファイル
@ONLY # 変数の置換を @VAR@ 形式の参照に制限する
)
{
"var1" : "hoge",
"var2" : "fuga"
}
今回は plugInfo.json.in
を CMakeLists.txt
でセットした変数へと置き換えるために使用しています
install
ビルド後に生成されたファイル等を、指定したディレクトリへとインストールするために使用するコマンド
install(TARGETS <target> DESTINATION <dir>)
TARGETS : ビルドしたターゲットをインストール
FILES : 単一または複数のファイルをインストール
DERECTORY : ディレクトリ内のファイルとサブディレクトリを再帰的にインストール
DESTINATION : インストール先のディレクトリパス(絶対パスや相対パス
オプションは他にもあるので、こちらを確認
Definitions
NOMINMAX
windef.h
やwindows.h
では、min
と max
のマクロが定義されており、
それらのマクロが std::min
や std::max
というC++の標準ライブラリ関数などと衝突してしまう可能性があるため
#define NOMINMAX
を使ってマクロを無効化しています
WIN32_LEAN_AND_MEAN
不要なWindows APIからヘッダーを除外することで、ビルド時間が短縮されます
またC++でUSDのコードを書こう(準備編)でも記載されているように、それらのヘッダーで定義されたマクロとの衝突を避けるためにも
#define WIN32_LEAN_AND_MEAN
を定義しています
_GLIBCXX_USE_CXX11_ABI
C++11 ABIを使用するライブラリと古いABIを使用するライブラリ間でバイナリの互換性がなくなるため、古いライブラリと新しいライブラリが混在していると、実行時に問題が発生することもあるので、DCCで使用されているABIにあわせて定義しています
#define _GLIBCXX_USE_CXX11_ABI 1
BOOST_ALL_NO_LIB
Boostの自動リンクの機能を無効にします
これを指定しない場合、Boostはリンクの必要がないヘッダーオンリーライブラリを使う場合でも、ライブラリを自動的にリンクしようとします。
不必要なライブラリのリンクを行わない為にも
#define BOOST_ALL_NO_LIB
を定義しています
自動リンクを有効にするためにはコンパイラが #pragma comment(lib, ...)
をサポートしている必要があるので、主にMSVCでのコンパイル時に定義します
HBOOST_ALL_NO_LIB
Houdiniで展開されているboostはプリフィックスに h が付くので
Houdiniからboostを使用する場合はマクロも H を付ける必要があります
Compiler Options
/Zc:inline
このオプションを有効にすると、参照されていない変数や関数などのシンボルを
コンパイラは「未使用」と見なし、最適化によって削除します
MSVC 2019 以降、このオプションがデフォルトで有効になっています
なぜ無効(/Zc:inline-)にするのか
attributes.hで定義されている ARCH_CONSTRUCTOR
ARCH_DESTRUCTOR
というマクロで使われているシンボルが/Zc:inline
によって未使用と判断されてしまうため、
そのシンボルが削除されないように /Zc:inline-
で無効にする必要があります
具体的には
# define ARCH_CONSTRUCTOR(_name, _priority, ...) \
static void _name(__VA_ARGS__); \
namespace { \
__declspec(allocate(".pxrctor")) \
extern const Arch_ConstructorEntry \
_ARCH_CAT_NOEXPAND(arch_ctor_, _name) = { \
reinterpret_cast<Arch_ConstructorEntry::Type>(&_name), \
static_cast<unsigned>(PXR_VERSION), \
_priority \
}; \
} \
_ARCH_ENSURE_PER_LIB_INIT(Arch_ConstructorInit, _archCtorInit); \
static void _name(__VA_ARGS__)
この部分の namespace{}
で囲まれている _ARCH_CAT_NOEXPAND(arch_ctor_, _name)
というシンボルが削除されるようです
VS2017では回避できていたみたいですが、VS2019以降ではextern
を付けてもエラーになるとの事
/OPT:NOREF
/OPT:NOREF
を有効にする事でも、参照されない関数とデータが保持されるので
/Zc:inline-
の代わりとして指定する事もできます
/w
コンパイル時に警告メッセージを表示しないようにする
-fPIC
動的リンクライブラリ(.so)を作成する際に主に使用されるオプション
-Wno-deprecated
非推奨機能の警告を無効にする
-Wno-deprecated-declarations
非推奨宣言の警告を無効にする
-Wno-changes-meaning
意味が変わる可能性のある変更に関する警告を無効にする
USD
Macros
PXR_NAMESPACE_OPEN_SCOPE / PXR_NAMESPACE_CLOSE_SCOPE
名前空間を開閉するためのマクロです。ビルドによって変わりますが、pxr.h では
pxrInternal_v0_24_11__pxrReserved__
という名前空間で展開されています
#define PXR_NS pxr
#define PXR_INTERNAL_NS pxrInternal_v0_24_11__pxrReserved__
#define PXR_NS_GLOBAL ::PXR_NS
namespace PXR_INTERNAL_NS { }
// The root level namespace for all source in the USD distribution.
namespace PXR_NS {
using namespace PXR_INTERNAL_NS;
}
#define PXR_NAMESPACE_OPEN_SCOPE namespace PXR_INTERNAL_NS {
#define PXR_NAMESPACE_CLOSE_SCOPE }
#define PXR_NAMESPACE_USING_DIRECTIVE using namespace PXR_NS;
のように定義されており PXR_INTERNAL_NS
は
PXR_NS
の名前空間ではusing ディレクティブが使用されています
PXR_NAMESPACE_USING_DIRECTIVE
using namespace PXR_NS
というディレクティブを挿入します
これによって名前空間のメンバーをコード内で簡単に使用する事ができます
#include <pxr/base/tf/token.h>
PXR_NS::TfToken("hoge");
#include <pxr/base/tf/token.h>
PXR_NAMESPACE_USING_DIRECTIVE
TfToken("hoge");
AR_API / AR_LOCAL / ARCH_IMPORT / ARCH_EXPORT / ARCH_HIDDEN
ライブラリのシンボルをエクスポートしたり、インポートする時の挙動を制御するためのマクロ
具体的にはコンパイラやOSに応じて
__declspec(dllexport) // Export
__declspec(dllimport) // Import
// Windows
__attribute__((dllexport)) // Export
__attribute__((dllimport)) // Import
// Unix
__attribute__((visibility("default"))) //Export
__attribute__((visibility("hidden"))) //Hide
などのアトリビュートを設定します
Windowsではシンボルのエクスポートとインポートを明示的に記述する必要があり、Unix系では可視性でシンボルを管理するので、外部から使用されたくない場合は __attribute__((visibility("hidden")))
を設定します
AR_DEFINE_RESOLVER(ResolverClass
, BaseClass...
)
指定したリゾルバーを登録するためのマクロ
第一引数に登録するリゾルバー、それ以降の引数に基底クラスを指定します
defineResolver.hをみてみると、
TF_REGISTRY_FUNCTION(TfType)
マクロ内で、TfType::Define
を使って TfType
を定義、その後 SetFactory
でリゾルバーを登録しているみたいです
- ソース内で使う事が想定されています
-
pluginfo.json
の基底クラスも合わせないと、読み込み時にエラーとなる可能性があります
AR_DECLARE_RESOLVER_CONTEXT(ContextObject
)
指定された ContextObject
が ArResolverContext
のコンテキストオブジェクトとして使用できることを宣言するマクロ
マクロが展開されるとArIsContextObject<ContextObject>
の構造体が定義され、コンテキストとして使用できることを示すために value
メンバを true
に設定しています
この構造体はResolverの変更を通知するための ArNotice
クラスで使用されています
ArIsContextObject<ContextObject>::value //true
ContextObject
が宣言されているヘッダー内で使用します
TF_WRAP(x
)
Boost.Pythonライブラリを使用して、C++のクラスや関数を
Pythonで使用できるようにラップするための関数を宣言、実行するためのマクロ
例えば TF_WRAP(Hoge)
とした場合、下記のように展開されます
ARCH_HIDDEN void wrapHoge();
wrapHoge()
wrap〜という関数の実装は別途行う必要があります
TF_WRAP_MODULE
TF_WRAP
で展開された関数を処理するための WrapModule()
という関数を定義します。TF_WRAP
は基本的にこのマクロ内で使用します
具体的には
TF_WRAP_MODULE
{
TF_WRAP(Hoge);
TF_WRAP(Fuga);
}
というマクロがこのように展開されます
static void WrapModule()
{
ARCH_HIDDEN void wrapHoge(); wrapHoge();
ARCH_HIDDEN void wrapFuga(); wrapFuga();
}
そしてWrapModule
関数はPyInit_<module_name>
という
Boost.Pythonで拡張モジュールを初期化する際に使用される関数内で呼び出されています
TF_DEBUG(enumVal
)
enumVal
が有効かどうかを確認するためのマクロ
有効であれば TF_DEBUG(enumVal).Msg(...)
でデバッグメッセージを表示する事ができます。
また、enumVal
は TF_DEBUG_CODES()
のマクロで定義されている必要があります
if (!TfDebug::IsEnabled(enumVal)) /* empty */ ; else TfDebug::Helper()
展開されると上記の式が評価され、enumVal
が有効であれば TfDebug::Helper
という構造体を返します
TF_DEBUG_CODES(...
)
TF_DEBUG
で使用するための、デバッグシンボルを定義するためのマクロ
このマクロを使用する事でデバッグシンボルを列挙した enum
型を定義したり、
デバッグシンボルに関する構造体や関数などが定義されます。
TF_DEBUG_CODES(
MY_E1,
MY_E2
);
というマクロがこのように展開されます
// 列挙型の名前は 最初の引数に__DebugCodesを付けた名前が定義されます
enum MY_E1__DebugCodes {
MY_E1,
MY_E2,
MY_E1__DebugCodes__PAST_END //内部的に使用される定数
};
// デバッグコードの情報を格納した構造体
template <>
struct TfDebug::_Traits<MY_E1__DebugCodes> {
static constexpr bool IsDeclared = true;
static constexpr int NumCodes = MY_E1__DebugCodes__PAST_END;
static constexpr bool CompileTimeEnabled = true;
};
//
inline char const *
Tf_DebugGetEnumName(MY_E1__DebugCodes val) {
constexpr char const *CStrings[] = {
"MY_E1",
"MY_E2",
"MY_E1__DebugCodes__PAST_END"
};
return CStrings[static_cast<int>(val)];
};
TF_DEBUG
環境変数に TF_DEBUG_REGISTRY
を設定することで挙動を確認できます
TF_DEBUG_ENVIRONMENT_SYMBOL(VAL
, descrip
)
デバッグシンボルにデバッグ内容の説明文を指定することができるマクロ
このマクロを定義しなくても、TF_DEBUG
でのデバッグ自体は有効
というのも、TF_DEBUG_ENVIRONMENT_SYMBOL
では Tf_DebugSymbolRegistry
を使ってデバッグシンボルを登録しますが、
TF_DEBUG
の環境変数が設定されていたり、TfDebug::SetDebugSymbolsByName
などの関数を実行したタイミングでも初期化されるため、TF_DEBUG_ENVIRONMENT_SYMBOL
で登録しなくても TF_DEBUG
自体は効きます
- ヘッダーではなく、ソースファイル内の
TF_REGISTRY_FUNCTION(TfDebug)
マクロの中で使うことが推奨されています - 登録するデバッグシンボルは、
TF_DEBUG_CODES()
のマクロで定義されている必要があります
TF_REGISTRY_FUNCTION(TfDebug) {
TF_DEBUG_ENVIRONMENT_SYMBOL(MY_E1, "loading of blah-blah files");
TF_DEBUG_ENVIRONMENT_SYMBOL(MY_E2, "parsing of mdl code");
// etc.
}
TF_REGISTRY_FUNCTION(KEY_TYPE
)
レジストリ(TfDebug
などの型)に関数を登録するためのマクロ
登録した関数を呼び出す時はTfRegistryManager::SubscribeTo<KEY_TYPE>()
を使います
これによって、SubscribeTo<KEY_TYPE>()
で指定された型の関数のみが実行されるので、不要な呼び出しを避ける事ができます
// 登録
TF_REGISTRY_FUNCTION(KEY_TYPE)
{
//ここに処理を入れる
}
// 呼び出し
TfRegistryManager::GetInstance().SubscribeTo<KEY_TYPE>()
TF_DEBUG
環境変数に TF_DISCOVERY_TERSE
を設定することで挙動を確認できます
KEY_TYPEはテンプレート化や、名前空間で修飾されていない必要があります
// ダメな例 : テンプレート
template <typename T>
class Hoge {};
TF_REGISTRY_FUNCTION(Hoge<int>){}
// ダメな例 : Namespace
namespace Hoge {
class Fuga {};
}
TF_REGISTRY_FUNCTION(Hoge::Fuga){}
もし名前空間を使用している場合は、using namespace
を使って名前空間を指定した後、TF_REGISTRY_FUNCTION
を使用します
TF_PP_STRINGIZE(x
)
x
を文字列に変換します
例えば TfToken
のコンストラクタでマクロを使いたい場合、TF_PP_STRINGIZE
を使用して文字列へと変換する事ができます
#define HOGE fuga
TfToken(TF_PP_STRINGIZE(HOGE)); // TfToken("fuga")
MFB_PACKAGE_NAME / MFB_ALT_PACKAGE_NAME / MFB_PACKAGE_MODULE
USDで内部的に使われているマクロ。
Cmakeの pxr_plugin(NAME)
関数からビルドする場合は
PXR_PACKAGE
という変数を設定するだけでよい
手動で設定する場合は、
MFB_PACKAGE_NAME
MFB_ALT_PACKAGE_NAME
はライブラリの名前、
MFB_PACKAGE_MODULE
は MFB_PACKAGE_NAME
の頭文字を大文字にした名前を設定
主に下記で使われています
-
TfAutoMallocTag2
でメモリを管理する時のタグ -
TfRegistryManager
でライブラリのコンストラクター、デストラクタを実行 - Pythonでモジュールを作成する時の名前
Classes
TfRegistryManager
プログラム起動時にライブラリが自動的に初期化される仕組みを、効率的に管理するためのクラス
ライブラリの初期化を必要な時に呼び出せるので、
スレッドセーフに処理したり、不要な初期化を避ける事ができます
静的コンストラクタ
プログラム起動時に初期化する方法として、
静的メソッドや静的変数がプログラム開始時に初期化されるという特性を活かして、
静的コンストラクタに近い動作をさせるテクニックがあります。
#include <iostream>
class MyClass {
public:
static void initialize() {
std::cout << "initialize!\n";
}
};
struct Initializer {
Initializer() { MyClass::initialize(); }
};
static Initializer forceInit;
int main() {
std::cout << "main!\n";
}
initialize!
main!
静的変数 forceInit
のインスタンスが作成されると、Initializer
のコンストラクタが呼び出され、その中で MyClass::initialize()
が実行されます
このように書く事で静的コンストラクタに近い動作を実現することができました
TfRegistryManagerによる拡張
ただ、そのまま使用するといくつか問題があるので、TfRegistryManager
によってそれらの問題を解決しています
- 各OSで初期化のタイミングが不確定
- 複数のライブラリがあった時の依存関係の管理が難しい
- 無駄な初期化が発生する
- スレッドセーフではない可能性がある
基本的には上記の静的コンストラクタやコンパイラで用意されている特別なアトリビュートを使用していますが、
※GCCやClangなら__attribute__((used, constructor))
のような
そこではまず関数の登録のみを行います。その後、必要になったタイミングでTfRegistryManager::SubscribeTo<KEY_TYPE>()
を呼び出す事で、任意のタイミングでの関数を実行する事ができます
TfScriptModuleLoader
Pythonで利用できるようにバインディングされたライブラリの
読み込みや依存関係の管理などを適切に行うクラス
主に以下のタイミングでモジュールが読み込まれます
- スクリプトモジュールがロードされた時
-
TfPyInitialize
が呼ばれた時 - Plugが共有ライブラリを開いた時
TF_DEBUG
環境変数に TF_SCRIPT_MODULE_LOADER
を設定することで挙動を確認できます
RegisterLibrary(name
, moduleName
, predecessors
)
ライブラリをTfScriptModuleLoader
に登録するための関数
name
には C++の動的ライブラリの名前
moduleName
にはPythonのモジュール名
predecessors
は依存するライブラリの動的配列
をそれぞれTfToken
型で指定します
TF_REGISTRY_FUNCTION(TfScriptModuleLoader) {
// List of direct dependencies for this library.
const std::vector<TfToken> reqs = {
TfToken("arch"),
TfToken("js"),
TfToken("plug"),
TfToken("tf"),
TfToken("vt")
};
TfScriptModuleLoader::GetInstance().
RegisterLibrary(TfToken("ar"), TfToken("pxr.Ar"), reqs);
}
PlugRegistry
C++の共有ライブラリや、Pythonモジュールなどで作成されたプラグインに対して登録や読み込みを動的に行い、それらを管理するためのクラス
プラグインを登録するにはplugInfo.json
を用意し、
RegisterPlugins()
という関数を使用します
登録されたプラグインは、プラグインが必要とされるまで読み込まれないので、
効率的にリソースを管理する事ができます
plugInfo.json
plugInfo.json
はプラグインの登録に必要な情報を記述するためのファイルです
PlugRegistry
は、このファイルをもとにプラグインを読み込み、依存関係を解決します
{
"Plugins": [
{
"Info": {
"Types": {
"ArResolver": {},
"ArDefaultResolver": {
"bases": [
"ArResolver"
],
"implementsContexts": true
},
"ArPackageResolver": {}
}
},
"LibraryPath": "../../libpxr_ar.so",
"Name": "ar",
"ResourcePath": "resources",
"Root": "..",
"Type": "library"
}
]
}
ライブラリの動的な読み込み
動的ライブラリの呼び出し、終了は TfDlopen
TfDlclose
を使用しています
具体的にはOSに応じて、下記関数が呼び出されています
LoadLibrary() // Open
FreeLibrary() // Close
dlopen() // Open
dlclose() // Close
TfStaticData
グローバル変数や静的メンバー変数などの、グローバルデータを
安全に管理する為のクラステンプレート
TfStaticData
はローカル変数では使用できません
グローバルスコープや、無名名前空間のようなファイルスコープで使用する事が推奨されています
グローバルデータの問題点
グローバル変数や静的メンバー変数はプログラム全体で共有する事ができるが、
それらの変数が初期化される前にアクセスされてしまうと、未定義動作を引き起こす可能性があります、
#include <iostream>
int A = B; // Bがまだ初期化されていない状態でAを使おうとする
int B = 10;
int main() {
std::cout << A << std::endl;
}
また、複数のスレッドからグローバルデータにアクセスしようとすると、競合によってデータの整合性が保たれなくなってしまい、スレッドセーフではなくなるという問題も起こります
#include <iostream>
#include <thread>
int globalVar = 0;
void increment() {
for (int i = 0; i < 1000; ++i) {
globalVar++; // 複数スレッドで同時にアクセスすると競合が起こる
}
}
int main() {
std::thread t1(increment);
std::thread t2(increment);
t1.join();
t2.join();
std::cout << globalVar << std::endl; // 結果は予測できない(競合による不整合)
}
TfStaticDataによる改善
TfStaticData
はデータの初期化を遅延させる事でそれらの問題を回避しています
TfStaticData
が指し示すデータはプログラムが実行された段階では初期化されずに、そのデータへ初めてアクセスした時に初期化されます。
そうする事で限りなく安全に初期化することができ、上記の問題を回避することができます
static TfStaticData<std::set<std::string>> Xyz_nameSet;
void XyzAddName(std::string name) {
Xyz_nameSet->insert(name); // 初めてアクセスした時に遅延初期化される
}
TfNotice
通知に関するシステムを構成したり、管理するためのクラス
このクラスを継承する事で、
イベントが発生した事をリスナーに知らせる為のセンダーとしての役割も持ちます
リスナーは主に TfWeakBase
や TfRefBase
を継承したクラスで定義され、
それらが、通知を受け取る役割を持ちます
#include <iostream>
#include <pxr/base/tf/type.h>
#include <pxr/base/tf/notice.h>
#include <pxr/base/tf/weakBase.h>
PXR_NAMESPACE_USING_DIRECTIVE
// Noticeを作成
class TestNotice : public TfNotice
{
public:
TestNotice() = default;
};
// Listenerを作成
class TestListener : public TfWeakBase
{
public:
TestListener()
{
TfWeakPtr<TestListener> me(this);
TfNotice::Register(me, &TestListener::ProcessHoge); //登録
};
// Register時に引数の型を見ているので、対象のNoticeクラスを引数にする
void ProcessHoge(const TestNotice &n)
{
std::cout << "Hello Notice!" << std::endl;
};
};
// Noticeを使用するには、一度定義する必要がある
TF_REGISTRY_FUNCTION(TfType)
{
TfType::Define<TestNotice, TfType::Bases<TfNotice> >();
}
int main() {
// ListenerのコンストラクタでTestNoticeを登録
TestListener *l1 = new TestListener();
// TestNoticeからイベントをSend
TestNotice().Send();
return 0;
}
TfWeakBase
TfWeakPtr
を使用するための基底クラス
オブジェクトが削除されたかどうかを監視する事ができます
また、継承する事で TfRefPtr
変数が追加され、
TfWeakPtr
は、TfRefPtr
が管理しているオブジェクトの状態を監視し、
TfRefPtr
は実際にオブジェクトを所有し、オブジェクトが有効かどうかを管理します
TfWeakPtr
オブジェクトが削除されたかどうかを監視するためのポインタ
基本的には TfWeakBase
を継承したクラスで使用します
参照しているオブジェクトが削除されると TfWeakPtr
は自動的に無効化され nullptr
になります
また、循環参照が起こらないように、オブジェクトに対する所有権は持ちません
TfRefBase
TfRefPtr
を使用するための基底クラス
オブジェクトが実際に参照されているかどうかを管理します
TfRefBase
を継承することで、
クラスは参照カウントを管理できるようになります。
TfRefPtr
オブジェクトの所有権を管理するポインタ
TfRefBase
を継承したオブジェクトの参照カウントを実装しています
オブジェクトへの最後の参照が破棄されるとオブジェクトを自動的に削除します
Functions
ArWrapResolverContextForPython(context
)
PythonからC++の ArResolverContext
オブジェクトに変換できるコンテキストオブジェクトとして、指定された型を登録する関数
ArWrapResolverContextForPython<This>();
TfGetBaseName(fileName
)
ファイル名を取得する関数
TfGetBaseName("hoge/fuga/fugo.txt") // fugo.txt
TfGetPathName(fileName
)
ディレクトリパスを取得する関数
最後にディレクトリ区切りのスラッシュがつきます
※ Windowsの場合は \
TfGetPathName("hoge/fuga/fugo.txt") // hoge/fuga/
TfStringTokenize(source
, delimiters
)
与えられた文字列を区切り文字で分解し、文字列のベクトルを返します
TfStringTokenize("hoge:fuga:fugo",":")
//std::vector<std::string> {"hoge","fuga","fugo"}
TfStringCatPaths(prefix
, suffix
)
ファイルパスなどで使われる ..
や /
を考慮した上で、2つの文字列を結合します
TfStringCatPaths( "foo/bar", "jive" ) => "foo/bar/jive"
TfStringCatPaths( "foo/bar", "../jive" ) => "foo/jive"
TfAbsPath(path
)
指定されたファイルパスを絶対パスに変換するための関数
- すでに絶対パスが指定されている場合はそのままそのパスが返されます
- 相対パスの場合、現在の作業ディレクトリを基準にして絶対パスを返します
- 指定されたパスが実際に存在しない場合でも、絶対パスを返します
TfIsRelativePath(path
)
指定されたファイルパスが相対パスかどうかを確認するための関数
WindowsAPIのPathIsRelativeW関数の戻り値がTrueかつ、
パスの先頭文字が / や \\ ではない場合にTrue
パスが空、もしくは先頭文字が / ではない場合にTrue
TfPathExists(path
, resolveSymlinks = False
)
パスが存在するかどうかを確認するための関数
resolveSymlinks
が True
の場合はシンボリックリンクを解決した上で確認します
TfGetenv(envName
, defaultValue = ""
)
環境変数を文字列で返します、見つからない場合は defaultValue
が返されます