C++ ときどき ごはん、わりとてぃーぶれいく☆

USAGI.NETWORKのなかのひとのブログ。主にC++。

UE4: クラスファイルにたいてい仕込むログマクロの糖衣マクロ

UE4C++ コードファイルを追加する場合、特に UCLASS では規模がよほど小さくない限りたいてい定義するログマクロの定義と糖衣を紹介。

// MyHoge.h
// コンパイラーオプションで MYHOGE_ENABLE_LOG=1 など明示的に渡されない場合でも
// デバッグビルドの場合にはログマクロを ON にする
#if defined( UE_BUILD_DEBUG ) && ! defined( MYHOGE_ENABLE_LOG)
  #define MYHOGE_ENABLE_LOG 1
#endif

// ログマクロを有効にする場合
#if MYHOGE_ENABLE_LOG > 0
  /// ログONの場合のログマクロラッパー定義
  DECLARE_LOG_CATEGORY_EXTERN( MYHOGE, Log, All );
#endif
// .cpp
// ログマクロを有効にする場合
#if MYHOGE_ENABLE_LOG > 0
  // ログカテゴリー定義
  DEFINE_LOG_CATEGORY( MYHOGE );
  // このソース内でのみ有効なログマクロ書き糖衣マクロ
  #ifndef LOG
    #define LOG( LEVEL, BODY, ... ) \
      { UE_LOG( MYHOGE, LEVEL, TEXT( BODY ), __VA_ARGS__ ) }
  #endif
// ログOFFの場合のログマクロラッパー定義
#else
  #ifndef LOG
    #define LOG( ... ) 
  #endif
#endif

この仕込みを施したならば、 .cpp でログを仕込む際には LOG( "hoge %s %f", *something_string, something_float ) と書くだけでよい。UE_LOG{ }ブロックスコープにしてあるので、 if ( x ) LOG( "hoge" ) のように使っても翻訳に支障もない。MYHOGE_ENABLE_LOG を明示的に制御したい場合は "{your-project-name}.Build.cs に DefinitionsAdd すればよい。

UE4: UnrealWebServer-1.4 の GetData に文字列末尾が汚染されるバグを見つけたので応急対処法

1. UnrealWebServer

2. 問題

  • UnrealWebServer-1.4 の GetData API の返り値の文字列の末尾がランダムな文字群で汚染されるバグに遭遇した。高頻度で汚染される。
    • GetData API は POST のリクエストボディー全体を一括取得する機能

3. 原因

GetData の実装詳細を確認したところ、内部バッファーとして使用する固定長の char 配列が未初期のため発生する問題の可能性が高い事がわかった。

4. 修正

UnrealWebServer-1.4 を使用して GetData するニーズのある方が応急処置可能な必要最小限の diff を示します。

Private/Connection.cpp:

43c43
<       char post_data[4096];
---
>       char post_data[4096] = { 0 };
  • Note: UnrealWebServer は Marketplace で販売されている有料のエンジンプラグインのため、応急処置として必要最小限の diff 露出に留めます。

5. おまけ: 自家修正版エンジンプラグインのビルド&エンジンへの配置の仕方

  1. diff に基いてパッチする。
  2. cmd でも何でもいいので "C:\Program Files\Epic Games\UE_4.18\Engine\Build\BatchFiles\RunUAT.bat" BuildPlugin -plugin="C:\Program Files\Epic Games\UE_4.18\Engine\Plugins\Marketplace\UnrealWebServerPlugin\UnrealWebServer.uplugin" -package="C:\Users\<your-account>\tmp\UnrealWebServerPlugin" などと唱える。
  3. 念の為、元のエンジンプラグイン一式 "C:\Program Files\Epic Games\UE_4.18\Engine\Plugins\Marketplace\UnrealWebServerPlugin\" をバックアップする。
  4. 自家ビルドの修正版エンジンプラグイン "C:\Users\<your-account>\tmp\UnrealWebServerPlugin" を元のエンジンプラグインがあった場所へ入れる。

6. 問題の報告と公式の更新について

この記事を書く前に開発元の Isara tech. にもバグ報告、原因、修正 diff を送ってあるのでそう遠くなくアップデートしてくれる、はず。

UE4: 4.17.2 -> 4.18.1 プロジェクトのアップデートで発生した Warning と Error と対処メモ

若干の調査は必要なものの何れも些細な単純な置き換えで済む問題だけで済んだ。

Warning

1. AddTorqueAddTorqueInRadians に置き換えよ

warning C4996: 'UPrimitiveComponent::AddTorque': Use AddTorqueInRadians instead. Please update your code to the new API before upgrading to the next release, otherwise your project will no longer compile.

void AddTorque
( FVector Torque // Torque to apply. Direction is axis of rotation and magnitude is strength of torque.
, FName BoneName = NAME_None // If a SkeletalMeshComponent, name of body to apply torque to. 'None' indicates root body.
, bool bAccelChange = false // If true, Torque is taken as a change in angular acceleration instead of a physical torque (i.e. mass will have no effect).
);
void AddTorqueInRadians
( FVector Torque // Torque to apply. Direction is axis of rotation and magnitude is strength of torque.
, FName BoneName = NAME_None // If a SkeletalMeshComponent, name of body to apply torque to. 'None' indicates root body.
, bool bAccelChange = false // If true, Torque is taken as a change in angular acceleration instead of a physical torque (i.e. mass will have no effect).
);

API Reference だけ見ても違いがわからない。違いについては予感としては「たぶんない」のだけど、「ない」と知る必要がある。単位や補助単位の変換が必要だとか何かとあり得ない事もない。

source: Runtime/Engine/Classes/Components/PrimitiveComponent.h

void AddTorque(FVector Torque, FName BoneName = NAME_None, bool bAccelChange = false)
{
  AddTorqueInRadians(Torque, BoneName, bAccelChange);
}

安心して単に置き換えるとしよう。

2. FPaths::GameDirFPath::ProjectDir に置き換えよ

warning C4996: 'FPaths::GameDir': FPaths::GameDir() has been superseded by FPaths::ProjectDir(). Please update your code to the new API before upgrading to the next release, otherwise your project will no longer compile.

source: Runtime/Core/Public/Misc/Paths.h

DEPRECATED(4.18, "FPaths::GameDir() has been superseded by FPaths::ProjectDir().")
static FORCEINLINE FString GameDir() { return ProjectDir(); }

これも単純な置き換えで構わない。

Error

  1. FFileHelper::LoadFileToString の第3引数の型 FFileHelper::EHashOptionsuint32 から暗黙的に変換不能となった

error C2664: 'bool FFileHelper::LoadFileToString(FString &,const TCHAR *,FFileHelper::EHashOptions)': cannot convert argument 3 from 'const uint32' to 'FFileHelper::EHashOptions'

4.18 に取り込まれた以下のコミットで Runtime/Core/Public/Misc/FileHelper.h の struct EHashOptions { enum Type {enum class EHashOptions { に変更されたので整数の値は直接設定不能になった。

素直に FFileHelper::EHashOptions::None など enum class 値に置き換えればよい。

UE4: Free Voxel Plugin の作者が Marketpalce にて $99.99 で売り出された Voxel Plugin に痛烈なコメントを付けている件

Marketplace に出品された問題のプラグインは↓の "Voxel Plugin"

f:id:USAGI-WRP:20171112130842p:plain

知らずに一見すると素晴らしいボクセル地形のプラグインと錯覚してしまい、 $99.99 の価格にも "これくらいなら" とポチってしまうかもしれない。

ところが、ポチる前によくコメントを見ると非常に有用で、愉快かつ痛烈なコメントを見つけられる。

f:id:USAGI-WRP:20171112130906p:plain

Hethger: What about the Free Voxel Plugin the community is working on? https://forums.unrealengine.com/community/released-projects/125045-free-voxel-plugin What are the improvements worth paying this price for?

Phyronnaz: I've tried the demo, and it seems to perform a lot worse than the free voxel plugin (edit is slow, low render distance). If you consider buying this, you should first check out the free voxel plugin, as it offers many additional features (materials, mesh/landscape/spline import, tesselation, load/save, LOD with infinite render distance ...). Disclaimer: Free Voxel Plugin creator

翻訳:

ヘザー「コミュニティー版の Free Voxel Plugin に比べてどう違うんだい? https://forums.unrealengine.com/community/released-projects/125045-free-voxel-plugin に対して $99.99 を払う付加価値がどこにあるのさ。」

フィロナズ「私がデモを試した限りでは Free Voxel Plugin より酷いシロモノに感じられたよ(編集が遅いし、レンダリング距離も短いし)。これを購入する気なら、それよりも先に Free Voxel Plugin を試してほしいね、それにはもっとたくさんの付加機能もあるし(マテリアル、メッシュ/ランドスケープ、スプラインのインポート、テッセレーション、ロードとセーブ、無限遠のLODレンダリング、ほかたくさん)。注:Free Voxel Plugin の作者より」

面白い、あるいはこのコメントのやりとりは有用だと思った UE4er はとりえあえず UP VOTE して来よう・x・

ちなみに、 Phyronnaz (フィロナズ)は本当に Free Voxel Plugin の開発者です。

f:id:USAGI-WRP:20171112132441p:plain

ちょうど、ボクセル地形を扱う用事が発生していたところでこのやり取りを見かけて少し面白い気持ちがしたので紹介でした。

参考:

  1. Voxel Plugin by CodeSpartan in Code Plugins - UE4 Marketplace
  2. Free voxel plugin - Unreal Engine Forums
  3. GitHub - Phyronnaz/MarchingCubes: Voxel plugin for Unreal Engine

MSVC++ BUG: C Preprocessor is not capable empty argument

MSVC++(2017; cl.exe-19.11.25547) で C プリプロセッサーのマクロが空の引数を受け付けない C++11/14/17, C99/11 に対する規格違反を報告しました。

概要

#define X( X0 ) something ## X0 () が定義される時、引数が空の呼び出し X() は「空のトークンの引数」を受け付ける事になった C++11 (C99) 以降の言語規格に従えば something() に展開されるべきだが、該当バージョンの現行最新 MSVC++ ではマクロの展開が期待通り行われるエラーとされる。

再現

// compile this code in the MSVC++-19.11.25547
// Expected: compilable
// Actual: not compilable; see the comment below

#include <cstdio>

#define Y( Y0, Y1 ) something ## Y0 ## Y1 ()
#define X( X0 ) something ## X0 ()

void something() { puts( "(n/a)" ); }
void somethingFoo() { puts( "Foo" ); }
void somethingBar() { puts( "Bar" ); }
void somethingFooBar() { puts( "FooBar" ); }

int main()
{
  Y( Foo, Bar );
  Y( Foo, ); // <-- C99 capable, MSVC++ is compilable
  Y( , Bar ); // <-- C99 capable, MSVC++ is compilable
  Y( , ); // <-- C99 capable, MSVC++ is compilable
  X( FooBar );
  X( ); // <-- C99 capable, MSVC++ is NOT compilable
}

この問題の影響

  1. 言語規格違反
  2. Y(,) パターンのような回避策を強いられる。
  3. Microsoft プラットフォームのため『だけ』に追加のコーディングコストを強いられる。

だるい・x・

C++: C プリプロセッサーで定義されるマクロに空のパラメーターを渡す合法性

背景

ソースコード中に大量の少し複雑なアクセサー大量に定義する必要があり、久しぶりに C プリプロセッサーのマクロでアートする機会がありました。マクロを組み上げる上で1つ気になる事が生じました。「マクロに空のパラメーターを渡すのは合法か?」と。

#include <cstdio>

#define X(X0,X1,X2) X0 ## _ ## X1 ## X2 ()

void Hoge_FugaPiyo() { puts( "A" ); }
void Hoge_Piyo() { puts( "B" ); }

int main()
{
  X( Hoge, Fuga, Piyo );
  X( Hoge, , Piyo );
}

結論

  • C++03 (C89) --> 「未定義」(もし意図通り動作するとすればそれはコンパイラーの独自拡張)
  • C++11 (C99) --> 「合法」(次節の「論拠」を参照)
  • C++14 (C99) --> 「合法」( C++11 を踏襲 )
  • C++17 (C11) --> 「合法」( C99 -> C11 で関連仕様は引っくり返っていない)

(Note: C++17 が C11 か C99 かで2回記事を修正しました。詳しくは記事末尾の「修正にあたり頂いた情報源」をどうぞ😃)

論拠

  1. C++11 は C99 を、C++17 は C11 を基本的には内包している。
  2. C99 規格
    1. WG14/N1256 Committee Draft - September 7, 2007 ISO/IEC 9899:TC3 §6.10.3/4
    2. Rationale for International Standard - Programming Languages - C Revision 5.10 April-2003 §6.10.3
  3. C11 規格
    1. ISO/IEC 9899:201x Committee Draft - April 12, 2011/N1570 §6.10.3/4

(2.1.) WG14/N1256 Committee Draft - September 7, 2007 ISO/IEC 9899:TC3 §6.10.3/4:

A new feature of C99: Function-like macro invocations may also now have empty arguments, that is, an argument may consist of no preprocessing tokens."

「C99における新機能: 関数様のマクロの実行において空の引数は有り得る、つまり、引数にはプリプロセッサーのトークンが無い事は有り得る。」

(2.2.) Rationale for International Standard - Programming Languages - C Revision 5.10 April-2003 §6.10.3

If the identifier-list in the macro definition does not end with an ellipsis, the number of arguments (including those arguments consisting of no preprocessing tokens) in an invocation of a function-like macro shall equal the number of parameters in the macro definition.

「マクロ定義の識別子リストが省略記号 ( ... ) で終わっていない場合、関数様のマクロ実行の引数群(プリプロセッシングトークンが無い引数も含め)はマクロ定義と同数でなければならない。」

(3.1.) ISO/IEC 9899:201x Committee Draft - April 12, 2011/N1570 §6.10.3/4

C99 と同じ。

と、いうわけで「C++11以降のCプリプロセッサーマクロでは、プリプロセッサーのトークンとしては空っぽの引数もあっていい」と書いてありました😃

参考

修正にあたり頂いた情報源

参照情報についてご意見頂き記事を再々修正しました(1回目:C++17がC11ではなくC99ままになったかと誤解して一旦断言を保留。2回目:以下のご意見からC++17はC11を参照と確認できたため記事を初稿同様C++17をC11参照として断言へ)。ありがとうございます😃

UE4 の EULA と GPL 、プラグインが内包するソースで確認が必要だったはなし

UE4 EULAGPL

UE4/EULA では UE4 と一緒に使ってはダメなソースコードのライセンスを明記しています。一部抜粋。

Other Restrictions Non-Compatible Licenses You may not combine, Distribute, or otherwise use the Licensed Technology with any code or other content which is covered by a license that would directly or indirectly require that all or part of the Licensed Technology be governed under any terms other than those of this Agreement (“Non-Compatible License”). Code or content under the following licenses, for example, are prohibited: GNU General Public License (GPL), Lesser GPL (LGPL) (unless you are merely dynamically linking a shared library), or Creative Commons Attribution-ShareAlike License. Code or content under the following licenses, for example, are allowed: BSD License, MIT License, Microsoft Public License, or Apache License. You may not sublicense the Licensed Technology under a Non-Compatible License.

ざっくり要訳としては、

  • UE4と相容れないライセンス」の「ソースコードなどのあらゆる技術的要素をUE4と組み合わせて使用、配布はできませんよ」
  • 「例えば GPL 、動的リンクではない LGPL 、 CC-SA などのライセンスが UE3と相容れないライセンスの技術要素に該当します」
  • 「ちなみに、 BSD License 、 Microsoft Public License 、 Apache License などは許容されますよ」

と書いてあります。不自由な GPL にラインセンスされた少なくとも UE4 使いにとっては不遇なライブラリー群に特に外部ライブラリーを必要に応じて導入する機会も少なくない UE4/C++er はよく注意する必要があります。

Note: EULAのより広範についての公式な日本語のFAQは UE4/日本語版FAQ をどうぞ。

Mongoose と GPLv2

先日、 UE4 の Marketplace にも登録されている "あるエンジン向けのソースコードプラグイン" の内部で Github Mongoose を使用している事に気付きました。ライセンシングにある程度の注意を払っている一般的な C++er としては脳が警戒モードの動作に切り替わり確認作業が始まります。

しかし、"あるプラグイン" については、結論としては「UE4での使用、製品のリリースに問題なし。GPLv2の影響は受けないので最終的な製品のソースコード開示義務も負わない」となりました。 "あるプラグイン" では確かに内部的に "Mongoose" ライブラリーを使用していましたが、"最新版の GPLv2 の Mongoose" のコードは使っていないと調査されたためです。そして、 "Mongoose" はある時点でライセンスを "AS IS" タイプの自由なライセンスから不自由な "GPLv2" へ切り替えています。

"あるプラグイン"のソースコードが添付の LICENSE ファイルだけでなく、本当にライセンス変更以前のバージョンのものか調査するコストは若干の手間はかかりますが、結果、 UE4 で使用しても問題のない自由なライセンス時代のソースをベースにしている事がわかり、無事にこの "ライセンス違反の恐れ" は問題無い事がわかったのでした。

Marketplace で手に入る"いわば公認"のプラグイン群については心配するだけ過剰な気もしますが、コミュニティーベースのフリーのプラグインや公式から遠いところで公開されているプラグイン、また自作のプラグインなどでうっかりライセンス違反が起こらないように気を付けたいですね。

参考

  1. UE4/EULA
  2. UE4/日本語版FAQ
  3. Github Mongoose
  4. Github Mongoose LICENSE latest
  5. Github Mongoose LICENSE diff

おまけ: Facebook での関連ポスト