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

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

UE4/C++: NewObject で生成したオブジェクトの Outer を変更したい場合のメモ

↓こういうのが居たとする。

// UObject 派生だがシングルトンファクトリーで生成されるオブジェクトがあったとする
UA* UA::GetInstance()
{
  if ( ! my_static_instance )
    my_static_instance = NewObject< UA >();
  return my_static_instance;
}

このようなファクトリーで得られる仕組みの UA のオブジェクトは Outer を持たないため GetWorld() しても nullptr が帰るなど不便な事もある。幾つか無理やり気味な方法は思いつくものの、 Outer を外から設定できてしまえば何かと解決する。この悩み事が生じた際は UObject::Rename が最も簡単な解決方法になる事がある。

// UGameInstance など World に絆付けされた何かから
void UMyGameInstance::Something()
{
  auto a = UA::GetInstance();
  a.Rename( nullptr, this );
}

こうすると、以降は UA のオブジェクト内部やそこからさらに NewObject<T>( this ) で生成したオブジェクトは Outer の絆により GetWorld() も期待動作するようになる。もちろん、もっと簡単な用途で単純に Outer を変更したい場合も UObject::Rename で同様に Outer を変更できる。

UObject::Rename は関数名からはオブジェクトの名前を変更するだけに見えるが、シグニチャーを見れば第2引数に UObject * NewOuter とある。実装では第1引数に NULL がデフォルトで与えられているが省略して呼ぶ意味があるかはさておき、名前を変更する必要の無い場合には nullptr を与えれば良い事を推量できる。(実際に第1引数は nullptr でそのように動作させられる)

// Runtime/CoreUObject/Public/UObject/Object.h
    /** Rename this object to a unique name.*/
    virtual bool Rename( const TCHAR* NewName=NULL, UObject* NewOuter=NULL, ERenameFlags Flags=REN_None );

例によって事実上アンドキュメントなので使う場合はソースを読んだ方が良い。

// Runtime/CoreUObject/Private/UObject/Obj.cpp
bool UObject::Rename( const TCHAR* InName, UObject* NewOuter, ERenameFlags Flags )

ERenameFlagsRF_ClassDefaultObject, REN_ForceGlobalUnique, REN_Test, REN_ForceNoResetLoaders, REN_NonTransactional, REN_DoNotDirty, RF_Public など指定すると意味があるようだが、さしあたり Outer を設定、あるいは挿げ替えたいだけの場合には実引数は省略して REN_None を与えれば良い。実装詳細的には LowLevelRenamePostRename も呼んでいるので思いのほか複雑な事をしている。

References

C#: Windows 青魔法により別プロセスのコマンドラインを掠め取ってくる方法、または WMI の使用方法

残念な事に別プロセスの起動時に与えられたコマンドライン情報を System.Diagnostics.ProcessStartInfo プロパティーFileNameArguments から取得はできない。それらはプロセスの起動を自身のプロセスから行う際に自身が設定した場合にのみ意味がある。別プロセスを相手にするのなら、そのアプローチは諦めよう。

ではどうするか?黒魔法は惜しいアイデアだ。 powershell でも cmd でも良いが、

wmic process where "processid=32096" get commandline /format:value
CommandLine="C:\Program Files\Epic Games\UE_4.19\Engine\Binaries\Win64\UE4Editor.exe" C:\Users\usagi\tmp\Experimental\Experimental.uproject -debug -game

などと取得できる。この実行例は UE4 プロジェクトを開発環境からデバッグ実行した状態で実行された Experimental プロジェクトのコマンドライン。プロセスIDはどうやって確認したのか?それは 1つ前の記事「UE4/C++: ジェネリック・プラットフォーム・罠ビリティー」 を眺めて欲しい。

wmicSystem.Diagnostics.ProcessStartInfoArguments に適当な引数を与えて標準出力から結果を読み出してパースすれば良い。そういうやり方は生きている世界にもよるが今回の記事のレベルとしては黒魔法という事にしておく。このような黒魔法的な方法も C++ アプリから WMI でさっくりと情報を取りたい場合には C++ネイティブコードで素直にDLLをリンクしてCOMを扱う よりもよほど現実的に便利だったりする事もある。

今回は C# なので WMI をあっさり使える。 "黒魔法" 的に実装するよりもせいぜい "青魔法" 的な実装で済む。 C# プロジェクトの ReferencesSystem.Management を追加し、次のような実装を行えば先の wmic と同様の事を "一応" C# だけで実装できる。

using System.Management;
static private string GetProcessCommandline( Int32 process_id )
{
  using ( var s = new ManagementObjectSearcher() )
  {
    s.Query.QueryString = "select * from win32_process";
    using ( var c = s.Get() )
    {
      var pid_string = process_id.ToString();
      foreach ( var o in c )
        if ( o[ "processid" ].ToString() == pid_string )
          return o[ "commandline" ].ToString();
    }
  }
  return null;
}

C# のセンセイ級の諸姉諸兄におかれましてはにゃーんか納得できないお気持ちでしょうか。

「クエリーを文字列で書くのか👹」

はい、そういうわけで "青魔法" っぽいじゃないですか。あはは😅

foreach 使っていいのは ISO/IEC 23270:2006 C# (2.0) までだから👹」

ManagementObjectSearcher 系に Where とか LINQ 対応を実装してくれていない Microsoft に言って下さい。

「その失敗 null じゃなくて例外使えよ👹」、「WMI 原理主義者として CommandLine と書くべきと要求する👹」、「文字列じゃなくて Int32 に合わせて比較しろよ👹」…

自分で何か作る時には自分へ要求して下さい。本当に効率が良いか、必要な事なのかはさておき。

「にゃーん」

『にゃあああああああああああああああん♥』

と、いうわけで WMI "青魔法" 実装でした😃 C++ と違ってまだ必要ならちょろりと実装を書こうかという気がしますが、そもそも OS の API としてfjkfjk (゜∀。)

にゃーーーーん。

References

UE4/C++: ジェネリック・プラットフォーム・罠ビリティー

諸事情により UE4 でゲームではない何かを開発したりユカイなマルチプロセスのゲームを作ったりすると プロセスID を取得したくなるかもしれない。わたしはなった。

UE4 にはそんな時に便利そうな FGenericPlatform::GetCurrentProcessId がある😃 プロセスIDを取得するためにクロスプラットフォームコードをちまちまOSごとに書く必要の無い理想郷がここにある。

さっそく実装しよう。

const auto pid = FGenericPlatform::GetCurrentProcessId();
UE_LOG( LogTemp, Log, TEXT( "pid=%d" ), pid )

しかしこの結果は常に、

0

となる。

驚きだろうか。私は一瞬驚いてソースコードを読みに行ってしまった。

// Runtime/Core/Private/GenericPlatform/GenericPlatformProcess.cpp
uint32 FGenericPlatformProcess::GetCurrentProcessId()
{
    // for single-process platforms (consoles, etc), just use 0
    return 0;
}

( ゚д゚)...

で、3秒くらいこの顔のまま考えて気づいた。『こっちじゃない』って。

// 『― ごめんね、ここはあなた( `Generic` )じゃダメなの ―』
// const auto pid = FGenericPlatform::GetCurrentProcessId();

// 『― あなたと、結合(リンク)したい! ―』
const auto pid = FPlatformProcess::GetCurrentProcessId();
UE_LOG( LogTemp, Log, TEXT( "pid=%d" ), pid )

FPlatform は実質的に Unreal Engine API Reference でもアンドキュメントになってしまっているのだけど、

// Runtime/Core/Public/HAL/PlatformProcess.h
#if PLATFORM_WINDOWS
#include "Windows/WindowsPlatformProcess.h"
#elif PLATFORM_PS4
#include "PS4/PS4Process.h"
#elif PLATFORM_XBOXONE
#include "XboxOne/XboxOneProcess.h"
#elif PLATFORM_MAC
#include "Mac/MacPlatformProcess.h"
#elif PLATFORM_IOS
#include "IOS/IOSPlatformProcess.h"
#elif PLATFORM_ANDROID
#include "Android/AndroidProcess.h"
#elif PLATFORM_HTML5
#include "HTML5/HTML5PlatformProcess.h"
#elif PLATFORM_LINUX
#include "Linux/LinuxPlatformProcess.h"
#elif PLATFORM_SWITCH
#include "Switch/SwitchPlatformProcess.h"
#endif

こんな具合に実装されていて、例えば Windows では

// Runtime/Core/Public/Windows/WindowsPlatformProcess.h
struct CORE_API FWindowsPlatformProcess
    : public FGenericPlatformProcess
// 中略
typedef FWindowsPlatformProcess FPlatformProcess;

と、なっていて、もちろんユーザープロジェクトからも他のエンジン実装のライブラリー群と同様に使用できる。

API Reference ドキュメントから機能を探してほぼ無意識にホイホイと実装してしまうと、うっかり FPlatformGenericProcess ほか Generic 系を叩いてしまうのだけど、プラットフォームに依存性の高い機能は今回の GetCurrentProcessId のように期待動作せず「( ゚д゚)」の3秒が訪れる事がある。

意識をしっかり保ちましょう。わたし、プログラマー( ・`ω・´)

C#/JSON/HTTP: .net で JSON-RPC-2.0 のクライアントを使ったら WebException が飛び ServerProtocolViolation で蹴られた件

Visual StudioC# プロジェクトをにゃんにゃんしている土曜日だった。JSON-RPC-2.0 のクライアントを実装する必要があったので NuGet を見ると導入が簡単そうなライブラリーが幾つも見つかる。

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

このへんが使うの簡単で使ったらいいんじゃないかと思ったライブラリー:

ダウンロード数とアップデートを見ると Nethereum.JsonRpc.RpcClient とか EdjCase.JsonRpc.Client が良いかもしれないと思ったが、依存ライブラリーが多く、そもそも大きなライブラリーの小さな部品の1つとして設計されていて JSON-RPC-2.0 クライアントをさっくり簡単に使いたい場合には無駄に複雑な上にドキュメントも難解でした。

ライブラリー選びによらず、たいていの .net の HTTP クライアントは System.Net.Http をライブラリーの実装詳細に用いている。先に挙げた2つもそのタイプ。

使ってみると System.Net.WebException 例外が飛んできた。catch してみると

  • Status: ServerProtocolViolation
  • Message: The server committed a protocol violation. Section=ResponseStatusLine

だそうだ。

WebException 例外氏「オレは悪くない。なんかサーバーがおかしい。RFC違反のヘッダー返してくる。だからオレは知らん。」

みたいな。

さて、この例外が帰ってくるサーバーアプリは、 Chromecurl で目視確認する限りではヘッダー含め異常は無く、人間の目には正常に JSON-RPC-2.0 のレスポンスを返してくれていた。

curl --dump-header - http://127.0.0.1:50080/API/JSON-RPC-2.0/
HTTP/1.1 200 OK
Content-Length: 97
Content-Type: application/json
Access-Control-Allow-Origin: *

{"jsonrpc":"2.0","error":{"code":-32700,"message":"Parse error; an invalid JSON data"},"id":null}

どう見ても正常に JSON-RPC-2.0 のエラーレスポンスを 200 OK している。少なくとも HTTP レスポンスのレベルでは問題無さそうに見える。(本当は問題があるのだが😋)

この状況でもクライアントアプリのプロジェクトの App.config<httpWebRequest> 要素を追加し、 useUnsafeHeaderParsing = true を設定すると JSON-RPC-2.0 ライブラリーたちは取得結果のパースも正常に行え、期待動作してしまう。

<configuration>
  <!--他の要素は省略-->
  <system.net>
    <settings>
      <httpWebRequest useUnsafeHeaderParsing="true"/>
    </settings>
  </system.net>
</configuration>

しかし、キモい。

さて、見えない原因は何だろうか?

幸い WebException が問題があると言うバイナリーは現にそこにあるのだ。解析すればよい。私、プログラマー。バイナリー、トモダチ。

curl --trace - http://127.0.0.1:50080/API/JSON-RPC-2.0/
== Info:   Trying 127.0.0.1...
== Info: TCP_NODELAY set
== Info: Connected to localhost (127.0.0.1) port 50080 (#0)
=> Send header, 96 bytes (0x60)
0000: 47 45 54 20 2f 41 50 49 2f 4a 53 4f 4e 2d 52 50 GET /API/JSON-RP
0010: 43 2d 32 2e 30 2f 20 48 54 54 50 2f 31 2e 31 0d C-2.0/ HTTP/1.1.
0020: 0a 48 6f 73 74 3a 20 6c 6f 63 61 6c 68 6f 73 74 .Host: localhost
0030: 3a 35 30 30 38 30 0d 0a 55 73 65 72 2d 41 67 65 :50080..User-Age
0040: 6e 74 3a 20 63 75 72 6c 2f 37 2e 35 38 2e 30 0d nt: curl/7.58.0.
0050: 0a 41 63 63 65 70 74 3a 20 2a 2f 2a 0d 0a 0d 0a .Accept: */*....
<= Recv header, 16 bytes (0x10)

ここまで送信。問題はここから先、帰ってくるレスポンスのヘッダー領域にある事になっている。

0000: 48 54 54 50 2f 31 2e 31 20 32 30 30 20 4f 4b 0a HTTP/1.1 200 OK.
<= Recv header, 19 bytes (0x13)
0000: 43 6f 6e 74 65 6e 74 2d 4c 65 6e 67 74 68 3a 20 Content-Length:
0010: 39 37 0a                                        97.
<= Recv header, 31 bytes (0x1f)

…む??

0000: 43 6f 6e 74 65 6e 74 2d 54 79 70 65 3a 20 61 70 Content-Type: ap
0010: 70 6c 69 63 61 74 69 6f 6e 2f 6a 73 6f 6e 0a    plication/json.
<= Recv header, 31 bytes (0x1f)

…これは・w・

<= Recv header, 31 bytes (0x1f)
0000: 41 63 63 65 73 73 2d 43 6f 6e 74 72 6f 6c 2d 41 Access-Control-A
0010: 6c 6c 6f 77 2d 4f 72 69 67 69 6e 3a 20 2a 0a    llow-Origin: *.
<= Recv header, 1 bytes (0x1)

なるほど。

0000: 0a                                              .
<= Recv data, 97 bytes (0x61)

今回は原因が「見え」ました😃 よくバイナリーを見ると、レスポンスヘッダーのセパレーターの改行文字が 0x0a だけになっています。 0x0d どこ行った。と、言うわけで今回の原因はサーバーアプリが HTTP レスポンスヘッダーのセパレーターの改行文字を本来ならば RFC2616 §4 HTTP Message に準拠し "CR LF" ( 0x0D 0x0A ) を使わなければなりませんところを "LF" ( 0x0A ) のみで返している事らしいとわかる。

で、サーバーアプリの HTTP レスポンスヘッダーの生成箇所の実装ソースコードを確認してみると、

constexpr auto ret = TEXT( "\n" );

などと書いてありました。

constexpr auto ret = TEXT( "\r\n" );

このように修正すると、 System.Net.HttpuseUnsafeHeaderParsing = false でも期待動作するようになり WebException も飛ばなくなりました。こわいですね💀

References

.net./C#/UE4: UE4 製のアプリを C# から起動したり終了したりさせたい場合のメモ、あるいは閉じても閉じても殺しても死なないアプリを殺すメモ。

// Process
using System.Diagnostics;
// C# アプリから UE4 製のアプリ(に限らず)を起動してプロセスをハンドルする
var p = Process.Start( "your-executable-path.exe" );
// 既に動作している UE4 製のアプリ(に限らず)を探してプロセスをハンドルする
// note: hoge.uproject で作った hoge.exe なら hoge で探せる。
var ps = Process.GetProcessesByName( "your-ue4-project-name" );
if ( ps.Length > 0 )
  var p = ps.First();
// ハンドルしている UE4 製のアプリ(に限らず)のプロセスを殺す(つもりだが死なない)
p.Close(); // <-- 残念でした。死にません。
// メインウィンドウを殺す(つもりだが死なない)
p.CloseMainWindow(); // <-- 残念でした。死にません。
// お前を…殺す(つもりだが死なない)
p.Kill(); // <-- 残念でした。死にません。

実は死んでいるのだが死んでいない。UE4製のアプリは元気に何事も無く動作している上に、 GetProcessByName に発見されないステルス状態になっている…かのような挙動が観測されるようになる。

ここで UE4 製のアプリのプロセスがどうなっているのか Process Monitor とかそういう何かでプロセスツリーを確認しよう。(WindowsPowerShell でも psエイリアスとして動作するようだけど、ツリー表示はできないらしい。 WSL からでは Windows のプロセスは見えないし。不便じゃのぅ…😅)

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

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

今回は G5 という UE4 製のアプリです。 UE4 のパッケージ機能を使い Windows 向けにプロダクトを出力すると、出力ディレクトリー直下に <project-name>.exe が生成され、一般的には配布先のユーザーはこれを起動してアプリを使います。

しかし、実際にはこの出力ディレクトリー直下の <project-name>.exeBinaries/Win64/<project-name>-Win64-Shipping.exe などを子プロセスとして起動させる事で UE4 製のアプリの"本体"(というか"実体")を間接的に起動させるプロセスのラッパー。だから C# の実装で <project-name> で起動したり探したりしたプロセスを Close しても CloseMainWindow しても Kill してもユーザーの目に映る動作中の UE4 製のアプリの "本体" は死なずに動作し続けます。プロセス名も尾びれが付いていて異なるため、目に見える UE4 製のアプリは動き続けているのに GetProcessesByName で探せない一見すると謎のステルス状態のように観測されるようになります。

// UE4 製のアプリを起動してプロセスをハンドル
var p = Process.Start( "your-executable-path.exe" );
// ぶち殺す
using ( var killer = new Process() )
{
  killer.StartInfo.FileName = System.IO.Path.Combine( Environment.GetFolderPath( Environment.SpecialFolder.System ), "taskkill.exe" );
  killer.StartInfo.Arguments = string.Format( "/PID {0} /T /F", p.Id );
  killer.StartInfo.CreateNoWindow = true;
  killer.StartInfo.UseShellExecute = false;
  killer.Start();
  killer.WaitForExit();
}
// ↑ ref. https://qiita.com/yohhoy/items/b6e32e17c9d568f927d8
// @yohhoy さんありがとう♥(2014年の記事なので最新のピカピカマイクロソフトAPIではこんなことしなくて済むのか知らんけど。)

率直に言ってウケル…笑っていいと思います。でも、 Windows ってたまにこーゆーことした方が楽だったり高速だったりって事あるのよね…。例えば真面目に WMI を使うよりコマンド呼んで文字列の結果をパースした方が早かったり速かったり、なんてこともあった。

UE4 アプリの場合は子プロセスで起動される事に気づいてしまえば子プロセスを確定する方法はもうちょっと丁寧だったり、あるいはファイルから泥臭く拾ってくるにせよ Close を呼んであげる事もできるけれど、汎用性もあるのでブログにこの方法もメモしておく事にしました。

そもそも終了に限らず別プロセスからにゃんにゃんするなら適当なプロセス間通信の手段を用意した方がいいけどね😅

References

C#/WPF: 10年ぶり?5年ぶり? Visual Studio で C# で XAML で WPF で

Note: 今回は技術的内容はたぶん無いよう・w・ たぶんただのエッセイだよう・w・

Visual StudioC# を書くのはお気持ち的にはたぶん10年ぶりくらい(実際はそんなに長くないよ😋)。 C# でアプリを書くのは5年ちょっと振りくらい(たぶんほんと)。 C#.net の概況や言語仕様のアップデートは毎年1回くらいは気にかけていたけどね(メテオにはMPが足りないテラおじいちゃんが「おもいだす」できる程度には)。

私が C# を使わなくなった理由は幾つかあるけれど、

  • Managed DirectX の緩やかな衰弱、そして事実上の死
  • XNA の緩やかな衰弱、そして死
  • Silverlight の緩やかな衰弱、そして死
  • Microsoft に振り回されたくない想いの強まり
  • Mono 黎明期と GNU/Linux プラットフォーム主環境化と ASP.net によるサーバーサイドエンジニア化
  • そもそもわたしが C# を使う理由が消失した

そんな事が10年くらい昔から5年くらい前までに私が C# を直接は使わなくなった流れ。言語としては好きなんだよ😃

で、最近ね、仕事のボスが

「伊藤さん、技術力すごい評価するけどビジネスアプリ的なUIとか作ってくれないですよね(だからボーナス評価は並に落としますね」

とか言うの。まあ、私の会社じゃないし、お世話になってるだけだからね、要求には答えてあげないとね♥

『べ、べつに作れないわけじゃ・・ないんだから!(ぁーだりぃーょぉー、そんなダサいもの作りたくないでござるよー・・・うぐぅー』

と、いうわけで伊藤さんは中略 WPFVisual StudioC#Windows 向けのビジネスアプリ的なUIというやつをぺぺっと作ってやることにしたのでした。

先ず、 C++ しか使わないので入れていなかった C#WPF でデスクトップアプリつくれるっぽいキットを Visual Studio Installer を起動してぽちぽちインストールします。 Win Form そんな存在すら危うさを感じるものいまさら使いません💀 UWP そんな制約で疲れそうなものぺぺっと作りたいだけなのに使いません(たぶん勝手な思い込みです)。

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

既に C++ 環境のため Visual Studio の本体的な何かはインストールされているため、1分もかからずあっさりダウンロードとインストールがおわった。再起動も不要だったのでそんなことに無駄に小さな幸せを感じてしまった私に悔しさを感じた。

起動して WPF デスクトップアプリのプロジェクトを新規作成。なんかフォーム(WPFなのでそうじゃない)のヴィジュアルなデザイナーの下にコードが同時表示されている。

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

お久しぶりデース、XAML =サン。ぁぁ、懐かしいですね、わたし好きですよ、こういう GUI をヴィジュアルプレビューと記述用のテキストな言語で作れる何かそういうの。(最近は UMGUE4 )を使っていたのでテキストソースでは簡単にはイジれないんだよね。その前は imgui で、あれはソースでしかイジれないしデザインのプレビューとか完全脳内コンパイラー必要だし。)

わたしの脳内のわたしじゃない誰か「ハァィ、うさぎー、それじゃあ今回はどうして QML は使わなかったんダーイ?」

 

わたしの脳内のそれじゃない誰か『うるせえな、あんなもん使ったら GPL 汚染(厳密には使うコンポーネントやソースのバージョンによって LGPL3 だったり GPL3 だったり GPL2 だったりする )を受けるしクロスプラットフォームなんてどうでもよく Windows でだけぺぺっと動けばいいなら Qt 開発環境もめんどくせーんだよ・w・』

脳内で誰かがおしゃべりしていた。私にはどうでもよいことだが。

わたしらしき何者か『さて、とりあえずボタンでも付けてみようか。』

Visual Studioコンポーネントがアイコンで表示されてくれていてマウスでどーっとするとばーんと GUI ができるあのアレは…どこだ…。

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

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

わたしのなかのかわいいかもしれないやつ『ぉぉ~なるほどぉ~!こんなところに縮こまっておったかーー、気付かんところだったではないかぁ~~😂』

などと思った気がしたが、ぺぺっとアプリを作るためにこの子には脳内の宇宙の扉の先のどこかへお帰り頂いた。

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

「押せ。」と一応書いてから気づいたが、引いたり、さすったり、殺したり、おしゃべりしたりはできないのでデザイン工学に一杯取られた気がした。実は殺すことはたぶんできるのだが、それは実行時にウィンドウハンドルを(中略)そのはなしではなかった。ぺぺっと忘れて続けよう。

それで、ただボタンを付けてイベントを束縛しても面白くないので Visual Studio のヴィジュアルな力とやらを見てやろうと思った。グループ化してテキストボックスも入れてやろう、グフフ…。…これを…こうして…ここへ…。

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

たぶんわたしに接近してきたわたし「私の…わたしの…「かわいいボタン。押せ。」が消えてしまったではないかぁ~~~~~うわぁああああああん」

こういう時、かわいいやつを出すと得をするのだが、それは相手が居る場合だ。残念だったな、帰れ、かわいいわたし。

わたし、プログラマー。ソース、信じる。(ごにょごにょ

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

わたし『ぐぬぬ…。なーにがグループじゃぁ~~\(^o^)/ うっかり騙されたではないかぁ~~~!(設計に基づく正しい挙動です)』

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

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

なんて遊びながらでもぺぺっと GUI アプリ作ってさっさと動かせちゃうんだから、便利でいいよね。

感覚をハンドリングできはじめてきた気がするので、落書きエッセイはこのくらいにします。こんなの読んでくれて、ありがとうね😋

UE4/C++: C++er がUE4型のシングルトンを作る際に気をつける事

UE4 にはガベージコレクションがある。しかし特に C++er は正しい実装を過剰に気に掛ける必要がある。」

UE4Editor から GUI でも設定できる GameSingletonClass, GameInstance を使う場合は気にする必要は無い。今回はその仕組みを使わない場合の話。

UObject 派生型のシングルトン・オブジェクトを C++ の実装側だけで NewObject し意図しないガベージコレクションを行わせない方法:

// static なシングルトンのインスタンスを取得するメンバー関数など
UMySingletonType* GetInstance()
{
  if ( instance = nullptr )
  {
    instance = NewObject< UMySingletonType >();
    check( instance )
    // ↓ これ: UObject の基底型の UObjectBaseUtility のメンバー関数
    instance->AddToRoot();
  }
  return instance;
}

たまに忘れる。そして初回動作に安心してしばらく、1分以上経過してからインスタンスが事故を起こす。この時、直接インスタンスを自身のコードで扱っていればすぐに気づくが、このクラスを介して保持しているはずオブジェクトをクローズドソースのサードパーティー製のライブラリーに弱参照させている場合など原因へ辿り着くには時間と経験が必要になり困難なバグとなる。

なお、 UObjectBaseUtility::AddToRoot の対は UObjectBaseUtility::RemoveFromRootUObjectBaseUtility::AddToRootUObjectBaseUtility::SetFlags( RF_MarkAsRootSet ) と等しい。RF_MarkAsRootSetEObjectFlags で定義される enum 値。

References