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

Generated executable can't load main assembly #102393

Closed
Aaron-Junker opened this issue May 17, 2024 · 6 comments
Closed

Generated executable can't load main assembly #102393

Aaron-Junker opened this issue May 17, 2024 · 6 comments
Labels
area-System.Reflection.Emit question Answer questions and provide assistance, not an issue with source code or documentation.

Comments

@Aaron-Junker
Copy link

Description

Using .NET 9 new PersistedAssemblyBuilder I try to generate an executable following the documentation.

Upon running the generated executable I get the following error:

Unbehandelte Ausnahme: System.IO.FileNotFoundException: Die Datei oder Assembly "System.Private.CoreLib, Version=9.0.0.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e" oder eine Abhängigkeit davon wurde nicht gefunden. Das System kann die angegebene Datei nicht finden.

Reproduction Steps

Official sample

PersistedAssemblyBuilder ab = new PersistedAssemblyBuilder(new AssemblyName("MyAssembly"), typeof(object).Assembly);
TypeBuilder tb = ab.DefineDynamicModule("MyModule").DefineType("MyType", TypeAttributes.Public | TypeAttributes.Class);
// ...
MethodBuilder entryPoint = tb.DefineMethod("Main", MethodAttributes.HideBySig | MethodAttributes.Public | MethodAttributes.Static);
ILGenerator il2 = entryPoint.GetILGenerator();
// ...
il2.Emit(OpCodes.Ret);
tb.CreateType();

MetadataBuilder metadataBuilder = ab.GenerateMetadata(out BlobBuilder ilStream, out BlobBuilder fieldData);
PEHeaderBuilder peHeaderBuilder = new PEHeaderBuilder(imageCharacteristics: Characteristics.ExecutableImage);

ManagedPEBuilder peBuilder = new ManagedPEBuilder(
                header: peHeaderBuilder,
                metadataRootBuilder: new MetadataRootBuilder(metadataBuilder),
                ilStream: ilStream,
                mappedFieldData: fieldData,
                entryPoint: MetadataTokens.MethodDefinitionHandle(entryPoint.MetadataToken));

BlobBuilder peBlob = new BlobBuilder();
peBuilder.Serialize(peBlob);

// in case saving to a file:
using var fileStream = new FileStream("MyAssembly.exe", FileMode.Create, FileAccess.Write);
peBlob.WriteContentTo(fileStream);

Expected behavior

The generated executable can be run

Actual behavior

Although I have the .NET 9 runtime installed and during generation typeof(object).Assembly points to the right assembly it cannot find it when running.

Unbehandelte Ausnahme: System.IO.FileNotFoundException: Die Datei oder Assembly "System.Private.CoreLib, Version=9.0.0.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e" oder eine Abhängigkeit davon wurde nicht gefunden. Das System kann die angegebene Datei nicht finden.

Regression?

No response

Known Workarounds

No response

Configuration

  • 9.0.0-preview.3.24172.9
  • x64 Windows 10.0.26212.5000

Other information

No response

@dotnet-policy-service dotnet-policy-service bot added the untriaged New issue has not been triaged by the area owner label May 17, 2024
Copy link
Contributor

Tagging subscribers to this area: @dotnet/area-system-reflection-metadata
See info in area-owners.md if you want to be subscribed.

Copy link
Contributor

Tagging subscribers to this area: @dotnet/area-system-reflection-emit
See info in area-owners.md if you want to be subscribed.

@jkotas
Copy link
Member

jkotas commented May 17, 2024

.NET Core managed assemblies are not directly executable. .NET Core does not have the integration with the OS loader to enable that. You have to launch the managed assembly using a native binary. For example, dotnet MyAssembly.exe.

Also, if you plan to generate the assembly on one machine and run it on a different machine with potentially difference version of .NET Core, it would be best to generate it against the reference assemblies. Look for "If Reflection.Emit is used to generate an assembly that targets a specific TFM, open the reference assemblies for the given TFM" in https://learn.microsoft.com/en-us/dotnet/fundamentals/runtime-libraries/system-reflection-emit-persistedassemblybuilder .

@jkotas jkotas added the question Answer questions and provide assistance, not an issue with source code or documentation. label May 17, 2024
@Aaron-Junker
Copy link
Author

.NET Core managed assemblies are not directly executable. .NET Core does not have the integration with the OS loader to enable that. You have to launch the managed assembly using a native binary. For example, dotnet MyAssembly.exe.

Also, if you plan to generate the assembly on one machine and run it on a different machine with potentially difference version of .NET Core, it would be best to generate it against the reference assemblies. Look for "If Reflection.Emit is used to generate an assembly that targets a specific TFM, open the reference assemblies for the given TFM" in https://learn.microsoft.com/en-us/dotnet/fundamentals/runtime-libraries/system-reflection-emit-persistedassemblybuilder .

@jkotas Thank you for the quick answer. For the example you provided I don't really get what refAssembliesPath should be. This isn't really specified.

And for the executable, then why can you generate executables if you cannot execute them? If you have to load the assemblies anyway why is generating exe's directly mentioned in the docs?

What would be the best alternative here? Generating an exe that checks if dotnet exists and then runs dotnet MyAssembly.exe?

@jkotas
Copy link
Member

jkotas commented May 18, 2024

And for the executable, then why can you generate executables if you cannot execute them?

.NET Core tooling works in two stages. The first stage are managed compilers (C#, VB, F#, IL, ...) that produce managed executable. The managed executable cannot be run directly. The second stage takes the managed executable, combines it with native launcher, and produces native executable that can be run directly. There are number of options: https://learn.microsoft.com/en-us/dotnet/core/deploying/ .

Reflection.Emit is for the first stage that produces managed executable. The second stage is implemented by the msbuild in the .NET SDK. If you are building a real ,NET compiler, you want to look into integrating it into the .NET SDK so that you get the second stage for free.

For the example you provided I don't really get what refAssembliesPath should be

If you are building a real compiler, it is best to let the existing .NET SDK logic figure the reference assemblies (.NET SDK looks at the <TargetFramework> property in your project file and maps it into nuget package with the reference assemblies, downloading it if necessary).

@steveharter
Copy link
Member

Thanks @jkotas for the quick replies above. Closing issue.

FWIW, a quick way to get the sample above to be called via EXE is to create a console app that references the generated assembly and calls MyType.Main() from its Main():

internal class Program
{
    static void Main(string[] args)
    {
        MyType.Main();
    }
}

Here's some quick edits on the sample above to use reference assemblies, as well as generate a call to Console.WriteLine() so you can see that the generated method was called.

// Change the path as needed:
string[] runtimeAssemblies = Directory.GetFiles(@"C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\9.0.0-preview.4.24223.11\ref\net9.0", "*.dll");

MetadataAssemblyResolver resolver = new PathAssemblyResolver(runtimeAssemblies);
MetadataLoadContext mlc = new MetadataLoadContext(resolver, "System.Runtime");

PersistedAssemblyBuilder ab = new PersistedAssemblyBuilder(new AssemblyName("MyAssembly"), mlc.CoreAssembly!);
TypeBuilder tb = ab.DefineDynamicModule("MyModule").DefineType("MyType", TypeAttributes.Public | TypeAttributes.Class);

MethodBuilder entryPoint = tb.DefineMethod("Main", MethodAttributes.HideBySig | MethodAttributes.Public | MethodAttributes.Static);
ILGenerator il2 = entryPoint.GetILGenerator();

Type stringType = mlc.CoreAssembly!.GetType("System.String")!;
MethodInfo consoleWriteLine = mlc.LoadFromAssemblyName("System.Console").
    GetType("System.Console")!.
    GetMethod("WriteLine", BindingFlags.Public | BindingFlags.Static, null, new Type[] { stringType }, null)!;

il2.Emit(OpCodes.Ldstr, "Hello World");
il2.Emit(OpCodes.Call, consoleWriteLine);
il2.Emit(OpCodes.Ret);

tb.CreateType();

MetadataBuilder metadataBuilder = ab.GenerateMetadata(out BlobBuilder ilStream, out BlobBuilder fieldData);
PEHeaderBuilder peHeaderBuilder = new PEHeaderBuilder(imageCharacteristics: Characteristics.ExecutableImage);

ManagedPEBuilder peBuilder = new ManagedPEBuilder(
                header: peHeaderBuilder,
                metadataRootBuilder: new MetadataRootBuilder(metadataBuilder),
                ilStream: ilStream,
                mappedFieldData: fieldData,
                entryPoint: MetadataTokens.MethodDefinitionHandle(entryPoint.MetadataToken));

BlobBuilder peBlob = new BlobBuilder();
peBuilder.Serialize(peBlob);

// in case saving to a file:
using var fileStream = new FileStream("MyAssembly.dll", FileMode.Create, FileAccess.Write);
peBlob.WriteContentTo(fileStream);

@steveharter steveharter closed this as not planned Won't fix, can't repro, duplicate, stale May 21, 2024
@dotnet-policy-service dotnet-policy-service bot removed the untriaged New issue has not been triaged by the area owner label May 21, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
area-System.Reflection.Emit question Answer questions and provide assistance, not an issue with source code or documentation.
Projects
None yet
Development

No branches or pull requests

3 participants