🚀

Private ProxyをUnityで実装

2024/01/17に公開

はじめに

このライブラリの解説記事です。
https://github.com/Akeit0/ILAttributes
まずはneue ccさんのこちらの記事をご覧ください。
読んだ前提で話を進めます。
この.NET8移行でしか動かないというこのライブラリをUnityに使えるようにしようということで作りました。

UnsafeAccessorを自前で実装する

.NET8の再現

PrivateProxyは.NET8のUnsafeAccessorという機能を用いているわけですが、2024/1/17現在Unityでは使えません。

[UnsafeAccessor(UnsafeAccessorKind.Field, Name = "_field1")]
static extern ref int ___field1__(Sample target);
[UnsafeAccessor(UnsafeAccessorKind.Method, Name = "PrivateAdd")]
static extern int __PrivateAdd__(Sample target, int x, int y);

しかし、.NET8以前でも使えるみちがあります。それがILとAllow 'unsafe' Codeです。
ILではprivateだろうがinternalだろうがアクセスできます(コンパイラがチェックしないので)。しかし、このままだと実行時エラーがでるのでAllow 'unsafe' Codeで押さえます。

そしてUnityで扱うならILPostProcessorがあるので、Mono.Cecilで関数を書き換えてやれば、OKです。

ILを扱うのはつらいですけど、ここまではそこまで大変ではなかったです。
ここまでの実装ならSource Generatorもほぼそのままでよかったのですが、UnsafeAccessorにある制限、static class やgenericsもILではできそうだったので、できるとこまで実装しようと思いました。

ILPostProcess static class対応

ここは意外と簡単で、UnsafeAccessortypeof(TargetClass)を書き込むだけで、ILPostProcessで受け取るだけで簡単でした。

ILPostProcess Generic関数対応

Generic関数をMono.Cecilで呼び出す方法がちょっと複雑だったので書いておきます。
Mono.Cecilちょっとはわかる人向け
元となるMethodReferenceは普通に作り、ParametersもGenericParametersもこのMethodReferenceに追加する。
その後これをもとにGenericInstanceMethodを作成し、GenericArgumentsはここに追加する。

ILPostProcess Generic Class対応

ここがILPostProcessorで一番躓きました。
そのまま関数定義に用いられたGenericParametersはそのまま使うと、うまくいかず、Generic Classの定義のGenericParametersを使うことでとりあえず動きました。
実装はGenericTypeConverter.csをご覧ください
関数に用いるGenericParametersとGeneric Classの定義のGenericParametersで名前が一致している必要があるので、いやですね、、

しかもこの方法だとGenericMethodと共存できず、Generic class のGeneric Methodは未対応です。誰か解決方法を教えて!!!

PrivateProxyのSourceGeneratorをStatic, Generics 対応させる

static class 対応

これはもういいですよね?static を足して、コンストラクタや、AsPrivateProxy()を消すだけ、簡単。

Generic対応

これがやはり難関でした。
Genericsの\<T\>とかを追加するのはややこしいだけで別に難しくないですけど、つまづきポイントがたくさんありました。

UnboundedGenericな型からはMemberが取れない。

UnboundedGenericな型とはList<>Dictionary<,>みたいなやつですね。
属性に書くときはこの状態で書きますが、GetMembersしても何も取れません。
ITypeSymbol.OriginalDefinitionから定義を持ってくることでMemberがとれます。

GenericParameterはPublicではない

GenericParameterのDeclaredAccessibilityはNotApplicableである。

Generic Constraintsもとれる

Generics対応するならConstraintsも対応しなければなりません。
INamedTypeSymbol.TypeParametersもしくはIMethodSymbol.TypeParametersをとり、
そこからさらにConstraintTypesをとることができます。
GenericParameterでもwhere T: unmanagedするとちゃんとIsUnmanagedがtrueになるので、同様にclassstructもかけました。

Ref Field対応

上記で完成かと思いきや、実はまだUnityでは使えません、、
なぜならstructのPrivateProxyではref fieldを用いて、変更が元に伝わるようになっているのですが、Unityではref fieldが対応してない!!
じゃあILでなんとかして見せましょう。
まず、前にこれを解決する方法は記事にしています。
https://zenn.dev/aakei/articles/3bcc5c11bab60f
しかし、これでは手間がかかります。ライブラリと導入してすぐ解決とはなりません。

そうだByRefernce<T>をFieldにInjectしてしまおう!!となりましたが、ILPostProcessorからは実行時mscorlibが参照できず、mscorlibのinternal classを使うことはできません。

じゃあEditorから実行時mscorlibを渡そうと思うわけですが、そもそも別プロセスなので、受け渡しはファイルと一度経由する必要があります。
ILPostProcessorは別プロセスなのでUnityEditor 名前空間なども使えません。
Pathはどう取得しましょうか。
Project のpathEnvironment.CurrentDirectroryで取得できますので、そこでやり取りします。
上手くいかない状況やより良い方法があったら教えてください

これで実行時mscorlibを取得できたので、ByRefernce<T>を見つけ、自分で定義したByRefernce<T>のFieldDefeinitionに加えて、参照の授受もILで書いて、ついに完成!!

最後に

ライブラリを書いている途中でPCが壊れるトラブルもありつつ、完成まで大変でした。

mscorlibやGenericsなど不満はまだあるので、ご意見ください。
https://github.com/Akeit0/ILAttributes

Discussion