はじめに
今回はPreserveAttribute
属性を用いてストリッピングを防止する方法について紹介したいと思います。
docs.unity3d.com
概要
まずはコードストリッピングについて概要を載せておきます。
マネージドコードのストリッピングはビルドから未使用のコードを削除し、最終ビルドサイズを大幅に削減します。
マネージコードストリッピングは、プロジェクトの C# スクリプトからビルドされたアセンブリ、パッケージとプラグインの一部であるアセンブリ、.NET Framework のアセンブリなどのマネージアセンブリからコードを削除します。
しかしリフレクションを利用した際に、削除されたくないコードが削除されてしまうといった問題がよく発生してしまいます。(特にDI周り)
その対策としてUnityではPreserveAttribute
というものが存在し、属性をつけることでコードストリッピングを防止することができます。
PreserveAttribute はクラス、メソッド、フィールド、プロパティーを削除することでバイトコードのストリッピングを防止します。
When you create a build, Unity will try to strip unused code from your project. This is great to get small builds. However, sometimes you want some code to not be stripped, even if it looks like it is not used. This can happen for instance if you use reflection to call a method, or instantiate an object of a certain class. You can apply the [Preserve] attribute to classes, methods, fields and properties. In addition to using PreserveAttribute, you can also use the traditional method of a link.xml file to tell the linker to not remove things. PreserveAttribute and link.xml work for both the Mono and IL2CPP scripting backends.
// DeepL翻訳
PreserveAttribute はクラス、メソッド、フィールド、プロパティーを削除することでバイトコードのストリッピングを防止します。
ビルドを作成するとき、Unityはプロジェクトから未使用のコードを取り除こうとします。これは小さなビルドを作成するのには最適です。しかし、たとえ使われていないように見えても、いくつかのコードを削除したくない場合があります。例えば、リフレクションを使ってメソッドを呼び出したり、特定のクラスのオブジェクトをインスタンス化したりする場合です。[Preserve]属性は、クラス、メソッド、フィールド、プロパティに適用できます。PreserveAttributeを使用するだけでなく、link.xmlファイルという伝統的な方法を使用して、リンカーに削除しないように指示することもできます。PreserveAttributeとlink.xmlは、MonoとIL2CPPの両方のスクリプト・バックエンドで動作します。
使い方
[Preserve]
をクラス・メソッド・フィールド・プロパティにつけることで、コードストリッピングを防止できます。
public class Test : MonoBehaviour { private void Start() { typeof(Test).GetMethod("Hoge", BindingFlags.NonPublic | BindingFlags.Static)?.Invoke(null, null); } // リフレクションで利用されていることをコンパイル時点で検知できないので、誤ってコードストリッピング(削除)されてしまう [Preserve] private static void Hoge() { Debug.Log("Hoge!"); } }
よくある例として、リフレクションでしか利用していないメソッドが誤ってコードストリッピングされてしまうことがあります。特にDI周りですね。そんな場合に[Preserve]
を利用すると良いです。
実験
本当に未使用なコードが削除されるか確かめてみましょう。
実際に適当なメソッドを用意してみてビルドして試してみます。またScripting Backend
はMono
を利用し、Managed Stripping Level
はHigh
に設定しています。
// 実際のコード namespace Main { public class Sample : MonoBehaviour { [Preserve] public void HogeWithPreserve() { } public void HogeWithNoPreserve() { } } }
ビルドした後のDll
を逆コンパイルして中身を調べてみます。
// 逆コンパイルしたコード namespace Main { public class Sample : MonoBehaviour { [Preserve] public void HogeWithPreserve() { } } }
[Preserve]
を付けていないメソッドはコードストリッピングされ、付けていたメソッドが残っていることが確認できます。
UnityEngine.dllに依存しないために
For 3rd party libraries that do not want to take on a dependency on UnityEngine.dll, it is also possible to define their own PreserveAttribute. The code stripper will respect that too, and it will consider any attribute with the exact name "PreserveAttribute" as a reason not to strip the thing it is applied on, regardless of the namespace or assembly of the attribute.
// DeepL翻訳
UnityEngine.dllに依存したくないサードパーティライブラリについては、独自のPreserveAttributeを定義することも可能です。コードストリッパーはそれを尊重し、アトリビュートの名前空間やアセンブリに関係なく、"PreserveAttribute "という正確な名前を持つアトリビュートを、それが適用されるものをストリップしない理由として考慮します。
Scripting.PreserveAttribute - Unity スクリプトリファレンス
例えば以下のような独自の属性を定義してみます。
// 実際のコード using System; using UnityEngine; namespace Main { public class Sample : MonoBehaviour { [Preserve] public void HogeWithPreserve() { } public void HogeWithNoPreserve() { } } public class PreserveAttribute : Attribute { public PreserveAttribute() { } } }
UnityEngine.Scripting.PreserveAttribute
を利用していませんが、どうなるのか確認してみましょう。
// 逆コンパイルしたコード namespace Main { public class Sample : MonoBehaviour { [Preserve] public void HogeWithPreserve() { } } }
// 逆コンパイルしたコード namespace Main { public class PreserveAttribute : Attribute { } }
しっかりと[Preserve]
の役割を果たしてくれていました。