泥庭

2013年12月13日

WindowsストアアプリでReflection

Filed under: .NET, ストアアプリ, Windows8 — タグ: , — yone64 @ 12:05 AM

この記事は、C# AdventCalendarの13日目の記事です。なお、あまり関係ありませんが、「メタプログラミング.NET」におおいに影響を受けています。
# 元々は、台風で流れた、10月開催のめとべやのLT用のネタだったり。

突然ですが、ストアアプリでもReflectionしたいですよね(当然)。で、とりあえず、いつも通りTypeクラスを取得して、ゴニョゴニョしようとすると

image

「あれ?メソッドが少ない。いつも使うGetMethodやGetPropertyがない」となるわけです。

(ちなみにいつものは↓な感じ)
image

これは、「Windows ストア アプリ用 .NET の概要」のページにも書いてあるとおりで、System.TypeからSystem.Reflection.TypeInfoに移動したとのこと。ちなみに、今までのTypeクラスはSystem名前空間にいるにもかかわらず、がっつりSystem.Reflection名前空間に依存していて、これを解決するための変更らしい。

image

というわけで、VisualStudio上でTypeクラスの定義をみてみると今までと定義が全く違うことがわかりますね。
image

(いつもの)
image

では元に戻ります、type.GetTypeInfo()でTypeInfo型をとれば良いことまでわかりました。
# ちなみにGetTypeInfo()メソッドは拡張メソッドであるため、using System.Reflectionを追加しないと出てこないの要注意です。
# (はまりました。

image

後は簡単。ちなみに、TypeInfoの各メンバはIEnumerable<T>を返すので、LINQと相性が良いです。
# LINQと言えば、奥様、明日MS大阪で
LINQ勉強会があるらしいですよ。(ステマ

var str = "Reflection!";
var result = str.GetType().GetTypeInfo().DeclaredProperties
                .First(p => p.Name == "Length").GetValue(str);

// GetRuntimeほげほげって拡張メソッドもあるので、こっちでもいいかも
//var result = str.GetType().GetRuntimeProperties()
//    .First(p => p.Name == "Length").GetValue(str);

textBlock1.Text = result.ToString();

さて、ここで新たな疑問が!!

ストアアプリとデスクトップアプリでTypeクラスが全然別物になってるって事が分かったわけですが、その両方から呼出し可能はPCLでは一体どうなってるのでしょう。

答えは大概MSDNを見れば分かるようにできていて、例えば

image
こんな感じ。緑の鞄がストアアプリで、WindowみたいなのがPCL。

つまり、GetConstructorメソッドは、ストアアプリからは呼び出せないけど、ストアアプリから呼び出し可能なPCLからは呼出し可能って事が分かります。

「えっと、それってどういうこと?Typeクラスってどうなってるの?」
余計に気になりますね。

仕方がないので、↓なコードを書いてみるのです。

var result = typeof(Type).GetTypeInfo().Assembly.FullName;
textBlock1.Text = result;

実行してみると、衝撃の事実が!!!!

image

「あれれ?mscorelib?System.Runtime.dllは、どこいった?」

衝撃の事実ってほどでもないですけど、つまりコンパイル時にはSystem.Runtime.dllを参照してるけど、実行時には結局今までのdllを見てるって事ですね。これは、参照アセンブリという仕組みらしいです。詳細な解説が、C#でググれなお方のページにありました。

特に落ちもなく、以上終了でもいいんですが。ここまでの情報を踏まえると、もう一つ試してみたいことが出てきますよね?

ストアアプリは、動的なDLLロードとかReflection.Emitとかはメソッドがなくなってて出来ないという話だが、デスクトップとmscorelibを共有しているのであれば、Reflectionで呼び出せば実は出来るんじゃないか疑惑が…。コレハタメシテミルシカ。なわけですよ。

用意いたしましたるは、HelloWorldを表示するだけの簡単なプログラム。

var aName   = new AssemblyName("MyAsm");
var ab = AppDomain.CurrentDomain.DefineDynamicAssembly(aName, AssemblyBuilderAccess.Run); 
var mb = ab.DefineDynamicModule("HelloWorld");
var tb = mb.DefineType("Hello", TypeAttributes.Public);
var mAttr    = (MethodAttributes.Public | MethodAttributes.Virtual);
var meB = tb.DefineMethod("Hello", mAttr, typeof(void), new Type[]{});
ILGenerator il = meB.GetILGenerator();
il.Emit(OpCodes.Ldstr, "Hello World");
il.Emit(OpCodes.Call, typeof(Console).GetMethod("WriteLine", new Type[]{ typeof(string) }));
il.Emit(OpCodes.Ret);
Type type = tb.CreateType();
dynamic hello = Activator.CreateInstance(type);
hello.Hello();

これをストアアプリで動かしてみましょう。とりあえずCurrentDomainを取得するところまでで、いったん実行。

var asmName = new AssemblyName("MyAssembly");
// AppDomainがないので
var domain = typeof(Type).Assembly.GetType("System.AppDomain").GetRuntimeProperty("CurrentDomain").GetValue(null);

image

見事に落ちました。そうですよね。素人が思いつきそうな所は塞いでますよね。このほかAssemble.LoadFromとかProcess.Startも試してみたのですが当然ダメでした。
# ストアアプリは部分信頼で動作しているから、コードアクセスセキュリティーで引っかかりそうなメソッドの呼出しは出来ないんでしょうね(想像

というわけで、今年のC# AdventCalendarでした。

P.S.
明日は
MS大阪へGo!(大事なことなので、以下略

資料作らねば(死

WordPress.com Blog.