更新
- 2020/02/03 (Mon) 17:40 根本的な対処方法を教えて頂いたので追記
- 2020/02/03 (Mon) 21:50 Unity のバージョン違いによる差異を追記
- 2020/02/05 (Wed) 15:10 PreserveAttribute について追記
- 2020/02/05 (Wed) 15:10 Release Notes に記載されている内容について追記
はじめに
Zenject (Extenject) 便利ですよね。大好きです。
IL2CPP 便利ですよね。凄い技術だと思います。
さて、この記事をお読みの皆様は、 Zenject (Extenject) と IL2CPP が組み合わさると、意外と大変なことになりやすい というコトをご存じでしょうか? *1
具体的には、Unity による Managed bytecode stripping with IL2CPP の地雷を思いっきり踏み抜くことが多くなるのです。
今日はその辺のお話しを少し纏めてみたいと思います。
TL; DR (2020/02/03 追記)
Unity 2019.3 を使って、コンストラクタに [Inject]
付ければ解決します!
前提
- Scripting Backend が IL2CPP である
- 最近だとほぼ全てのプラットフォームが該当するかな?
- Editor では問題なく動いており、「 何故か実機でだけ動かない 」という状態である
エラーとその対処
MissingMethodException: Constructor on type '[type name]' not found.
- Zenject とかの Reflection による動的なアレコレをやっている場合に遭遇しがち
- 特に多いのは、Generics 型を Bind している時とか
- このメッセージの場合は、「Constructor Injection をしたいが、そんなオーバーロードねーよ?」みたいな感じ
- コンストラクタに限らず、 MissingMethodException が出た場合は、これの可能性が高い
MissingMethodException: Constructor on type 'Foo`1[[Bar, Buz, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null]]' not found.
Foo
: クラス名Bar
: 第一引数の型Buz
: Assembly 名
internal static class Preserver { private static void PreserveTypes() { { var _ = new Foo(default); } } }
Attempting to call method '[method name]' for which no ahead of time (AOT) code was generated.
- だいたい interface に生えた Generics なメソッドに値型を投げると起きるコトが多い
- 当該メソッドが IL2CPP コンパイラによって生成されていないコトが原因
- 拡張メソッドとして提供してたりする場合も起きがち
[method name]
が存在するコトを教え込みましょう
Attempting to call method 'Foo::Bar.Buz<Quz>' for which no ahead of time (AOT) code was generated.
internal static class Preserver { private static void PreserveTypes() { { var _ = default(Bar); _.Buz<Quz>(); } } }
2020/02/03 (Mon) 21:50 追記ここから
このエラーについては、Unity 2019.3.0f6 で改善されていることを確認しております。
public interface IFoo { T Get<T>(); } public class Bar : IFoo { public T Get<T>() { // 中身はどうでも良い return default; } } public class Buz { private IFoo Foo { get; } = new Bar(); public void Quz() { Foo.Get<string>(); // string は参照型なので Unity 2019.2 でも Unity 2019.3 でも死なない。 Foo.Get<int>(); // int は値型なので Unity 2019.2 だと死ぬが、Unity 2019.3 だと死なない。 Foo.Get<DateTime>(); // DateTime も値型(構造体)なので…(ry } }
実際、手元の検証プロジェクトではちゃんと動いてて、実プロダクトの方だと死んでて「なんでだー!?」って頭抱えてました。
ふと画面をみた時に、「あれ?検証プロジェクトの Unity は UI がフラット…。というコトは………???」→「っっっ!!!」という感じで会社で叫びそうになったのは内緒です。
2020/02/03 (Mon) 21:50 追記ここまで
2020/02/05 (Wed) 15:10 追記ここから
2019.3 の Release Notes にもそれっぽい記載がありました。
IL2CPP: Handle calls to open delegates on instance methods of value types properly. (1191419)
微妙に内容は違いますが、この辺の絡みで直ったんじゃないかと思われます。
2020/02/05 (Wed) 15:10 追記ここまで
Called BindInterfacesTo for type [class name] but no interfaces were found
- 別に
BindInterfacesTo
に限った話しでもないけど、何らかのライブラリのクラスとかを Bind したい時に起きるコトがある - 基本的にライブラリ側で
link.xml
を提供してくれていればモウマンタイ - 別に Zenject 使っている場合に限った話でも無いんだけどね
Called BindInterfacesTo for type Foo but no interfaces were found
Foo
はBar
という Assembly に属しているモノとする- つまり
Bar.asmdef
があるってコトね
- つまり
<linker> <assembly fullname="Bar" preserve="all"/> </linker>
- 細かくクラスを指定する方法もあるけど、その辺は個々に対応してくれたまへ
2020/02/03 (Mon) 17:40 追記ここから
解決方法
個別に対処するワークアラウンドは上述した通りですが、Zenject (Extenject) を用いている場合に使える魔法のような解決方法があります!
というか、Twitter で @hadashiA さんに以下のリプを頂戴しました。
こんにちは >< ZenjectのREADMEによると、コンストラクタには 明示的に [Inject] アトリビュートをつけるのがstrip回避の策と書かれてましたが、どうなんでしょう…? 🤔 最新の IL2CPP は コンストラクタは保護されるけど、型パラメータが値型なクラスは相変わらず問題があるらしい(?)
— ハダシA (@hadashiA) 2020年2月3日
方法としては以下のように、消されてしまうクラスのコンストラクタに [Inject]
属性を付けるだけ、というとても簡単な方法です。*2
using Zenject; class Foo { [Inject] public Foo(Bar bar) { // (snip) } }
The recommended fix in these cases was to add an [Inject] attribute above the constructor.
実は Extenject の README.md にも上記のように記載があったりするので、知る人ぞ知る(?)解決策のようです。
2020/02/03 (Mon) 17:40 追記ここまで
2020/02/05 (Wed) 15:10 追記ここから
Twitter で @makutatsumako さんに以下のリプをもらいました。
PreserveAttributeじゃダメなんでしたっけ?
— 幕田ツマコ(こまったくま) (@makutatsumako) 2020年2月3日
その先のスレッドでもやりとりしているように、 [Preserve]
でも何とかなりそうな気がします。
ガッツリ調査はしていませんが、理屈的にはコンストラクタに [Preserve]
付ければ良いんじゃないかと。
(当初、class に対して [Preserve]
を付けて検証してしまっていたので、「駄目か…。」と早合点しておりました。)
2020/02/05 (Wed) 15:10 追記ここまで
おわりに
ここ数日、「無限に IL2CPP と格闘しているなぁ…。」とか感じてしまったので、勢いで記事を書いてみました。
「こういうケースもあるよ!」とか「こういう回避方法がオシャレだよ!」とかあれば @monry までメンションなり DM なり飛ばして貰えると、泣いて喜びます。マジで。