1
open System
open Mono.Cecil
open Mono.Cecil.Cil

let myHelloWorldApp = 
    AssemblyDefinition.CreateAssembly(
        new AssemblyNameDefinition("HelloWorld", new Version(1, 0, 0, 0)), "HelloWorld", ModuleKind.Console)

let module_ = myHelloWorldApp.MainModule

// create the program type and add it to the module
let programType = 
    new TypeDefinition("HelloWorld", "Program",
        Mono.Cecil.TypeAttributes.Class ||| Mono.Cecil.TypeAttributes.Public, module_.TypeSystem.Object)

module_.Types.Add(programType)

// add an empty constructor
let ctor = 
    new MethodDefinition(".ctor", Mono.Cecil.MethodAttributes.Public ||| Mono.Cecil.MethodAttributes.HideBySig
        ||| Mono.Cecil.MethodAttributes.SpecialName ||| Mono.Cecil.MethodAttributes.RTSpecialName, module_.TypeSystem.Void)

// create the constructor's method body
let il = ctor.Body.GetILProcessor()

il.Append(il.Create(OpCodes.Ldarg_0))

// call the base constructor
il.Append(il.Create(OpCodes.Call, module_.ImportReference(typeof<obj>.GetConstructor([||]))))

il.Append(il.Create(OpCodes.Nop))
il.Append(il.Create(OpCodes.Ret))

programType.Methods.Add(ctor)

// define the 'Main' method and add it to 'Program'
let mainMethod = 
    new MethodDefinition("Main",
        Mono.Cecil.MethodAttributes.Public ||| Mono.Cecil.MethodAttributes.Static, module_.TypeSystem.Void)

programType.Methods.Add(mainMethod)

// add the 'args' parameter
let argsParameter = 
    new ParameterDefinition("args",
        Mono.Cecil.ParameterAttributes.None, module_.ImportReference(typeof<string[]>))

mainMethod.Parameters.Add(argsParameter);

// create the method body
il = mainMethod.Body.GetILProcessor()

il.Append(il.Create(OpCodes.Nop))
il.Append(il.Create(OpCodes.Ldstr, "Hello World"))

let writeLineMethod = 
    il.Create(OpCodes.Call,
        module_.ImportReference(typeof<Console>.GetMethod("WriteLine", [|typeof<string>|])))

// call the method
il.Append(writeLineMethod)

il.Append(il.Create(OpCodes.Nop))
il.Append(il.Create(OpCodes.Ret))

// set the entry point and save the module
myHelloWorldApp.EntryPoint <- mainMethod

I've borrowed the example from the answer to that question and rewritten it in F#. When I try to run it, I get the following error:

Unhandled Exception: System.TypeLoadException: Could not load type 'HelloWorld.Program' from assembly 'Hello
on=1.0.0.0, Culture=neutral, PublicKeyToken=null' because the method 'Main' has no implementation (no RVA).

What is wrong here?

Marko Grdinić
  • 3,798
  • 3
  • 18
  • 21

1 Answers1

2

My guess is that the problem is caused by the following line:

// create the method body
il = mainMethod.Body.GetILProcessor()

In C#, this would assign the IL processor for the Main method to the il variable, but in F#, this is just an equality test which results in false - and so the IL code that you wanted to generate for the Main method is added to the previous il processor for the constructor.

You should be able to fix this using variable shadowing:

// create the method body
let il = mainMethod.Body.GetILProcessor()
Tomas Petricek
  • 240,744
  • 19
  • 378
  • 553
  • Yeah, this was it. Though rather than use a let statement it would be better to bind it to a lambda otherwise the duplicate variable error will occur. Also, I've already noticed once before that for some reason F# is not giving me the usual is not unit warning on the equality test, but it did not occur to me to check for the same error again. Thanks. – Marko Grdinić Oct 13 '17 at 13:08
  • 1
    @MarkoGrdinic You do not get the equality test warning at the top-level in a script (where you might want to write a number of expressions and run them one by one). You would get those if your code was inside a function (which is a better structure anyway) – Tomas Petricek Oct 13 '17 at 14:07