Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

.NET Core generated assembly references mscorlib #646

Closed
odalet opened this issue Feb 2, 2020 · 6 comments
Closed

.NET Core generated assembly references mscorlib #646

odalet opened this issue Feb 2, 2020 · 6 comments

Comments

@odalet
Copy link

odalet commented Feb 2, 2020

I'm trying to generate an assembly using Cecil completely from scratch. For now, the generated application is simply a Hello World resembling many examples I found on the web.
I'm building my code by targetting netcoreapp3.0. Everything seems: my app.dll is generated and if I place alongside it the correct app.runtimeconfig.json I can dotnet it and "Hellow, World" is displayed.
However, by using ILSpy, I can see my app.dll assembly has references to mscorlib, System.Console and System.Private.CoreLib, and btw, ILSpy is unable to locate the latter.
I would have rather expected System.Runtime instead of mscorlib and System.Private.CoreLib...

I'm using Mono.Cecil package Version 0.11.1

Here is the code I'm using:

using System;
using Mono.Cecil;
using Mono.Cecil.Cil;

namespace repro
{
    // Build as a.netcoreapp3.0 project
    internal sealed class Program
    {
        [STAThread]
        private static void Main()
        {
            var outputFile = @"c:\temp\app.dll";
            new Program().Run(outputFile);
        }

        private void Run(string filename)
        {
            const string assemblyAndMainModuleName = "app";

            var assemblyNameDefinition = new AssemblyNameDefinition(assemblyAndMainModuleName, new Version(1, 0, 0, 0));
            using (var assemblyDefinition = AssemblyDefinition.CreateAssembly(assemblyNameDefinition, assemblyAndMainModuleName, ModuleKind.Console))
            {
                var mainModule = assemblyDefinition.MainModule;
                mainModule.RuntimeVersion = "v4.0.30319";

                var type = new TypeDefinition("app", "Program", TypeAttributes.NotPublic | TypeAttributes.Sealed, mainModule.TypeSystem.Object)
                {
                    IsBeforeFieldInit = true
                };

                var main = new MethodDefinition("Main", MethodAttributes.Private | MethodAttributes.Static, mainModule.TypeSystem.Void);

                var writeLine = mainModule.ImportReference(typeof(Console).GetMethod("WriteLine", new[] { typeof(string) }));

                var il = main.Body.GetILProcessor();
                il.Emit(OpCodes.Nop);
                il.Emit(OpCodes.Ldstr, "Hello, World!");
                il.Emit(OpCodes.Call, writeLine);
                il.Emit(OpCodes.Nop);
                il.Emit(OpCodes.Ret);

                type.Methods.Add(main);
                mainModule.Types.Add(type);

                mainModule.EntryPoint = main;

                assemblyDefinition.EntryPoint = main;
                assemblyDefinition.Write(filename);
            }
        }
    }
}
@odalet
Copy link
Author

odalet commented Feb 2, 2020

Oh, I just noticed my issue is probably related to #524...
So I stripped my repro app to the bare minimum:

private void Run(string filename)
{
    const string assemblyAndMainModuleName = "app";

    var assemblyNameDefinition = new AssemblyNameDefinition(assemblyAndMainModuleName, new Version(1, 0, 0, 0));
    using (var assemblyDefinition = AssemblyDefinition.CreateAssembly(assemblyNameDefinition, assemblyAndMainModuleName, ModuleKind.Console))
    {
        var mainModule = assemblyDefinition.MainModule;
        var type = new TypeDefinition("app", "Program", TypeAttributes.NotPublic | TypeAttributes.Sealed, mainModule.TypeSystem.Object)
        {
            IsBeforeFieldInit = true
        };

        mainModule.Types.Add(type);
        assemblyDefinition.Write(filename);
    }
}

The resulting assembly still references mscorlib obviously because Program inherits Object.
But why wasn't System.Object resolved to System.Private.CoreLib's System.Object through System.Runtime? I can't see what in my code would reference mscorlib...

@jbevain
Copy link
Owner

jbevain commented Feb 2, 2020

That's just the default behavior of the TypeSystem:

https://github.com/jbevain/cecil/blob/master/Mono.Cecil/TypeSystem.cs#L93

Cecil simply doesn't know which runtime you're targeting, and where System.Object is defined. It ends up assuming mscorlib because historically that's where System.Object lives.

Before creating any type, create an AssemblyNameReference for System.Runtime, add it to the module, and the TypeSystem should pick it up instead of creating a new assembly reference.

@odalet
Copy link
Author

odalet commented Feb 3, 2020

Thanks, that's what I ended up doing:

var corlibReference = new AssemblyNameReference("System.Runtime", new Version(4, 2, 1, 0))
{
    PublicKeyToken = new byte[] { 0xb0, 0x3f, 0x5f, 0x7f, 0x11, 0xd5, 0x0a, 0x3a }
};
...
mainModule.AssemblyReferences.Add(corlibReference);

So, now my references make more sense, but still I wonder:

my assembly now has explicit references to:

  • System.Runtime,
  • System.Private.CoreLib
  • System.Console

whereas an assembly built with the C# compiler and doing roughly the same thing has no explicit reference to System.Private.CoreLib...

I suppose there is some ForwardedType magic going on here, and not having the exact same behavior when generating with Mono.Cecil is not an issue; however, do-you have any explanation for the different behavior?

@jbevain
Copy link
Owner

jbevain commented Feb 3, 2020

I think it's because of the line:

mainModule.ImportReference(typeof(Console).GetMethod("WriteLine", new[] { typeof(string) }))

That's a perfectly valid way of creating a reference scoped for the module, however it's using reflection to get the member and assembly data. At that point, it seems that the assembly of the type Console is System.Private.CoreLib, and Cecil is blindly creating a reference to that.

I'm not smart enough to understand the difference System.Runtime, System.Private.CoreLib and mscorlib accross multiple platforms, so I'd rather not implement something in Cecil that tries to be smart and smoothes references to core assemblies.

However, by now I think that System.Runtime is only typeforwarders on .NET Framework and on .NET Core. On .NET Framework, Object lives in mscorlib and on .NET Core it ultimately lives in System.Private.CoreLib. You could also only reference netstandard.dll. It all depends on what you want to achieve.

@odalet
Copy link
Author

odalet commented Feb 4, 2020

Thanks for sharing your thoughts! This confirms what I suspected by reading the code in https://github.com/jbevain/cecil/blob/master/Mono.Cecil/Import.cs

If I really wanted to fine tune my assembly references, I suppose I'd have to somehow preprocess a set of "reference" assemblies into a huge list of type references so that every type I need would be resolved against this list instead of relying upon reflection (that by design will inspect the generator application's assemblies). This approach could open some interesting scenarios, such as being able to target netfx/netcore/netstandard from any framework... This would still be a huge amount of work!

Btw, I'm pretty sure the ratio benefit/investment would be a better reason for not carrying up such a task and not you being not smart enough; this great lib of yours proves it!

Thanks again. I'm closing this as you graciously answered all I was wondering.

@odalet odalet closed this as completed Feb 4, 2020
@jbevain
Copy link
Owner

jbevain commented Feb 4, 2020

The best way to more finely control the creation of reference is to not use the System.Type/System.Reflection overloads for ImportReference, and use the Cecil based one. If you couple that with your own assembly resolver you should have a lot more control.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants