The limitations of Reflection.Emit


Question: Is Reflection.Emit a comprehensive API that lets you build any assembly you want?


Answer: No. There are some things that are valid IL, and yet can’t be built with Reflection.Emit. Thanks to Eric Lippert for showing me the following example.


Why do we care about this? Partly it’s just curiosity. Partly it’s because we’re porting the VB compiler from C++ into VB, and we wanted to know if we could use Reflection.Emit for the back-end (answer “no”). Partly because we’re exploring the idea of a “REPL loop” and want to know how best to implement it (no solid answers yet).


Attempt 1: Wrong Order


Imports System.Reflection
Imports System.Reflection.Emit



‘ Goal: to emit this class structure through Reflection.Emit:


Public Class X


    Inherits Y


 


    Public Interface I


    End Interface


End Class


 


Public Class Y


    Implements X.I


End Class


 


 


‘ Implementation: this is our attempt. We can’t actually do it,


‘ because of limitations in Reflection.Emit


Module Module1


    Sub Main()


        Dim name = New AssemblyName(“test”)


        Dim ba = Threading.Thread.GetDomain.DefineDynamicAssembly(name, AssemblyBuilderAccess.RunAndSave)


        Dim bm = ba.DefineDynamicModule(name.Name, name.Name & “.dll”)


 


        Dim by = bm.DefineType(“Y”, TypeAttributes.Public Or TypeAttributes.Class)


        Dim bx = bm.DefineType(“X”, TypeAttributes.Public Or TypeAttributes.Class, by)


        Dim bi = bx.DefineNestedType(“I”, TypeAttributes.NestedPublic Or TypeAttributes.Interface Or TypeAttributes.Abstract)


        by.AddInterfaceImplementation(bi)


        ‘ The question now is: in what order do we create the types X/Y/I ?


        ‘ http://msdn.microsoft.com/en-us/library/system.reflection.emit.typebuilder.createtype.aspx


        ‘ * X before I – “create enclosing type before creating the nested type”


        ‘ * Y before X – “create parent type before creating the current type”


        ‘ * I before Y – “create interface type before creating the current type”


        ‘ It’s impossible to satisfy all three constraints


        Dim ti = bi.CreateType()


        Dim ty = by.CreateType()


        Dim tx = bx.CreateType()


 


        ba.Save(name.Name & “.dll”)


    End Sub


End Module


 


 


 


Attempt 2: Callbacks


Update: Thank you for the excellent discussion in the comments. Kvb86 pointed out the use of “ResolveEventHandler” callbacks. With them we can emit the code at the top of this article:


Imports System.Reflection


Imports System.Reflection.Emit


 


‘ Goal: to emit this class structure through Reflection.Emit:


Public Class X


    Inherits N.Y


    Public Interface I


    End Interface


End Class


 


Namespace N


    Public Class Y


        Implements Global.X.I


    End Class


End Namespace


 


 


‘ Implementation: this is our attempt. We can’t actually do it,


‘ because of limitations in Reflection.Emit


Module Module1


    Sub Main()


        Dim ad = Threading.Thread.GetDomain()


        Dim name = New AssemblyName(“test”)


        Dim ba = Threading.Thread.GetDomain.DefineDynamicAssembly(name, AssemblyBuilderAccess.RunAndSave)


        Dim bm = ba.DefineDynamicModule(name.Name, name.Name & “.dll”)


 


 


        Dim by = bm.DefineType(“N.Y”, TypeAttributes.Public Or TypeAttributes.Class)


        Dim bx = bm.DefineType(“X”, TypeAttributes.Public Or TypeAttributes.Class, by)


        Dim bi = bx.DefineNestedType(“I”, TypeAttributes.NestedPublic Or TypeAttributes.Interface Or TypeAttributes.Abstract)


        by.AddInterfaceImplementation(bi)


        ‘ The question now is: in what order do we create the types X/Y/I ?


        ‘ http://msdn.microsoft.com/en-us/library/system.reflection.emit.typebuilder.createtype.aspx


        ‘ * X before I – “create enclosing type before creating the nested type”


        ‘ * Y before X – “create parent type before creating the current type”


        ‘ * I before Y – “create interface type before creating the current type”


        ‘ It’s impossible to satisfy all three constraints


 


        Dim yc = by.DefineDefaultConstructor(MethodAttributes.Public Or MethodAttributes.PrivateScope Or MethodAttributes.HideBySig Or MethodAttributes.SpecialName Or MethodAttributes.RTSpecialName)


        Dim xc = bx.DefineConstructor(MethodAttributes.Public Or MethodAttributes.PrivateScope Or MethodAttributes.HideBySig Or MethodAttributes.SpecialName Or MethodAttributes.RTSpecialName, CallingConventions.Standard, Type.EmptyTypes)


        Dim ilg = xc.GetILGenerator()


        ilg.Emit(OpCodes.Ldarg_0)


        ilg.Emit(OpCodes.Call, yc)


        ilg.Emit(OpCodes.Ret)


 


        Dim eh = Function(o As Object, e As ResolveEventArgs) As Assembly


                     Select Case e.Name


                         Case “X” : bx.CreateType()


                         Case “Y” : by.CreateType()


                         Case “I” : bi.CreateType()


                     End Select


                     Return ba


                 End Function


 


        AddHandler ad.TypeResolve, eh


        Dim ti = bi.CreateType()


        Dim ty = by.CreateType()


        Dim tx = bx.CreateType()


        RemoveHandler ad.TypeResolve, eh


 


        ba.Save(name.Name & “.dll”)


    End Sub


End Module


 


 


 


Attempt 3: Lack of precision


Update: but as Rolf points out, the existing ResolveEventHandler in Reflection.Emit is inadequate. That’s because it doesn’t qualify the names. So if you were trying to emit this code, where everything is called “X”, then the ResolveEventHandler wouldn’t tell you which “X” it has in mind!


Public Class X


    Inherits N.X


    Public Interface X


    End Interface


End Class


 


Namespace N


    Public Class X


        Implements Global.X.X


    End Class


End Namespace


Conclusion: if Reflection.Emit were to be used seriously, it’d have to be fixed first.


 

Comments (3)

  1. kvb86 says:

    Well, it’s certainly not easy, but it can be done.  The key is to subscribe to the AppDomain’s TypeResolve event, which will allow you to handle cyclical dependencies.  However, you also need to treat the constructors explicitly to get a reference to Y’s constructor to call from X’s.

    F# code follows (hopefully formatting won’t be mangled too badly).  Ironically, I used F# because the REPL made rapidly iterating variations of the code quite easy.

    open System

    open System.Reflection

    open System.Reflection.Emit

    let name = AssemblyName("test")

    let ad = System.Threading.Thread.GetDomain()

    let ba = ad.DefineDynamicAssembly(name, AssemblyBuilderAccess.RunAndSave)

    let bm = ba.DefineDynamicModule(name.Name, name.Name + ".dll")

    let by = bm.DefineType("Y", TypeAttributes.Public ||| TypeAttributes.Class)

    let bx = bm.DefineType("X", TypeAttributes.Public ||| TypeAttributes.Class, by)

    let bi = bx.DefineNestedType("I", TypeAttributes.NestedPublic ||| TypeAttributes.Interface ||| TypeAttributes.Abstract)

    by.AddInterfaceImplementation(bi)

    let yc = by.DefineDefaultConstructor(MethodAttributes.Public ||| MethodAttributes.PrivateScope ||| MethodAttributes.HideBySig ||| MethodAttributes.SpecialName ||| MethodAttributes.RTSpecialName)

    let xc = bx.DefineConstructor(MethodAttributes.Public ||| MethodAttributes.PrivateScope ||| MethodAttributes.HideBySig ||| MethodAttributes.SpecialName ||| MethodAttributes.RTSpecialName, CallingConventions.Standard, Type.EmptyTypes)

    let ilg = xc.GetILGenerator()

    ilg.Emit(OpCodes.Ldarg_0)

    ilg.Emit(OpCodes.Call, yc)

    ilg.Emit(OpCodes.Ret)

    let eh =

     System.ResolveEventHandler(fun o t ->

       match t.Name with

       | "X" -> bx.CreateType() |> ignore

       | "Y" -> by.CreateType() |> ignore

       | "I" -> bi.CreateType() |> ignore

       ba :> Assembly)

    let xInstance =

       try

           ad.add_TypeResolve(eh)

           let ti = bi.CreateType()

           let ty = by.CreateType()

           let tx = bx.CreateType()

           ba.Save(name.Name + ".dll")

           ba.CreateInstance("X")

       finally

           ad.remove_TypeResolve(eh)

  2. ninputer says:

    Do you want to modify Reflection.Emit or create another emitting library. So you choose the first way becasue there’s many people want to create compilers using Reflection.Emit.

    I’m also curious if there’s another library solved this problem, what would that code look like?

  3. rolfbjarne says:

    Nice to see another VB compiler written in VB! And thanks for the badge!

    Sidenote: there is a problem with with the TypeResolve event fix: you only get the name of the type, not the namespace. So by expanding the sample a little bit with types with the same name, but different namespace, you still have the same problem.

    vbnc is now using SRE to create the assemblies (when I started writing vbnc there weren’t any alternatives), though I’m currently rewriting it to use another API (http://www.mono-project.com/Cecil).

    Rolf