I have been playing around with Mono.Cecil recently, mainly for a compiler that I'm planning on writing. I started by trying out the code in this answer. Here is the code:
var myHelloWorldApp = AssemblyDefinition.CreateAssembly(
new AssemblyNameDefinition("HelloWorld", new Version(1, 0, 0, 0)), "HelloWorld", ModuleKind.Console);
var module = myHelloWorldApp.MainModule;
// create the program type and add it to the module
var programType = new TypeDefinition("HelloWorld", "Program",
Mono.Cecil.TypeAttributes.Class | Mono.Cecil.TypeAttributes.Public, module.TypeSystem.Object);
module.Types.Add(programType);
// add an empty constructor
var 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
var il = ctor.Body.GetILProcessor();
il.Append(il.Create(OpCodes.Ldarg_0));
// call the base constructor
il.Append(il.Create(OpCodes.Call, module.ImportReference(typeof(object).GetConstructor(Array.Empty<Type>()))));
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'
var mainMethod = new MethodDefinition("Main",
Mono.Cecil.MethodAttributes.Public | Mono.Cecil.MethodAttributes.Static, module.TypeSystem.Void);
programType.Methods.Add(mainMethod);
// add the 'args' parameter
var 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"));
var writeLineMethod = il.Create(OpCodes.Call,
module.ImportReference(typeof(Console).GetMethod("WriteLine", new[] { 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;
myHelloWorldApp.Write("HelloWorld.exe");
Note that I changed module.Import
to module.ImportReference
since the former is apparently obsoleted.
I put this into a .NET 5 project, and this created a HelloWorld.exe
. Since I was on macOS, I tried running the exe with mono:
mono HelloWorld.exe
And it printed "Hello World". So far so good.
The problem arises, when I sent this HelloWorld.exe
to my friend who is on a Windows machine. When he runs it like this (Note that he doesn't have mono on Windows):
.\HelloWorld.exe
It outputs the error:
Unhandled Exception: System.IO.FileNotFoundException: Could not load file or assembly 'System.Console, Version=5.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a' or one of its dependences. The system cannot find the file specified.
at
HelloWorld.Program.Main(String[] args)
I tried to look up the error message, but all the results were about not able to find System.Runtime
. And is System.Console
even an assembly? Isn't it a class?
How can I run the exe on a Windows Machine? Is there something I need to change in the code? Or is there something that the Windows machine needs to install? I think this might have to do with me using .NET 5, but the Windows machine only has .NET Framework.
Question ends there, below are my findings:
As the "control group", I tried doing the simplest Hello World C# program:
class Program {
public static void Main() {
System.Console.WriteLine("Hello World");
}
}
Compiling that with csc
on macOS (i.e. the Mono compiler), and running the output exe on Windows. This works, so I disassembled (using dotnet-ildasm
) both the exe produced by Mono.Cecil and the exe produced by csc
, and compared them. The most interesting difference I found is that there are these extra assembly references in the "broken" exe:
.assembly extern System.Private.CoreLib
{
.publickeytoken = ( 7C EC 85 D7 BE A7 79 8E ) // ....y.
.ver 5:0:0:0
}
.assembly extern System.Console
{
.publickeytoken = ( B0 3F 5F 7F 11 D5 0A 3A ) // .._.....
.ver 5:0:0:0
}