Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Auto-generate main method from referenced assembly

I am editing my question I think it is a little confusing and it does not explain what my intent is.

Edit:

My goal is that when my HelloWorld application references MyClassLibrary my code does not compile so that I ensure to initialize some code prior to running the main method. Kind of like a constructor of a class. When I reference MyClassLibrary I will like to run some code in there before running the main method of my HelloWorld application. NUnit has a similar functionality. When my HelloWorld application references NUnit I get the error: Error CS0017 Program has more than one entry point defined. Compile with /main to specify the type that contains the entry point. As @Alex pointed out that Main method that NUnit creates is auto-generated. I will like to auto-generate a main method with some custom code. How can I do that from MyClassLibrary without doing anything on my HelloWorld application just like NUnit does it?


OLD Question:

I want to perform the same behavior that NUnit tests perform that it prevents using a Main method. In this case the error that I need is a good thing. Let me explain what I mean.

  1. I create a hello world application targeting the .net core

Project file:

<Project Sdk="Microsoft.NET.Sdk">

  <PropertyGroup>
    <OutputType>Exe</OutputType>
    <TargetFramework>netcoreapp2.1</TargetFramework>
  </PropertyGroup>

</Project>

Code file: (default hello world c# code)

  1. If I then run that application it runs fine

  2. Add a reference to NUnit and my project file now contains.

.

<Project Sdk="Microsoft.NET.Sdk">

  <PropertyGroup>
    <OutputType>Exe</OutputType>
    <TargetFramework>netcoreapp2.1</TargetFramework>
  </PropertyGroup>

  <ItemGroup>
    <PackageReference Include="NUnit" Version="3.12.0" />
    <PackageReference Include="NUnit3TestAdapter" Version="3.13.0" />
    <PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.2.0" />
  </ItemGroup>

</Project>    
  1. When I try to compile the project I get the error:

Error CS0017 Program has more than one entry point defined. Compile with /main to specify the type that contains the entry point.

That means that there is another Main method. That method is probably located on the NUnit nuget package I am referencing. This is the error I am trying to replicate!.


Now this is how I try to replicate the same error:

  1. I remove the NUnit nugget package having no references to NUnit on my hello world application.

  2. Create a Project ClassLibrary1 with the following code:

.

public class MyLib
{
    static void Main()
    {
        Console.WriteLine("fooooo");
        // do something
    }
}
  1. Have my hello world application reference that project:

enter image description here

When I compile I get no errors even though there are 2 Main methods!

How does NUnit manages to prevent using a Main method? How can I replicate the same behavior? I want to create an assembly that when referenced it prevents executing the Main method.

like image 511
Tono Nam Avatar asked Feb 03 '23 17:02

Tono Nam


1 Answers

  • It's just Microsoft.NET.Test.Sdk making build fail.
  • Adding <GenerateProgramFile>false</GenerateProgramFile> into <PropertyGroup> makes it compile and work anyway.
  • But adding another class with static void Main to the application makes build fail again regardless <GenerateProgramFile>.
  • In your example build fails because Microsoft.NET.Test.Sdk adds some auto-generated code to your application before compilation. That code is in ...\.nuget\packages\microsoft.net.test.sdk\16.2.0\build\netcoreapp1.0\Microsoft.NET.Test.Sdk.Program.cs. It's a class with another Main:

// <auto-generated> This file has been auto generated. </auto-generated>
using System;
[Microsoft.VisualStudio.TestPlatform.TestSDKAutoGeneratedCode]
class AutoGeneratedProgram {static void Main(string[] args){}}

BTW: it's absolutely legal to have Main method in another assembly. You just cannot have 2 Mains in one exe. But you can have any number of them in dll like this:

public class Class1
{
    public static void Main() { }

    public static void Main(string[] args) { }
}

public class Class2
{
    public static void Main() { }

    public static void Main(string[] args) { }
}

It compiles.


Update:

I found the solution. It's all about installing nuget, not just adding a reference.

  1. Create a .NET Core Class Library and name it MyCoreLib.
  2. Add MyCoreClass.

namespace MyCoreLib
{
    public static class MyCoreClass
    {
        public static void Initialize()
        {
            System.Console.WriteLine("Initialized from 'MyCoreLib'");
        }
    }
}

  1. Build the library.
  2. Create the following file structure:

├───nuget
└───src
    │   MyCoreLib.nuspec
    │
    ├───build
    │   └───netcoreapp2.1
    │           ForcedEntryPoint.cs
    │           MyCoreLib.targets
    │
    └───lib
        └───netcoreapp2.1
                MyCoreLib.dll

MyCoreLib.nuspec

<?xml version="1.0" encoding="utf-8"?>
<package xmlns="http://schemas.microsoft.com/packaging/2012/06/nuspec.xsd">
  <metadata>
    <id>MyCoreLib</id>
    <version>1.0.0</version>
    <authors>MyCoreLib</authors>
    <owners>MyCoreLib</owners>
    <requireLicenseAcceptance>false</requireLicenseAcceptance>
    <description>Some description here</description>
    <dependencies>
      <group targetFramework=".NETCoreApp2.1" />
    </dependencies>
  </metadata>
</package>

ForcedEntryPoint.cs

//╔════════════════════════════════════╗
//║ This code was added automatically. ║
//║    Do not change or remove it.     ║
//╚════════════════════════════════════╝
public static class ForcedEntryPoint
{
    public static void Main(string[] args)
    {
        MyCoreLib.MyCoreClass.Initialize();
    }
}

MyCoreLib.targets

<Project InitialTargets="ForceEntryPoint" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
  <PropertyGroup>
    <OutputType>Exe</OutputType>
  </PropertyGroup>
  <PropertyGroup>
    <ForcedEntryPoint Condition="'$(ForcedEntryPoint)' == ''">$(MSBuildThisFileDirectory)ForcedEntryPoint$(DefaultLanguageSourceExtension)</ForcedEntryPoint>
    <ForceEntryPoint Condition="'$(ForceEntryPoint)' == ''">true</ForceEntryPoint>
  </PropertyGroup>
  <Target Name="ForceEntryPoint" Condition="'$(ForceEntryPoint)' == 'true'">
    <ItemGroup>
      <Compile Include="$(ForcedEntryPoint)"/>
    </ItemGroup>
  </Target>
</Project>

  1. Use NuGet Commandline to build a package like this:

D:\nugetwalkthrough\nuget>D:\nugetwalkthrough\nuget.exe pack D:\nugetwalkthrough\src\MyCoreLib.nuspec

  1. Create a .NET Core Console App and make sure it works.
  2. Install the created package.
  3. Try to run the application and get error:

    CS0017 Program has more than one entry point defined. Compile with /main to specify the type that contains the entry point.

  4. Remove the Main method from the application, run it and see it prints Initialized from 'MyCoreLib'.
  5. Put the Main method back to the application and change the project file so that <PropertyGroup> contains <ForceEntryPoint>false</ForceEntryPoint>
  6. Now it compiles and prints Hello World! from its own Main method.
  7. Changing <ForceEntryPoint> to true makes it use another entry point (not that one of the application) again.
like image 153
Alex Avatar answered Feb 06 '23 06:02

Alex