- PR -

MSILでbase.メソッド名の生成方法

1
投稿者投稿内容
sugimoto
常連さん
会議室デビュー日: 2002/12/05
投稿数: 45
投稿日時: 2005-04-15 18:47
杉本と申します。
base.メソッド名のMSILを生成したいのですが、方法が分かりません。
環境は.NET Framework 1.1 で言語はC#です。

ITestDaoというインターフェースがあり、それを実装したTestDaoというクラスがあります
そこで、ITestDaoを実装し、TestDaoを継承したクラスを動的に作成します。
そしてGetTestListというメソッドをオーバーライドして、その中でbase.GetTestListを
呼び出したいのですが、ご存じの方おられないでしょうか?

下記のコードのような感じで試してみたのですが、
記載コードの下から2行目のITestDao dao =〜の部分で次のようなエラーが出ます。
System.TypeLoadException : クラス 'TestDaoImpl' の メソッド tmp13E, Version=0.0.0.0 は異なる virtual 状態で、メソッドの宣言を実装しようとしました。型 : 'TestDaoImpl'。アセンブリ : 'tmp13E, Version=0.0.0.0'。

最終的にはAOPを実現したいのですが・・・よろしくお願い致します。

// クラス作成
TypeBuilder tb = moduleBuilder.DefineType("TestDaoImpl", TypeAttributes.Public);
tb.AddInterfaceImplementation(typeof(ITestDao)); // インターフェースを実装
tb.SetParent(typeof(TestDao)); // TestDaoを継承(これで継承?)
// メソッドの実装
MethodBuilder mb = tb.DefineMethod("GetTestList",MethodAttributes.Virtual | MethodAttributes.Public, typeof(int), new Type[] {typeof(int)});
// MSIL生成
ILGenerator il = mb.GetILGenerator();
il.Emit(OpCodes.Ldc_I4_3);
il.Emit(OpCodes.Call, tb.BaseType.GetMethod("GetTestList",new Type[]{typeof(int)}));
il.Emit(OpCodes.Ret);
MethodInfo mi = typeof(ITestDao).GetMethod("GetTestList");
// オーバーライド
tb.DefineMethodOverride(mb, mi);
ITestDao dao = tb.CreateType().GetConstructor(Type.EmptyTypes).Invoke(null) as ITestDao;
Console.WriteLine(dao.GetTestList(4));
_________________
Kazuya Sugimoto
http://kamedane.com
Hongliang
ぬし
会議室デビュー日: 2004/12/25
投稿数: 576
投稿日時: 2005-04-16 06:51
まず基本の所の確認ですが、MSILで新しく定義するクラスは、ITestDaoを再実装するんですね?
そう言う前提で話を進めます。

TestDaoでは、GetTestListはどう実装されているのでしょうか。
パターンとしては、
  1. public int GetTestList
  2. public virtual int GetTestList
  3. int ITestDao.GetTestList
の3種類が考えられますかね。

この内、まず3番目、インターフェイスの明示的な実装を行っている場合。
派生クラスから基底クラスのインターフェイスの実装を呼ぶ事は基本的にできません。
基底クラスの中でprivateとして宣言される事になりますから。
派生クラスのメソッド内でリフレクションを使う事になります。
詳説は省きますが、MSILを作成するのは面倒になるでしょうね。

残る2つですが、まず非仮想メソッドとして実装した場合の[1.]から見ていきましょう。
非仮想メソッドですから、派生クラスでは同じシグネチャのメソッドはnew付きで宣言しなければなりません。
インターフェイスメンバの再実装とは言え、基底クラスに同じシグネチャのメソッドがありますからね。
この隠蔽を指定する属性がMethodAttributes.NewSlotです。この属性をつける必要があります。
それから、インターフェイスメンバの実装には必ずMethodAttributes.Virtualもつける必要があります。
MethodAttributes.Virtualをつけないと、(シグネチャが同じにもかかわらず)インターフェイスの実装と受け取って貰えず、
ITestDao型でメソッドを実行したときに基底クラスのインターフェイスの実装が使用されてしまいます。
なんとも不思議な状態ですが。

次に仮想メソッドとして実装した場合の[2.]です。
仮想メソッドの場合、派生クラスではオーバーライドするか隠蔽するかの選択肢があります。
インターフェイス型からメソッドを呼ぶ場合、派生クラスでインターフェイスを再実装してるわけですから、どちらにせよ派生クラスで実装したメソッドが呼ばれます。
//基本的には。自分でMethodAttributesを設定する場合、前述のような変な事になる場合があります。
挙動が変わるのは基底型からメソッドを呼ぶ場合ですね。
MethodAttributes.Virtualは当然インターフェイスの実装ですから必要になります。
オーバーライドする場合、MethodAttributes.NewSlotはつけません。
隠蔽する(new付きで宣言する)場合、MethodAttributes.NewSlotが必要です。
実はそれだけの差です。

さて、以上でオーバーライド(またはインターフェイスメンバの実装が)されるかどうかが決まるのであれば、
DefineMethodOverrideは何なのでしょう?
えー、率直に言って私もこの辺はよく分かってませんが、これにはスロットというものが関わるようです。
が、細かい事は気にしない方向で。
MethodAttributesとシグネチャさえしっかりしていればこのDefineMethodOverrideを呼ぶ事はない、と言う事さえ分かってれば。
sugimoto
常連さん
会議室デビュー日: 2002/12/05
投稿数: 45
投稿日時: 2005-04-16 11:46
杉本と申します。Hongliang様ありがとうございます。
すいません。自分が大きな勘違いをしておりました。
引用:

この内、まず3番目、インターフェイスの明示的な実装を行っている場合。
派生クラスから基底クラスのインターフェイスの実装を呼ぶ事は基本的にできません。


できると思っていました・・・

引用:

それから、インターフェイスメンバの実装には必ずMethodAttributes.Virtualもつける必要があります。


こちらの方を試してみると、前回起きていたエラーは起きなくなり、dao.GetTestListメソッド呼び出し時に
System.InvalidProgramException:共通言語ランタイムが無効なプログラムを検出しました。
と出るようになりました。

とりあえず継承のことは忘れて、別の方向で探ろうと思います。
TestDaoクラス(ITestDaoを実装)に次のメソッドがある場合、
int ITestDao.GetTestList(int testCode)
{
Console.WriteLine("TestDao.GetTestListです");
return 1;
}
動的に作成するクラスで(ITestDaoを実装)で次のようなメソッドを動的に作成したいのです
int ITestDao.GetTestList(int testCode)
{
Console.Write("動的に作成したメソッドを開始");
// ここから下はTestDao.GetTestListから作成する
Console.WriteLine("TestDao.GetTestListです");
int ret = 1;
// ここまで
// 勘違いで継承したら int ret = base.GetTestList(testCode);
// で実現できると思っていました。
Console.Write("動的に作成したメソッドを終了");
return ret;
}

このよう動的クラス・動的メソッドをMSILでなんとかかけないでしょうか?
よろしくお願いします。
_________________
Kazuya Sugimoto
http://kamedane.com
Hongliang
ぬし
会議室デビュー日: 2004/12/25
投稿数: 576
投稿日時: 2005-04-16 13:46
ひとまず動的な作成の事はおいておきましょう。
まずは実際にクラスを作ってみる事です。

さて、基底クラスの明示的なインターフェイスの実装を呼び出すところですが。
はっきり言ってしまうと普通にGetMethodでMethodInfoを取得してInvokeするだけです。
ここで問題になるのはどの型からどのメソッドを取得するか、ですね。
型については、基底クラスのメソッドを呼び出すのですから当然typeof(TestDao)と言う事になります。
より動的にするのならtypeof(this).GetType().BaseTypeですかね。
難しいのがどのメソッドか、です。
インターフェイスを明示的に実装する場合、同じクラス内に同じシグネチャの別メソッドが存在する可能性があります。
ILレベルでは、この問題を、明示的実装のメンバ名には名前空間からのインターフェイスの完全限定名をつけ加える事で解決しています。
このことは、インターフェイスの明示的実装を含むクラスに対して
コード:

Type type = typeof(TestDao);
MethodInfo[] infos = type.GetMethods(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance);
foreach (MethodInfo info in infos)
Console.WriteLine(info);


と言うコードを試す事で簡単に実証できます。
つまり、
コード:

typeof(TestDao).GetMethod("NamespaceA.ITestDao.GetTestList", bindingFlags);
//bindingFlagsは.Instance | .NonPublicの組み合わせ。
//もちろんITestDaoにGetTestListのオーバーロードがあるなら引数の型なども指定して取得しなければなりません。


以上でようやく実行すべき基底クラスの明示的実装のMethodInfoが取得できました。
あとはこれをInvokeするだけです。

以上の操作を実際にクラスに書いてコンパイルし、
それをILDASMでどうILに落とされているのかを確認してみて下さい。
あとはそう言う風にEmitしていくだけです。

それからもう一つ、動的にメソッドを作成する際にインターフェイスの明示的実装にするには。
前述の通り、明示的実装はprivateメンバとして扱われます。
つまりMethodAttributes.Privateが必要です。
しかしこのままでは本当にPrivateになってしまって外部からアクセスできなくなります。
このメンバがインターフェイスメンバの明示的実装であると宣言するのが、
前回では不要と言ったDefineMethodOverrideです。
//よく分からないと言っておいてなんですが(w;
実際にILDASMでインターフェイスメンバを明示的に実装しているメンバを見てみて下さい。
スタックサイズの宣言の前にインターフェイスメンバをオーバーライドしているのがわかるでしょう。

[ メッセージ編集済み 編集者: Hongliang 編集日時 2005-04-16 14:03 ]
sugimoto
常連さん
会議室デビュー日: 2002/12/05
投稿数: 45
投稿日時: 2005-04-16 23:38
杉本です。Hongliang様ありがとうございます。
MSIL逆アセンブラツールのIldasm.exeは初めて知りました。これは使えそうですね。

インターフェースは以下のようにしました。
public interface ITestDao
{
int GetTestList(int testCode);
}

TestDaoクラスですが以下のようにしてみました。
public class TestDao : ITestDao
{
// コンストラクタ省略

// int ITestDao.GetTestListとしなくてもインターフェース経由で呼び出せたのでこれにしました
public int GetTestList(int testCode)
{
Console.WriteLine("TestDao.GetTestList");
return 30;
}
}

そしてそれを継承し、ITestDaoを実装したTestDao2を以下のようにしてみました。
public class TestDao2 : TestDao, ITestDao
{
// コンストラクタ省略

public new int GetTestList(int testCode)
{
Console.WriteLine("TestDao2.GetTestList Start");
base.GetTestList(3);
Console.WriteLine("TestDao2.GetTestList End");
return 20;
}
}
以上のようなクラスを用意して
ITestDao dao = new TestDao2();
dao.GetTestList(3);
と実行してみると
TestDao2.GetTestList Start
TestDao.GetTestList
TestDao2.GetTestList End
とコンソールに出力されました。

これでTestDao2を動的に作成することができたら、AOP的な事ができると思うので、
教えていただいたIldasm.exeを使用してILを出力してみました。
TestDao2のGetTestListの部分を見てみると

.method public hidebysig newslot virtual final
instance int32 GetTestList(int32 testCode) cil managed
{
// コード サイズ 35 (0x23)
.maxstack 2
.locals init (int32 V_0)
IL_0000: ldstr "TestDao2.GetTestList Start"
IL_0005: call void [mscorlib]System.Console::WriteLine(string)
IL_000a: ldarg.0
IL_000b: ldc.i4.3
IL_000c: call instance int32 TestAttoContainerLib.Dao.TestDao::GetTestList(int32)
IL_0011: pop
IL_0012: ldstr "TestDao2.GetTestList End"
IL_0017: call void [mscorlib]System.Console::WriteLine(string)
IL_001c: ldc.i4.s 20
IL_001e: stloc.0
IL_001f: br.s IL_0021

IL_0021: ldloc.0
IL_0022: ret
} // end of method TestDao2::GetTestList

これを元に動的に作れるようにコーディングしてみます。
できましたらまた報告します。

教えていただいたように、MethodInfoで取得してinvokeしてみようと思ったのですが、その呼び出すメソッドが静的であれば良いのですが、そうでない場合はちょっと良く分からなくなってきたので、上記のようにやってみました。
_________________
Kazuya Sugimoto
http://kamedane.com
sugimoto
常連さん
会議室デビュー日: 2002/12/05
投稿数: 45
投稿日時: 2005-04-18 18:11
杉本と申します。
.NETでAOPを実現するために、このスレッドを立ち上げさせてもらいました。

しかし、その手段として検討していたMSILですが、対象のクラスを継承したクラスを動的に作成しようとしていた方法は、継承となるといろいろ制約がでて来るので、止めました。

2つ目に検討したのは、AttributeUsage, ContextBoundObjectなんかを使った方法なのですが、ContextBoundObjectを継承しないといけないとか、属性を付けないといけないとか、自分の方向性と違っているので止めました。

3つ目に辿り着いたのは、RealProxyを使った方法です。これはインスタンスを作成するときにプロキシから作成しないといけない弱点があるのですが、そこはDIコンテナで隠すことができます。なので、RealProxyを使った方法でAOPを実現しようと思います。

ありがとうございました。
_________________
Kazuya Sugimoto
http://kamedane.com
sugimoto
常連さん
会議室デビュー日: 2002/12/05
投稿数: 45
投稿日時: 2005-04-22 17:58
杉本と申します。
タイトルの件ですが、以下のようにILでbase.メソッド名を実行できます。
(ilはILGeneratorのインスタンス、tbはTypeBuilderのインスタンス)

// base.メソッド名のMethodInfo
MethodInfo mi2 = tb.BaseType.GetMethod("GetTestList",new Type[]{typeof(int)});
// メソッドを実行するインスタンスをセット、
// 引数の0番目が基本クラスということみたいです
il.Emit(OpCodes.Ldarg_0);
// メソッドに与える引数をセット(これはintの3という意味)
il.Emit(OpCodes.Ldc_I4_3);
// メソッドをcall
il.EmitCall(OpCodes.Call,mi2,null);

_________________
Kazuya Sugimoto
http://kamedane.com

[ メッセージ編集済み 編集者: sugimoto 編集日時 2005-04-22 17:59 ]
1

スキルアップ/キャリアアップ(JOB@IT)