2D勢にもclusterで配信してほしいので何か作った話
この記事はcluster Advent Calendar 2019 24日目の記事です。
昨日はなんで屋さんによる「clusterをやったら友達が増えた話」でした。そうなんです、clusterをやると友達が増えるんです。皆さんclusterやりましょう。
お前 is 何
こんにちは、スワンマンです。クラスター株式会社でカスタマーサポートとかTwitterの中の人とかQAとかスワンマンとかを担当しております。 私がクラスターに入社した経緯などはこの辺を見ていただくとして、簡単に言うと元々clusterのヘビーユーザーでした。こんなアバターで参加したものだから、clusterにBAN機能を導入しようか本気で検討されたというのは入社後に聞いた話です。
2D勢はclusterが使えない?
clusterは3Dアバターを用いてイベントを行うツールなので、2D勢の方からすると無縁のサービスに思えちゃいますよね。わかる。
それならユーそのまま入っちゃいなよ、ということで絵を2枚指定するだけでアバターにするツールを作りました。
Panelman
上記リンクから入手可能です。使い方は画像を2枚指定して出力するだけなので説明するまでもないのですが、詳しくは中に入ってるテキストを読んでください。 こんな感じでアバターをいとも簡単にアップロードすることができます。
clusterを使って配信しよ?
clusterってVRライブをやるところだという認識が強いと思うんですけど、ファンミーティングみたいなイベントにもめちゃくちゃ向いてるんですよね。
私が入社する前にclusterで行われた、とある推しVtuberさんとの1:1のチェキ会イベントに参加したことがあるんですけど、これ推しが自分に向かって走ってきてくれるんですよ。推しが自分に向かって走ってきてくれるんですよ。わかりますか?つまり推しが自分に向かって走ってきてくれるということなんですよ。*1
ちょっと興奮してしまいましたが、これはイベントを開催する側にも大きいと思っていて、ファンの姿を視認できるという点ではリアルイベントと同じですけど、clusterでは自分とファンが同じ空間に存在可能というのが圧倒的に違うんですよね。
clusterで行われるファンミではよくある光景の「ファンひとりひとりに駆け寄って一声かける」みたいなのは同じ空間にいるからこそできるファンサービスだと思います。
あとリアルイベントは会場を押さえるのにお金がかかりますが、clusterは無料!圧倒的コスパ…!
余談
Panelmanというツール名はclusterのバチャ春という1週間くらい続いたイベントで私が作ったアバターの名前に基づいております。
さくらみこちゃんがゲストで参加した次の日、会場にさくらみこちゃんの等身大パネルのようなものが設置されたんですが、「パネルっぽいアバター作って隣に勝手にパネル増やしたら面白いのでは?」と思ってサクッと作ったものです。反省はしている。
なお社内からの反応(社内Slackのアイコンにしてたので)
*1:なお限界を迎えたので「それ以上近付いてはいけない」という旨を伝えました
自作プラグインの10.3 Rio対応状況。
10.3 Rioに新規対応したもの
10.3 Rioでもそのまま動いたもの
サポート終了のもの
- May the bevel be with you Ver.1.0 (旧凹ンパイルプラグイン)…コンパイル状況ウィンドウが一新されたので。
- Quick "Quick Edit" Ver.0.0.1…気が向いたら対応するかも?
自作プラグイン更新のお知らせ。
今更で申し訳ないですが><
10.2 Tokyoに新規対応したもの
10.2 Tokyoでもそのまま動いたもの
- 16進数表示プラグイン for Delphi Ver.1.0
- Lock on Load plugin
- 無名メソッド入力補完プラグイン Ver.0.0.2
- New Edit Window for Embedded Designer plugin Ver.0.0.1
- Lock Toolbar Ver.0.0.1
- May the bevel be with you Ver.1.0 (旧凹ンパイルプラグイン)
- DLight Ver.0.0.7
- Open standard libraries as read-only by default Ver.0.0.1
- Quick "Quick Edit" Ver.0.0.1
- Component Tray Ver.1.1.0
- Clipboard History Disabler Ver.0.0.1
- Style Selector for TFrame Ver.0.0.1
ユニットが使用しているシンボルの一覧を出力する
概要
「--symbol-report」オプションを使うことで、各ユニットが使用しているシンボルの一覧を.symbol_reportファイルとして出力できるようになります。
方法
「プロジェクトオプション」の「コンパイラに渡す追加オプション」に「--symbol-report」を指定します。
正しく指定されていれば、ビルド後にユニットの出力ディレクトリに対して.symbol_reportファイルが生成されます。
サンプル
新規フォームを生成し、そのままビルドを行った場合、このようなファイルが生成されます。
<?xml version="1.0" encoding="UTF-8"?> <unit name="Unit1"> <uses name="Vcl.Dialogs"> </uses> <uses name="Vcl.Forms"> <symbol name="TForm"/> <symbol name="TCustomForm.AlignControls"/> <symbol name="TCustomForm.ClientWndProc"/> <symbol name="TCustomForm.CreateParams"/> <symbol name="TCustomForm.CreateWindowHandle"/> <symbol name="TCustomForm.CreateWnd"/> <symbol name="TCustomForm.DefineProperties"/> <symbol name="TCustomForm.DestroyHandle"/> <symbol name="TCustomForm.DestroyWindowHandle"/> <symbol name="TCustomForm.DoCreate"/> <symbol name="TCustomForm.DoDestroy"/> <symbol name="TCustomForm.DoThumbButtonNotify"/> <symbol name="TCustomForm.DoWindowPreviewRequest"/> <symbol name="TCustomForm.DoThumbPreviewRequest"/> <symbol name="TCustomForm.GetClientRect"/> <symbol name="TCustomForm.GetFloating"/> <symbol name="TCustomForm.Loaded"/> <symbol name="TCustomForm.Notification"/> <symbol name="TCustomForm.PaintWindow"/> <symbol name="TCustomForm.GetDesignDpi"/> <symbol name="TCustomForm.ScaleForCurrentDpi"/> <symbol name="TCustomForm.ReadState"/> <symbol name="TCustomForm.RequestAlign"/> <symbol name="TCustomForm.SetParentBiDiMode"/> <symbol name="TCustomForm.SetParent"/> <symbol name="TCustomForm.UpdateActions"/> <symbol name="TCustomForm.ValidateRename"/> <symbol name="TCustomForm.WndProc"/> <symbol name="TCustomForm.Resizing"/> <symbol name="TCustomForm.QueryInterface"/> <symbol name="TCustomForm.Create"/> <symbol name="TCustomForm.CreateNew"/> <symbol name="TCustomForm.Destroy"/> <symbol name="TCustomForm.ScaleForPPI"/> <symbol name="TCustomForm.CloseQuery"/> <symbol name="TCustomForm.DefaultHandler"/> <symbol name="TCustomForm.SetFocus"/> <symbol name="TCustomForm.SetFocusedControl"/> <symbol name="TCustomForm.ShowModal"/> <symbol name="TCustomForm.WantChildKey"/> <symbol name="TCustomForm.AfterConstruction"/> <symbol name="TCustomForm.BeforeDestruction"/> <symbol name="TScrollingWinControl.AdjustClientRect"/> <symbol name="TScrollingWinControl.AutoScrollEnabled"/> <symbol name="TScrollingWinControl.AutoScrollInView"/> <symbol name="TScrollingWinControl.DoGesture"/> <symbol name="TScrollingWinControl.DoGetGestureOptions"/> <symbol name=".TForm"/> </uses> <uses name="Vcl.Controls"> <symbol name="TWinControl.UpdateStyleElements"/> <symbol name="TWinControl.AsyncSchedule"/> <symbol name="TWinControl.AssignTo"/> <symbol name="TWinControl.CanAutoSize"/> <symbol name="TWinControl.CanResize"/> <symbol name="TWinControl.ConstrainedResize"/> <symbol name="TWinControl.CreateHandle"/> <symbol name="TWinControl.CustomAlignInsertBefore"/> <symbol name="TWinControl.CustomAlignPosition"/> <symbol name="TWinControl.DestroyWnd"/> <symbol name="TWinControl.DockReplaceDockClient"/> <symbol name="TWinControl.GetClientOrigin"/> <symbol name="TWinControl.GetControlExtents"/> <symbol name="TWinControl.GetDeviceContext"/> <symbol name="TWinControl.ResetIme"/> <symbol name="TWinControl.ScaleControlsForDpi"/> <symbol name="TWinControl.SetIme"/> <symbol name="TWinControl.SetParentBackground"/> <symbol name="TWinControl.SetParentDoubleBuffered"/> <symbol name="TWinControl.ShowControl"/> <symbol name="TWinControl.UpdateControlOriginalParentSize"/> <symbol name="TWinControl.UpdateTIPStatus"/> <symbol name="TWinControl.Invalidate"/> <symbol name="TWinControl.Repaint"/> <symbol name="TWinControl.SetBounds"/> <symbol name="TWinControl.Update"/> <symbol name="TControl.GetParentCurrentDpi"/> <symbol name="TControl.CreateTouchManager"/> <symbol name="TControl.GetAction"/> <symbol name="TControl.GetEnabled"/> <symbol name="TControl.GetFloatingDockSiteClass"/> <symbol name="TControl.DefaultScalingFlags"/> <symbol name="TControl.SetAutoSize"/> <symbol name="TControl.SetDragMode"/> <symbol name="TControl.SetEnabled"/> <symbol name="TControl.SetName"/> <symbol name="TControl.SetBiDiMode"/> <symbol name="TControl.SetStyleElements"/> <symbol name="TControl.GetDragImages"/> <symbol name="TControl.InitiateAction"/> </uses> <uses name="Vcl.Graphics"> </uses> <uses name="System.Classes"> <symbol name="TComponent.CanObserve"/> <symbol name="TComponent.ObserverAdded"/> <symbol name="TComponent.GetObservers"/> <symbol name="TComponent.UpdateRegistry"/> <symbol name="TComponent.WriteState"/> <symbol name="TComponent.SafeCallException"/> <symbol name="TComponent.UpdateAction"/> <symbol name="TPersistent.Assign"/> </uses> <uses name="System.Variants"> </uses> <uses name="System.SysUtils"> </uses> <uses name="Winapi.Messages"> </uses> <uses name="Winapi.Windows"> </uses> <uses name="SysInit"> </uses> <uses name="System"> <symbol name="TObject.Equals"/> <symbol name="TObject.GetHashCode"/> <symbol name="TObject.ToString"/> <symbol name="TObject.Dispatch"/> <symbol name="TObject.NewInstance"/> <symbol name="TObject.FreeInstance"/> </uses> </unit>
TJsonSerializerの実用例
TFormなどを継承したクラスの場合
例えば起動時と終了時にフォームの位置を記憶・復元することを考えた場合、TJsonSerializerにTForm1などのインスタンスを渡すことになりますが、デフォルトでは余計なデータまで保存されてしまうため、保存項目を自分で指定するためにまずTForm1にJsonSerialize(TJsonMemberSerialization.In)というカスタム属性を指定し、次に保存したいメンバにJsonIn属性を持たせることで任意の項目のみをシリアライズすることになります。
しかし自分で定義した型と違い、TFormの派生クラスでは親クラスより上の段階でLeftやTopといったプロパティが定義されているため、ぱっと見属性を書く場所がありません。そういった場合はプロパティを再定義し、そこに属性を記述します。
type TForm1 = class(TForm) procedure FormCreate(Sender: TObject); procedure FormClose(Sender: TObject; var Action: TCloseAction); published [JsonIn] property Left; // <==再定義 [JsonIn] property Top; // <==再定義 end; procedure TForm1.FormCreate(Sender: TObject); var serializer: TJsonSerializer; begin if FileExists('config.json') then begin serializer := TJsonSerializer.Create; try serializer.Populate(TFile.ReadAllText('config.json', TEncoding.UTF8), Self); finally serializer.Free; end; end; end; procedure TForm1.FormClose(Sender: TObject; var Action: TCloseAction); var serializer: TJsonSerializer; begin serializer := TJsonSerializer.Create; try TFile.WriteAllText('config.json', serializer.Serialize(Self), TEncoding.UTF8); finally serializer.Free; end; end;
ちなみに属性を何も指定せずにTFormのインスタンスをシリアライズしようとすると、スタックオーバーフローが発生します。これはTFormのプロパティを辿っていくと再び自身(TForm)が出てくる場合があり、無限ループになってしまうせいです。そのため必ず属性による制御が必要です。
定義済みの型の一部のみをシリアライズしたい場合
先の例は継承したクラスがあるため自分で属性付けが可能でしたが、継承せずそのまま使うクラスであったり、そもそも継承が不可能なレコードの場合、直接カスタム属性を付与することはできません。この場合は少し面倒になりますが、TJsonDynamicContractResolverを使用して動的に属性を付与することになります。
例えばTRectをシリアライズしようと考えた場合、そのままシリアライズするとLeft, Top, Right, Bottomだけではなく、可変部分であるTopLeftとBottomRightも一緒に出力されてしまいます。そこでLeft, Top, Right, Bottomのみをシリアライズするような場合を考えます。
var rect: TRect; serializer: TJsonSerializer; resolver: TJsonDynamicContractResolver; begin rect.Left := 100; rect.Top := 200; rect.Right := 300; rect.Bottom := 400; serializer := TJsonSerializer.Create; try resolver := TJsonDynamicContractResolver.Create; serializer.ContractResolver := resolver; resolver.SetFieldsIgnored(TypeInfo(TRect), ['TopLeft', 'BottomRight']); TFile.WriteAllText('rect.json', serializer.Serialize(rect), TEncoding.UTF8); finally serializer.Free; end; end;
何も指定せずにシリアライズすると{"Left":100,"Top":200,"Right":300,"Bottom":400,"TopLeft":{"X":100,"Y":200},"BottomRight":{"X":300,"Y":400}}となってしまいますが、このコードでシリアライズすると{"Left":100,"Top":200,"Right":300,"Bottom":400}のみが得られます。
なお、ContractResolverプロパティはインターフェース型で、代入した時点で自動管理になるのでresolverを自身で解放する必要はありません。
TJsonSerializerの使い方。
待っててもdocwikiに空ページすら作られる気配が無いので、使い方を調べてみました。
System.JSON.SerializersとSystem.JSON.Convertersユニット
この2つは10.2 Tokyoで新しく追加されたJSON関連のユニットです。2つありますがメインはSystem.JSON.Serializersで、System.JSON.ConvertersはSerializers内で使用するためのコンバータクラス(詳細は後述)が定義されています。
TJsonSerializer
System.JSON.Serializersは名前の通り、JSONにシリアライズ/デシリアライズするためのユニットで、その機能はTJsonSerializerクラスに集約されています。関連するカスタム属性を使うことで柔軟かつ複雑な処理を行うこともできますが、必要最低限のコードは以下の通り非常にシンプルです。
uses System.JSON.Serializers; type TFoo = record Field1: Integer; Field2: string; end; procedure Sample; var foo: TFoo; serializer: TJsonSerializer; begin foo.Field1 := 123; foo.Field2 := 'ABC'; serializer := TJsonSerializer.Create; try // シリアライズ ShowMessage(serializer.Serialize(foo)); // -> '{"Field1":123,"Field2":"ABC"}' // デシリアライズ(新規に生成する場合) foo := serializer.Deserialize<TFoo>('{"Field1":456,"Field2":"DEF"}'); ShowMessageFmt('%d, %s', [foo.Field1, foo.Field2]); // -> '456, DEF' // デシリアライズ(既存のものに代入する場合) serializer.Populate('{"Field2":"XYZ"}', foo); ShowMessageFmt('%d, %s', [foo.Field1, foo.Field2]); // -> '456, XYZ' finally serializer.Free; end; end;
カスタム属性による制御
基本的な使い方が分かったところで、次はカスタム属性を使ったより便利な使い方を紹介します。シリアライズ/デシリアライズの際に使用可能なカスタム属性はこのように複数あります。
カスタム属性 | 用途 | デフォルト値 |
---|---|---|
JsonConverterAttribute | コンバータの指定 | なし |
JsonIgnoreAttribute | 無視するメンバの指定 | なし |
JsonNameAttribute | メンバ名を別名で出力する | なし(元の識別子) |
JsonInAttribute | シリアライズするメンバの指定(TJsonMemberSerializationがInの時のみ有効) | なし |
JsonObjectHandlingAttribute | クラス型メンバをデシリアライザ側で生成するかどうか | TJsonObjectHandling.Auto |
JsonObjectOwnership | デシリアライザでクラス型メンバを生成した場合の元のインスタンスをどうするか | TJsonObjectOwnership.Auto |
JsonSerializeAttribute | シリアライズ/デシリアライズするメンバの選び方 | TJsonMemberSerialization.Fields |
なお、カスタム属性は例えばFooAttributeという名前の場合、Attribute部分を省いてFooとすることが可能なため、以降では省略して記述します。
JsonSerialize属性
このカスタム属性は型に直接指定するもので、メンバのうちどれをシリアライズ/デシリアイズするかの方法を指定します。指定時は列挙型であるTJsonMemberSerialization型の引数を取り、Fields, Public, Inの3つが指定できます。
例えば以下のようなレコードTFooとクラスTBarを考えます。
type [JsonSerialize(TJsonMemberSerialization.Fields)] TFoo = record private FValue1: Integer; public Value2: Integer; property Value3: Integer read FValue1 write FValue1; end; TBar = class private FValue1: Integer; public Value2: Integer; property Value3: Integer read FValue1 write FValue1; end;
これらの型にTJsonMemberSerialization.Fieldsを指定した場合、あるいは何も指定しなかった場合(=デフォルト)、TJsonSerializerは全てのフィールドを処理対象とします。この場合TFooもTBarも同じでFValue1とFValue2が該当します。フィールドのRTTIは可視性にかかわらず全て生成されるため、privateでもpublicでも処理対象となりますが、$RTTI指令でこれが変更されていた場合、TJsonSerializerの処理対象もそれに追随します。
次にTJsonMemberSerialization.Publicを指定した場合を考えます。この場合可視性がpublicなものを処理対象とするため、TFooもTBarでもValue2とValue3が対象となりそうなものですが、実はレコードのプロパティはRTTIが生成されないため、実際にはTFooはValue2のみ処理対象となります。プロパティを持ったレコードを扱う際は注意が必要です。
最後にTJsonMemberSerialization.Inを指定した場合です。これは前の2つと異なり、指定しただけでは何も起きません。このモードではJsonInというカスタム属性と組み合わせることで、任意のメンバを対象とします。例えば以下のようなクラスがあった場合、JsonInを付けたメンバのみがシリアライズ/デシリアライズの対象となります。この場合FValue1とValue3です。
type [JsonSerialize(TJsonMemberSerialization.In)] TBar = class private [JsonIn] FValue1: Integer; FValue2: string; public [JsonIn] property Value3: Integer read GetValue3 write SetValue3; end;
JsonIgnore属性
このカスタム属性はシリアライズ/デシリアライズをしないメンバに対して指定します。TJsonMemberSerializationのFieldsとPublicは細かい指定ができないため、この属性と組み合わせることで対象となるメンバを調整します。仕様上JsonInと組み合わせで使うこともできますが、動作としてはJsonIgnoreの方が優先されるため、両方する指定する意味はあまりありません。
JsonName属性
このカスタム属性はJSONのキー名を実際のメンバ名とは別のものにする際に指定します。例えば以下のようなレコードを考えます。
type TFoo = record [JsonName('Id')] Field1: Integer; [JsonName('Value')] Field2: string; end;
このレコードをシリアライズすると、{"Id": 123, "Value": "abc"}のようなJSONが得られます。デシリアライズ時も同様に機能します。
JsonObjectHandling属性
このカスタム属性は引数に列挙型のTJsonObjectHandlingを取り、デシリアライズ時のクラス型メンバの生成方法を制御しますが、Deserializeメソッドは無条件で全て新規に生成するため、この属性はPopulateメソッド専用です。
動作モードは3種類あり、Reuseは新しくインスタンスを生成することなく渡されたものをそのまま使います。ただし対象のメンバがnilだった場合は新しく生成します。Replaceは対象のメンバの中身にかかわらず新しく生成した上で代入します。その際に元々入っていたインスタンスの扱いは後述のJsonObjectOwnershipで指定します。Autoは単にカスタム属性とは別にTJsonSerializerが持つObjectHandling設定を使うというだけです。コンポーネントでいうParentColorやParentFontプロパティのようなもので、親設定に従う、というものです。ただTJsonSerializerの初期値もAutoのため、特に変更のない場合Reuseとして動作します。
JsonObjectOwnership属性
このカスタム属性はDeserializeやPopulateで新しくインスタンスが生成された時、元の値を解放するかどうかを決定します。JsonObjectHandlingにReplaceが指定された時にだけ意味を持つ属性です。
動作モードは3種類あり、Ownedは自身が所有権を持つということで、新しいインスタンスが代入された時に元からあったインスタンスを解放します。NotOwnedは代入されても何もしません。AutoはJsonObjectHandlingと同じくTJsonSerializerのObjectOwnershipプロパティに従います。TJsonSerializerの初期値もAutoのため、その場合は実質的にOwnedとして動作します。
…のはずなんですが、動作しません。何を指定してもAuto扱いになってしまうようです。ドキュメントがないため使い方が間違っている可能性もありますが、ソースを見る限りではTJsonDefaultContractResolver.SetPropertySettingsFromAttributes内でこの属性だけ受け渡しがされていないのが原因のような気がします。ただしAutoの場合上述のようにTJsonSerializerの設定を使いますが、ここでの指定は問題ないためNotOwnedが指定したい場合はこれしか方法が無さそうです。ただし全体に適用されてしまうため注意が必要です。
JsonConverter属性
このカスタム属性は引数にコンバータクラスを取り、複雑な構造を持つ型をシリアライズ/デシリアライズしたり、デフォルトの処理とは違う方法でシリアライズ/デシリアライズする際の動作を指定します。
コンバータクラスはTJsonConverterクラスを継承して作成しますが、ある程度使用頻度が高そうなものはSystem.JSON.Convertersユニット内にあらかじめ実装されています。例えばTJsonEnumNameConverterは列挙型のシリアライズに、TJsonStackConverter
TJsonSerializerのプロパティ
TJsonSerializerにはオプションが複数あり、これを指定することでシリアライズ/デシリアライズの動作を変更することが可能です。全てをテストしたわけではないため、説明はソースからの推測を含みます。
プロパティ名 | 説明 | 取り得る値 |
---|---|---|
DateFormatHandling | 日付のフォーマット(シリアライズ時のみ) | Iso(デフォルト), Unix, FormatSettings |
DateParseHandling | TDateTime形式としてパースするか否か(デシリアライズ時のみ) | None(デフォルト), DateTime |
DateTimeZoneHandling | TDateTimeのタイムゾーン設定 | Local(デフォルト), Utc |
FloatFormatHandling | NaNなどの特殊な小数値の出力設定 | String(デフォルト), Symbol, DefaultValue |
Formatting | インデント設定(シリアライズ時のみ) | None(デフォルト), Indented |
MaxDepth | 読み取るネストの深さ(デシリアライズ時のみ) | Integer(デフォルト=-1(無制限)) |
ObjectHandling | JsonObjectHandling属性の全体設定 | Auto(デフォルト), Reuse, Replace |
ObjectOwnership | JsonObjectOwnership属性の全体設定 | Auto(デフォルト), Owned, NotOwned |
StringEscapeHandling | 文字列のエスケープ(シリアライズ時のみ) | Default(デフォルト), EscapeNonAscii, EscapeHtml |
実行時にカスタム属性を変更
カスタム属性は通常設計時に指定するため、ある時はFieldAとFieldBを、ある時はFieldAとFieldCを…というように、実行時に出力したいメンバを変更することができません。このような時はTJsonDynamicContractResolverを使用することで、型やメンバに紐付いたカスタム属性を実行時に動的に変更が可能なようです。
これについてはまだ実際に使用していないので憶測になってしまいますが、TJsonSerializerにContractResolverというIJsonContractResolver型のプロパティがあり、ここにTJsonDynamicContractResolverのインスタンスを代入して使用するようです。動作原理としてはTJsonSerializerがシリアライズする際、カスタム属性をIJsonContractResolver経由で取得するようなのですが、その際にTJsonDynamicContractResolverであらかじめ上書きされたカスタム属性を本来のカスタム属性より優先的に返すことで、実行時にカスタム属性の付け替えを擬似的に行っているようです。
Delphi 10.2 Tokyoのdocwikiに書かれてなさそうな変更点。
ざっくりと見ただけなので全体を網羅できてないですし、10.1以前での変更点も含むかもしれない程度に適当ですのであらかじめご了承下さい。
System.SysUtils
IntToHexのoverloadが増えた
具体的にこんなのが増えてます。それぞれ引数の型サイズ×2の桁数で表示されるようです。
function IntToHex(Value: Int8): string; overload; inline; function IntToHex(Value: UInt8): string; overload; inline; function IntToHex(Value: Int16): string; overload; inline; function IntToHex(Value: UInt16): string; overload; inline; function IntToHex(Value: Int32): string; overload; inline; function IntToHex(Value: UInt32): string; overload; inline; function IntToHex(Value: Int64): string; overload; inline; function IntToHex(Value: UInt64): string; overload; inline;
文字列を32bit符号なし整数に変換できるようになった
以下の3つの関数が追加されてます。今までStrToIntはあったんですが、符号付きの範囲しか変換できなかったため、$80000000以上$FFFFFFFF以下を変換しようと思うとStrToInt64などを使わざるを得ませんでした。
function StrToUInt(const S: string): Cardinal; overload; function StrToUIntDef(const S: string; Default: Cardinal): Cardinal; overload; function TryStrToUInt(const S: string; out Value: Cardinal): Boolean; overload;
System.Classes
TStreamが64bitサイズの読み書きに対応
以前まではReadメソッドやWriteメソッドのCount引数の型がIntegerだったため、どうあがいても一度に読み書きできるのは32bit範囲に限定されていましたが、今回Read64とWrite64というCountの型がInt64になったメソッドが新設されたことでその制限が無くなりました。ただし形無し引数版は無く、対応してるのはTBytesの読み書きだけです。
同時に、TStreamのReadBufferやWriteBuffer、それに関連してTReaderやTWriter、TParserなどの中にあるCount引数を持つメソッドで、Countの型がIntegerからNativeIntになりました。これはoverloadされたメソッドが新設されたわけではなく既存メソッドの仕様変更なので、場合によっては注意する必要がありそうです。
Countの件とは関係ないですが、TStreamにはReadData
TMemoryStreamのCapacityの仕様が変更
具体的にはアクセサであるSetCapacityメソッドがprivateからprotectedに移動し、virtual化してます。
またTStreamの64bit対応に合わせてか、Capacityの型もLongintからNativeIntになったので、64bitアプリではより大きなサイズを扱えるようになりました。
System.JSON.ConvertersとSystem.JSON.Serializersユニットが追加
こういうボリューミーな新機能こそdocwikiに書いておいて欲しいですが、なんか追加されたみたいです。
Serializersの方はJsonIgnoreAttributeやJsonNameAttributeみたいなカスタム属性があり、シリアライズする際の挙動を細かく指定できるようでなかなか便利そうです。だからちゃんと説明して下さい>エンバカデロ
Vcl.ExtCtrls
TImage.OnFindGraphicClassイベント
TImage.Pictureに画像を読み込む際、今までは拡張子やクリップボードの内容をVCLが自動で判定して適切なTGraphic派生クラスを使用していましたが、このイベントを使うことでそれを制御できるようになったようです。さらにTPictureにLoadFromStreamが新設され、その読み込み判定にも使用されます。
また、関連してTGraphicにCanLoadFromStreamというクラスメソッドが追加され、これを使うことで事前にロード可能か判定できるようになったようです。ざっと見たところ全てのTGraphic派生クラスで実装されているようなので、気にせず使っても大丈夫そうです。