プログラムのコード自体の(動的な)出力を行いたい への返答

投稿で使用できる特殊コードの説明。(別タブで開きます。)
本名は入力しないようにしましょう。
投稿した後で削除するときに使うパスワードです。返答があった後は削除できません。
返答する人が目安にします。相手が小学生か社会人かで返答の仕方も変わります。
最初の投稿が質問の場合、質問者が解決時にチェックしてください。(以降も追加書き込み・返信は可能です。)
※「過去ログ」について書くときはその過去ログのURLも書いてください。

以下の返答は逆順(新しい順)に並んでいます。

投稿者 threecourse  (社会人) 投稿日時 2010/5/22 11:12:29
魔界の仮面弁士さん、どうもありがとうございます。

レジストリに HKEY_CLASSES_ROOT\VisualStudio.DTEはありませんでした。
ProgIDも教えていただき、どうもありがとうございます。
無償版ではやはりだめなようですね、はっきりわかり、助かりました。
有償版を購入することになれば、試してみようと思います。
投稿者 葉月  (社会人) 投稿日時 2010/5/20 21:04:29
魔界の仮面弁士さん、ご回答ありがとうございます。
CodeDomの機能に驚かされました。
他言語にも対応した自作のコンパイラーが作れてしまうわけですね。
他言語への変換を行うサービス(サイト)に、BooやDelphiの変換ができる理由がわかりました。

>この他にも、
VJ#はインストールしてたと思うので使ってみます。
Cω用~は、初めて知りました。
(最初、絵文字かと思いました)
IronPython用~も名称しか知りませんので、
少し時間を取ってwikiで調べてからインストールしてみます。

>既定のファイル名は MsilCodeProvider.DLL です。
お忙しい中、教えてもらいありがとうございます。
早速ダウンロードして、csファイルを見ました。
なかなか手ごわそうですが、サンプルは読めそうなので少しずつ取り組んでいきます。
投稿者 魔界の仮面弁士  (社会人) 投稿日時 2010/5/20 10:44:09
> CodeDomが他の言語へ変換するのに適しているのがわかりました。
CodeDom は、コード生成機能の他にコンパイラ機能も有しています。
葉月さんが紹介された"あにすさんの投稿"がそれにあたりますね。


> せっかくなので、MSILへの変換も試したいのですができません。
MSIL 用の CodeDom は、ソースコードの形でサンプル提供されています。
既定のファイル名は MsilCodeProvider.DLL です。
http://www.microsoft.com/downloads/details.aspx?FamilyId=7E979ED3-416B-43B6-993B-308A160831B6&displaylang=en
(ただし、先のコードだけでは正しく出力されません)


この他にも、
 VJ#用の Microsoft.VJSharp.VJSharpCodeProvider クラス(VJSharpCodeProvider.DLL)
 Cω用の Microsoft.Comega.ComegaCodeProvider クラス(Microsoft.Comega.DLL)
 IronPython用の IronPython.CodeDom.PythonProvider クラス(IronPython.DLL)
なんてのもあります。これをインストールしている場合には試してみて下さい。

また、Microsoft 製以外の言語でも、Boo 言語用の BooCodeProvider クラスや
Borland Delphi用の DelphiCodeProvider クラスなどがあるようです。
投稿者 葉月  (社会人) 投稿日時 2010/5/19 23:52:11
>>CodeDOM を提示したのは、IL バイナリから VB ソースコードへの変換という意図
お忙しい中、サンプルまで作成して頂きありがとうございます。
サンプルを打ち込みながら動かして、CodeDomが他の言語へ変換するのに適しているのがわかりました。
せっかくなので、MSILへの変換も試したいのですができません。
よかったらご回答頂けると嬉しいです。

' CppCodeProviderの参照を追加 
'Dim provider As New Microsoft.VisualC.CppCodeProvider() 

' Micorosoft.JScriptの参照を追加 
'Dim provider As New Microsoft.JScript.JScriptCodeProvider()  


VC++とJScriptは、上記の参照の追加を行い読むことができました。
投稿者 魔界の仮面弁士  (社会人) 投稿日時 2010/5/19 13:23:26
> dte2 = System.Runtime.InteropServices.Marshal.GetActiveObject("VisualStudio.DTE")
Marshal.GetActiveObject メソッドの代わりに、CreateObject 関数を使う手法もあります。

> クラス文字列が無効です (HRESULT からの例外: 0x800401F3 (CO_E_CLASSSTRING))
レジストリに HKEY_CLASSES_ROOT\VisualStudio.DTE のエントリがあるかどうかを
確認してみてください。それが無い環境の場合、呼び出しは失敗することになるでしょう。

なお、Visual Studio の ProgID は、
 VisualStudio.DTE … バージョン非依存のProgID
 VisualStudio.DTE.7 … VS.NET(2002)環境のProgID
 VisualStudio.DTE.7.1 … VS.NET2003環境のProgID
 VisualStudio.DTE.8.0 … VS2005環境のProgID
 VisualStudio.DTE.9.0 … VS2008環境のProgID
 VisualStudio.DTE.10.0 … VS2010環境のProgID
となっています。


> それとも2008 Express Editionでは無理なのでしょうか。
手元に無償版のみの環境が無いため、実際の状況は把握できていないのですが、
Visual Studio SDK 自体が「Standard Edition 以降が必要、Express Edition では不可」と
されていますから、恐らくは、VSX 関連の機能は利用できないであろうかと思います。

Visual Studio 2008 Express Edition
http://www.microsoft.com/downloads/details.aspx?displaylang=ja&FamilyID=3254c868-bcb9-412c-95c6-d100c872ec60

Visual Studio 2008 Shell (Integrated Mode)
http://www.microsoft.com/downloads/details.aspx?displaylang=ja&FamilyID=40646580-97fa-4698-b65f-620d4b4b1ed7

Visual Studio 2008 Shell (Isolated Mode)
http://www.microsoft.com/downloads/details.aspx?displaylang=ja&FamilyId=021B3BEE-B2AD-42A8-854A-C5EAEF69E927

Visual Studio 2008 SDK
http://www.microsoft.com/downloads/details.aspx?displaylang=en&familyid=30402623-93ca-479a-867c-04dc45164f5b
http://msdn.microsoft.com/ja-jp/library/bb166441(VS.90).aspx

Visual Studio Extensibility (VSX) - Code Gallery
http://code.msdn.microsoft.com/vsx
投稿者 魔界の仮面弁士  (社会人) 投稿日時 2010/5/19 10:17:59
>>> 2. それぞれの OpCode を解釈して、CodeDOM へと展開する。
> デリゲートインスタンスを取得したのに、
> 鬼門を通る行為は現実的ではないのが理解できました。

IL バイナリを「実行する」という話と CodeDOM の件は別の話です。
CodeDOM を提示したのは、IL バイナリから VB ソースコードへの変換という意図です。


手順1の概要は先に書いたので、手順3の部分を。

Imports System
Imports System.IO
Imports System.CodeDom

Module Module1
    Sub Main(ByVal args As String())
        'メソッド宣言 
        Dim method As New CodeMemberMethod()
        method.Name = "MyAdd"
        method.Parameters.Add(New CodeParameterDeclarationExpression(GetType(Integer), "iA"))
        method.Parameters.Add(New CodeParameterDeclarationExpression(GetType(Integer), "iB"))
        method.ReturnType = New CodeTypeReference(GetType(Integer))

        '加算処理 
        Dim arg1 As New CodeArgumentReferenceExpression("iA")
        Dim arg2 As New CodeArgumentReferenceExpression("iB")
        Dim expression As New CodeBinaryOperatorExpression( _
            arg1, _
            CodeBinaryOperatorType.Add, _
            arg2)

        '戻り値として返す 
        Dim returnStatement As New CodeMethodReturnStatement(expression)
        method.Statements.Add(returnStatement)


        'ソース化 
        Dim generatedSource As String
        Using sw As New StringWriter()
            Dim provider As New Microsoft.VisualBasic.VBCodeProvider() 'VB用 
            'Dim provider As New Microsoft.CSharp.CSharpCodeProvider() 'C#用 
            'Dim provider As New Microsoft.VisualC.CppCodeProvider() 'VC用 
            'Dim provider As New Microsoft.JScript.JScriptCodeProvider() 'JScript用 
            'Dim provider As New Microsoft.Msil.MsilCodeProvider() 'MSIL用 

            Try
                provider.GenerateCodeFromMember(method, sw, Nothing)
            Catch ex As NotImplementedException
                For Each s As CodeStatement In method.Statements
                    provider.GenerateCodeFromStatement(s, sw, Nothing)
                Next
            End Try

            sw.Flush()
            generatedSource = sw.ToString()
        End Using

        MsgBox(generatedSource)
    End Sub
End Module
投稿者 葉月  (社会人) 投稿日時 2010/5/18 23:07:54
前回のレスが中途半端でしたので続きになります。

>>>無関係とは言いませんが、直接の関連性は見出せませんでした。
ご指摘ありがとうございます。
>>2. それぞれの OpCode を解釈して、CodeDOM へと展開する。
デリゲートインスタンスを取得したのに、
鬼門を通る行為は現実的ではないのが理解できました。

>>>GetILAsByteArray で IL を読み取り、それを DynamicMethod 経由で
>>>呼び出すという行為ですよね
未熟なために難しく考えてしまうので、助言が頂けて助かります。
早速、提示されたサンプルに置き換えて動作を確認いたしました。

>サンプル
Dim fn As Func(Of IntegerIntegerInteger) = AddressOf CHoge.MyAdd
Dim ret As Integer = fn(iA, iB)


>>>これを System.Reflection.Emit.OpCodes に照らし合わせると、下記のような意味になります。
>>>分かりにくいですが、これが「Return iA + iB」の内部処理というわけです。
ILコードが読めるようになると、無駄な処理を省くことができるんですね。
丁寧な説明があったおかげで、私にも読み解くことができました。
00は明らかな無駄な処理ですし、D6・0A・06も必要ないのが理解できました。
魔界の仮面弁士さんのサンプルにある
info.SetCode(New Byte() {&H2, &H3, &HD6, &H2A}, 2)

SetCodeメソッドに必要な処理である02-03-D6-2Aを4バイトのバイナリーで
指定しているのが理解できました。
元のコードを使用して、丁寧に解説頂きましてありがとうございました。
まずはOpCodesクラスを覚えてから、ILGeneratorクラスを使い
サンプルを作っていこうと思います。
投稿者 葉月  (社会人) 投稿日時 2010/5/18 01:49:31
threecourseさん、こんばんは。
ブックマークに登録したので、ここからガッツリ読んでみます。
サイトの紹介ありがとうございました。
投稿者 葉月  (社会人) 投稿日時 2010/5/18 01:40:23
魔界の仮面弁士さん、お世話になっています。
お忙しい中、親身に答えて頂き感謝しています。

CLRやMSILを知る上で重要な資料だと感じました。
私程度のスキルで全体像を理解するのは、富士山が登れずにエベレストに挑戦するぐらい
無謀なことだと思ったので、薦められたところを読みました。
見当違いなことをいいましたら申し訳ありません。

>>>23.2 Blobs and s ignatures" の "LocalVarSig" のあたりを参照してみて下さい。
見当違いだとわかり、質問してよかったです。
SetLocalSignatureとLocalVarSigの関連が少しわかりました。
一通り目を通したのですが、
英語が苦手で訳に自信がないので、別の日にまた取り組みます。

>>>……ただ、何故 &H7, &H2, &H8, &H8 が選択されているのかが読み取れませんでした。
元は別サイトで掲載されていたサンプルを参考に、私が理解したところをVBで書き換えました。
そのため、サンプルを掲載した私にもわからない状態です。
ご指摘のあった通り、info.SetLocalSignature(New Byte() {&H7, &H1, &H8})で動作いたしました。

>>>CHoge.MyAdd メソッドの戻り値を、As Integer から As Object に変更した場合
アドバイスのおかげで――
23.1.16 Element types used in signatures
に書かれている内容がわかりました。

中途半端な返事になってしまいました。
残りの返事は土日にがっつり取り組んで、
理解度を深めてからにいたします。
投稿者 threecourse  (社会人) 投稿日時 2010/5/18 01:06:47
葉月さん、魔界の仮面弁士さん、どうもありがとうございます。
目的は、コードを整形し表示して確認しやすくすることですので、ILはそれには敷居が高いかな、と思います。

頂いたリンクの中で、
.NETにはソースコードを文字列で渡してコンパイル、実行する機能があります。
というのは少し興味深かったです。

ISについては、この辺りを少し見たので、ご参考まで。
http://www.atelier-blue.com/program/il/index.htm
http://www.atelier-blue.com/link/il.htm



また、別に少し調べていると、以下でマクロを用いたコードの取得を行っていました。
https://www.microsoft.com/japan/msdn/msdnmag/issues/05/07/XMLComments/default.aspx#S12

2008 Express Editionではマクロを使えないのですが、
EnvDTEを用いて何かできないかと思い、以下を参考に、下記のコードを試してみたのですが、
http://msdn.microsoft.com/ja-jp/library/yf86a8ts%28v=VS.100%29.aspx
http://msdn.microsoft.com/ja-jp/library/68shb4dw%28v=VS.100%29.aspx
COMExceptionはハンドルされませんでした。
クラス文字列が無効です (HRESULT からの例外: 0x800401F3 (CO_E_CLASSSTRING))
というエラーが出てしまいます。
引数の文字列が間違っているのか、それとも2008 Express Editionでは無理なのでしょうか。

Imports EnvDTE
Imports EnvDTE80
Imports EnvDTE90

 Sub Main()
        Dim dte2 As DTE
        dte2 = System.Runtime.InteropServices.Marshal.GetActiveObject("VisualStudio.DTE")
 End Sub

投稿者 魔界の仮面弁士  (社会人) 投稿日時 2010/5/17 21:37:35
> 関係ないかも知れませんが、
無関係とは言いませんが、直接の関連性は見出せませんでした。

葉月さんのコードが行っていることは、GetILAsByteArray で IL を読み取り、それを DynamicMethod 経由で
呼び出すという行為ですよね。これはどちらかというと、動的にメソッドを定義/実行するときに
使われる物であって、元のコードを出力したいという目的からは外れているように思えます。
http://d.hatena.ne.jp/akiramei/20040722/p1


もしも実行だけが目的なら、IL のバイナリを得る必要は無く、もっと単純に書くことができます。
既にデリゲートインスタンスは取得されているとのことでしたから、たとえばこんな感じ。
Dim fn As Func(Of IntegerIntegerInteger) = AddressOf CHoge.MyAdd
Dim ret As Integer = fn(iA, iB)
または
Dim mi As MethodInfo = GetType(CHoge).GetMethod("MyAdd", BindingFlags.Static Or BindingFlags.Public)
ret = CInt(mi.Invoke(NothingNew Object() {iA, iB}))


一方、IL のバイナリを VB のソースコードに変換するという視点から考えてみた場合、方針としては
 1. IL のバイナリが、どの System.Reflection.Emit.OpCodes に相当するかを調べる。
 2. それぞれの OpCode を解釈して、CodeDOM へと展開する。
 3. そこから GenerateCodeFromMember メソッドでソースコードへと展開。
という手順を思いついたのですが、2. が鬼門なので、あまり現実的な解では無さそうです。
(2 のコードが書ける人なら、そもそも IL 自体を読めるはずですし)


>> ILのコードを呼び出すのだと思うのですが、
>> 読んで理解するのは難しそうですので、諦めようと思います。。
たとえば葉月さんの CHoge.MyAdd を例に挙げると、GetILAsByteArray からは
"00-02-03-D6-0A-2B-00-06-2A" の 9 バイトのバイナリが返されていました。

これを System.Reflection.Emit.OpCodes に照らし合わせると、下記のような意味になります。
分かりにくいですが、これが「Return iA + iB」の内部処理というわけです。

00 : OpCodes.Nop      (nop : 何もしない)
02 : OpCodes.Ldarg_0  (ldarg.0 : 引数#0を stack にロード)
03 : OpCodes.Ldarg_1  (ldarg.1 : 引数#1を stack にロード)
D6 : OpCodes.Add_Ovf  (add.ovf : stack から値を2つ取り出し、加算結果をstack に入れる)
0A : OpCodes.Stloc_0  (stloc.0 : stack から値を1つ取り出し、インデックス#0の変数に移動)
2B : OpCodes.Br_S     (br.s : <int8> で指定されたオフセットの位置に無条件ジャンプする)
 00 : オフセット 0x00
06 : OpCodes.Ldloc_0  (ldloc.0 : インデックス#0の変数を stack にロード)
2A : OpCodes.Ret      (ret : stack から値を1つ取り出し、それを戻り値としてメソッドから戻る)
(Add_Ovf では、加算結果のオーバーフローチェックも行われます)


良く見ると、この中には無意味な処理が幾つかありますので、それらを整理すると
"02-03-D6-2A" の 4 バイトだけでも済ませられそうです。
Dim dm As New DynamicMethod( _
    "HogetyauYo!", _
    GetType(Integer), _
    New Type() {GetType(Integer), GetType(Integer)}, _
    GetType(CHoge), _
    False)

Dim info As DynamicILInfo = dm.GetDynamicILInfo()
info.SetCode(New Byte() {&H2, &H3, &HD6, &H2A}, 2)
info.SetLocalSignature(New Byte() {&H7, 0})

Dim iA As Integer = 5
Dim iB As Integer = 2
Dim ret As Integer = CInt(dm.Invoke(NothingNew Object() {iA, iB}))



おまけ。この"02-03-D6-2A" の処理を ILGenerator 経由で生成してみました。
デリゲートインスタンス内のコードを出力するという目的からは外れますが。
Dim dm As New DynamicMethod( _
    "MyAdd", _
    GetType(Integer), _
    New Type() {GetType(Integer), GetType(Integer)})

Dim g As ILGenerator = dm.GetILGenerator()
g.Emit(OpCodes.Ldarg_0)          '02: 引数#0の読み込み 
g.Emit(OpCodes.Ldarg_1)          '03: 引数#1の読み込み 
g.Emit(OpCodes.Add_Ovf)          'D6: 加算 
g.Emit(OpCodes.Ret)              '2A: Return 

Dim iA As Integer = 5
Dim iB As Integer = 2
Dim ret As Integer = CInt(dm.Invoke(NothingNew Object() {iA, iB}))


元の "00-02-03-D6-0A-2B-00-06-2A" のコードだと、こんな感じ。
Dim g As ILGenerator = dm.GetILGenerator()
g.DeclareLocal(GetType(Integer)) 'ローカル変数。強いて言えば「Dim MyAdd As Integer」に相当。 
g.Emit(OpCodes.Nop)              '00: 何もしない 
g.Emit(OpCodes.Ldarg_0)          '02: 引数#0の読み込み 
g.Emit(OpCodes.Ldarg_1)          '03: 引数#1の読み込み 
g.Emit(OpCodes.Add_Ovf)          'D6: 加算 
g.Emit(OpCodes.Stloc_0)          '0A: 変数#0に代入 
g.Emit(OpCodes.Br_S, CByte(0))   '2B,00: 次の行へジャンプ 
g.Emit(OpCodes.Ldloc_0)          '06: 変数#1を読み込み 
g.Emit(OpCodes.Ret)              '2A: Return 
投稿者 (削除されました)  () 投稿日時 2010/5/17 20:40:22
(削除されました)
投稿者 魔界の仮面弁士  (社会人) 投稿日時 2010/5/17 13:51:15
むぅ。[関連URLなど]のボックスは、複数行書けるようになっているのに、
実際には 1 つしか書けないのですね…。削除して再訂正します。
http://www.ecma-international.org/publications/standards/Ecma-335.htm
http://msdn.microsoft.com/ja-jp/library/3f4t3ddz.aspx 

# 本文を削除したら、URL も削除されるべきなのでは? >るきおさん


で、本題。

> 'TODO: レイアウトで使う値がよくわからない。 
このバイナリは、LocalVarSig と呼ばれており、
              ┌──────────────────────────────────────┐
              │ ┌─────────────────────┐              │
 ┏━━━━━┓ ┏━━━┓↓ ↓  ┏━━━━━┓    ┏━━━━━┓ │  ┏━━━┓  ┏━━┓ │
→┃LOCAL_SIG ┠→┃Count ┠─┬─┬→┃CustomMod ┠─┬─→┃Constraint┠─┴┬→┃BYREF ┠─→┃TYPE┠─┴→
 ┗━━━━━┛ ┗━━━┛ | │ ┗━━━━━┛↑|  ┗━━━━━┛↑ │ ┗━━━┛↑ ┗━━┛↑
               | └────────┘└─────────┘ └──────┘     │
               |         ┏━━━━━┓                    |
               └────────→┃TYPEDBYREF┠────────────────────┘
                         ┗━━━━━┛
という順で指定されるものです。
詳細は、CLI 仕様書の "Partition II: Metadata Definition and Semantics" 内にある
"23.2 Blobs and s ignatures" の "LocalVarSig" のあたりを参照してみて下さい。

それを踏まえて読み取ると、今回指定した、
 &H7, &H2, &H8, &H8
は、それぞれ
 LOCAL_SIG, Count, TYPE, TYPE
の意味で記述されたものなのだと思います。

LOCAL_SIG は &H7 固定です。IL でいうところの .locals ディレクティブのシグネチャを意味しています。
Count はローカル変数の数を表す 1 以上の整数です。
TYPE は、文字通りデータ型。&H8 の場合は、ELEMENT_TYPE_I4 すなわち Int32 型を意味します。

なので IL 的には『.locals (int32 a, int32 b)』、VB 的には『Dim a, b As Integer』という宣言に
相当する署名を表しているのだと思います。


……ただ、何故 &H7, &H2, &H8, &H8 が選択されているのかが読み取れませんでした。

今回のコードである
Shared Function MyAdd(ByVal iA As IntegerByVal iB As IntegerAs Integer
    Return iA + iB
End Function
の場合、IL的には「.locals init (int32 MyAdd)」だけで済むはずなので、今回は
info.SetLocalSignature(New Byte() {&H7, &H1, &H8}) で良いような気もします。


ちなみに CHoge.MyAdd メソッドの戻り値を、As Integer から As Object に変更した場合、
ローカル変数は「.locals init (object MyAdd)」となるため、ELEMENT_TYPE_I4 は使えません。
この場合は、System.Object 型を意味する ELEMENT_TYPE_OBJECT = &H1C を指定して、
info.SetLocalSignature(New Byte() {&H7, &H1, &H1C}) という指定になります。
投稿者 (削除されました)  () 投稿日時 2010/5/17 13:43:03
(削除されました)
投稿者 (削除されました)  () 投稿日時 2010/5/17 10:06:23
(削除されました)
投稿者 葉月  (社会人) 投稿日時 2010/5/16 17:17:19
続けて、横から失礼します。
TODOコメントにしてある
info.SetLocalSignatureの部分がわかりません。
リフレクションを行うのに必要なのはわかるのですが、
0x07、0x02の関連がわかりません。
0x07などがBELのことを指しているのか不明です。

ここの意味を、よかったら答えてくれると嬉しいです。
投稿者 葉月  (社会人) 投稿日時 2010/5/16 17:15:57
こんにちは。

関係ないかも知れませんが、
このスレにある「あにすさん」のサンプルは参考になりませんか?
http://rucio.cloudapp.net/ThreadDetail.aspx?ThreadId=140

>読んで理解するのは難しそうですので、諦めようと思います。。
このまま終わらすのは、もったいない内容だと思いますが……
スレが終わりそうだったので、関連するクラスのMSDNを読みました。
MSDNを読んでみて、正直、私は宇宙人が翻訳してるんじゃないかと思いました(笑)
だから、諦めたくなる気持ちもわかります。
でも、短いサンプルを掲載して不明点を聞いた方が、長い目で見れば建設的ですよ。
業務では見送るにしても、知識として持っておくにはいいと思いましたので。

>一通り目を通したMSDNの記事
http://msdn.microsoft.com/ja-jp/library/system.reflection.emit.dynamicilinfo.aspx
http://msdn.microsoft.com/ja-jp/library/system.reflection.emit.ilgenerator.aspx
http://msdn.microsoft.com/ja-jp/library/3f4t3ddz.aspx
http://msdn.microsoft.com/ja-jp/library/xc6e708b%28v=VS.90%29.aspx


>>サンプル
>Module1.VB
' 入門から初級の方は見送るのを推奨 
' 今回は珍しくコンソールアプリケーションです。 
' Module1の他にCHogeクラスを作成する必要があります。 

Imports System
Imports System.Reflection
Imports System.Reflection.Emit

Module Module1

    Sub Main(ByVal args As String())

        ' ■dmコンストラクターの説明 
        ' 第1:Null不可。好きな名称でOK 
        ' # 今回はふざけてますが、~Addなどわかりやすい名称がいいと思います。 
        ' 第2:メソッドの戻り値 
        ' 第3:メソッドの引数 
        ' 第4:扱うクラス 
        ' 第5:Jitの参照範囲のチェック(trueだと省略) 
        Dim dm As New DynamicMethod( _
            "HogetyauYo!", _
            GetType(Integer), _
            New Type() {GetType(Integer), GetType(Integer)}, _
            GetType(CHoge), _
            False)

        ' メソッド一覧の情報を取得 
        Dim mi As MethodInfo = GetType(CHoge).GetMethod("MyAdd", BindingFlags.Static Or BindingFlags.Public)

        ' メタデータ生成の代替 
        Dim info As DynamicILInfo = dm.GetDynamicILInfo()
        info.SetCode(mi.GetMethodBody().GetILAsByteArray(), 10)

        'TODO: レイアウトで使う値がよくわからない。 
        ' 0x07(BEL???) BELを指す??? それだと、意味が通じないような。 
        ' 0x02(STX???) テクスト開始? 
        ' 0x08(IP) インストラクションポインタ(アセンブルの開始と終了?) 
        info.SetLocalSignature(New Byte() {&H7, &H2, &H8, &H8})

        Dim iA As Integer = 5
        Dim iB As Integer = 2

        Console.WriteLine("{0} + {1} = {2}", iA, iB, dm.Invoke(NothingNew Object() {iA, iB}))
        Console.ReadLine()
    End Sub
End Module


>CHoge.VB
Public Class CHoge
    Shared Function MyAdd(ByVal iA As IntegerByVal iB As IntegerAs Integer
        Return iA + iB
    End Function
End Class

投稿者 threecourse  (社会人) 投稿日時 2010/5/15 23:12:59
>魔界の仮面弁士さん
返信遅れました、どうもありがとうございました。

リフレクションでは、MethodBody.GetILAsByteArrayから
ILのコードを呼び出すのだと思うのですが、
読んで理解するのは難しそうですので、諦めようと思います。。

XMLドキュメントと結合するのが一番現実的かな、と思うので、頑張って見ようと思います。
投稿者 魔界の仮面弁士  (社会人) 投稿日時 2010/5/12 02:42:57
> コードも出力できればベストで、
動作時には既にコンパイルされた状態になっていますから、リフレクションで得られるコードは
VB のコードではなく IL のコードになりますが、それでも構いませんか?
それで良いのなら、MethodInfo クラスの GetMethodBody メソッドが利用できそうです。

また、リフレクション経由ではなく、現在呼び出しているメソッドに関する情報に限定して良ければ、
StackTrace クラスから StackFrame を辿ることで、ソースファイルの名前や行番号などの情報を
取得することができます。ただし、Debug ビルドである事が条件となります。

> 無理ならXMLコメントなどと組み合わせることができないかと思っています。
XMLコメントは、コンパイル結果(EXEやDLL)の内には埋め込まれていません。
EXE と同じフォルダに XML ファイルが出力されますので、それを読み取ってみてください。
(ビルド時に、XMLドキュメントを出力する設定にしている必要があります)
投稿者 threecourse  (社会人) 投稿日時 2010/5/12 01:11:20
ちなみに、行っていることは以下のような感じです。
プログラムの実行と同時に、
CalcFunctionClassのname,calcfuncのコードを出力するのが理想です。

Sub Main()

Dim CalcFunctionClasses as new List(Of CalcFunctionClass) 
'CalcFunctionClassのインスタンスの作成は省略しています。 

For t = 0 to PROJECTION_PERIOD - 1 '時系列で分析する 
 '順番に計算していく 
 For i = 0 to CalcFunctionClasses.count - 1
  CalcFunctionClasses(i).calcfunc(t)
 Next 
Next

End sub

’関数と関数を適用して得られる値を保持するクラス
Public Class CalcFunctionClass
    Public name As String 
  Public value as double() ’関数を適用した結果を保持する
    Public calcfunc as DelegateCalcFunction
    Sub calc(byval t as integer)
     value(t) = calcfunc(t)
    End Sub
End Class

Public Delegate Function DelegateCalcFunction(ByVal t As IntegerAs Double
'別途、関数を定義します。その関数の中で、他のCalcFunctionClassを参照したりします。 


投稿者 threecourse  (社会人) 投稿日時 2010/5/12 01:04:44
皆様どうもありがとうございます、説明不足だったかもしれず、背景を追記させていただきます。

・非システム部門で、エンドユーザー(主に私)が、今まではEXCELVBAで行っていた作業を
 Visual Studioを直接使って行うことを想定しています。
  
・プログラムでは、金融関係の「計算のモデル」を中で複数作ります。
 大まかには、計算のモデルは30以上の関数の配列で構成されます。
(複数の計算のモデルを作ったり修正したりする必要があるので、継承などを利用して管理します)

・なぜコード自体を出力したいかというと、それぞれの計算モデルにおいて適切な計算をしていることを確認したいのですが、それはコードを出力した後、何らかの形で表にして見るのが一つの良い方法と思ったからです。

>魔界の仮面弁士さん
デリゲートインスタンス(を含むクラス)の配列に対して順に実行する作りになっているのですが、その関数の情報を出力したく、
関数名はリフレクションで出せる?かなとは思うのですが、コードも出力できればベストで、無理ならXMLコメントなどと組み合わせることができないかと思っています。

>へつさん
IDだけ出力してテキストファイルを別途結合するのも考えていて、クラスファイルを読み込んで正規表現を使って結合したりできないか、とも考えています。
が、Visual Studioの機能を使って簡潔にできないかなぁ、と考えています。

>葉月さん
順序、というより、それぞれの関数および組み合わせが正しい計算モデルになっているかということを確認したいです。
サンプルコードまで書いていただきまして、どうもありがとうございます。
投稿者 葉月  (社会人) 投稿日時 2010/5/11 21:18:27
複雑なFunctionの組み合わせで、呼び出される順序がズレる場合があるということですが、
並列処理を行っており、非同期処理のため順序がズレる可能性があるのでしょうか?
それとも、トリッキーな作りで想定していない順序になるということでしょうか?

前者なら非同期処理を、同期しなければならない部分で同期することで対処できます。
あとは条件文で異常な変数を摘出すれば、使用者に教えるなりログを取ればいいと思います。
詳細は、BeginInvokeとIAsyncResultを調べてみてください。

後者だったら複数のFunctionをコンボジットした自作メソッドを作り、想定外の順序にならないようにします。
問題が起きた場合に、どのFunctionを通ったのか、コンボジットされたメソッドをみるとわかるようにします。
ログに出力してチェックするための並列処理を走らせれば、要望に近いことができると思います。

>>>コンボジットのイメージ
' FunctionがAからZまであると想定します。 

    Private Sub Hoge1()
        FuncA()
        FuncB()
        FuncC()
    End Sub

    Private Sub Hoge2()
        FuncB()
        FuncE()
        FuncZ()
    End Sub

    Private Sub Hoge3()
        FuncA()
        FuncS()
        FuncX()
    End Sub
投稿者 るしぇ  (社会人) 投稿日時 2010/5/11 10:17:06
>プログラムのコード自体の(動的な)出力
は必要なさそうですね。

要は
>2
>2
>3
>1
というデータを読み込んで、
>Function2
>Function2
>Function3
>Function1
と実行できて、実行中に
>2
>2
>3
>1
と出力できればいい気がします。
投稿者 へつ  (社会人) 投稿日時 2010/5/11 09:56:13
コード自体を出す必要があるんですか?
どのFunctionを通ったか、ということだけ出力するようにして、
あとはフローチャートで見ていく方が現実的だと思うのですが…

どうしてもと言うのであれば、プログラム内ではFunctionのIDだけ出力するようにして、
あとは、例えばFunctionごとにテキストファイルを用意しておき
別のプログラムや、あるいはExcelやAccessなんかで、そのIDからテキストファイルを結合していくとか。
ソースが二重管理になるので、逆にバグ発生の原因になりやすい気もしますが……
投稿者 魔界の仮面弁士  (社会人) 投稿日時 2010/5/11 00:53:44
> フラグに応じて、Function1またはFunction2またはFunction3を実行する。
> このとき、呼び出されたFunctionのコードを同時に出力する。

それぞれのデリゲート インスタンスを配列等に保持しておき、
それを呼び出すというのは如何でしょう。

あるいは、CallByName 関数やリフレクションで呼び出すという方法もあるかと。
投稿者 threecourse  (社会人) 投稿日時 2010/5/11 00:10:48
VB.netで、以下のようなことを行いたいのですが、方法はありますでしょうか?
フラグに応じて、Function1またはFunction2またはFunction3を実行する。
このとき、呼び出されたFunctionのコードを同時に出力する。
(実際にはもっと複雑にFunctionを組み合わせて実行するので、
その組み合わせが適切かをコード自体を出力することで確認するのが目的です。)

できれば、プログラムを一度実行させる中で行いたいですが、
プログラムを実行する前にXMLなどに一度出力し、プログラム中で読み込むという方法があればそれでも構いません。

環境は、Visual Studio 2008 Express editionです。

よろしくお願いいたします。