Skip to content

Latest commit

Β 

History

2 Commits

Folders and files

NameName
Last commit message
Last commit date
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 

Repository files navigation

Universal C# Compiler & Runtime Experiment

Author: Dr. Foad S. Farimani
Socials: LinkedIn | X / Twitter | Stack Overflow


🎯 Project Overview

This project demonstrates a "canonical" approach to Manual Cross-Compilation in .NET. It uses a single, monolithic C# source file (Universal.cs) and a robust CLI helper script (Build.cmd) to compile executable binaries targeting completely different runtimes using nothing but the raw csc.exe compilers found on a standard Windows machine.

It proves that modern C# code does not require MSBuild or .csproj files to run; it simply requires the correct Reference Assemblies and Compiler Flags.

πŸš€ Usage

  1. Clone the repository git clone https://github.com/MinimalWindowsDev/Universal-CSharp
  2. Run Build.cmd
  3. Select a target environment:
Option Compiler Target Execution Method
[1] .NET SDK 10.0.101 Roslyn .NET 10 dotnet Universal.exe
[2] Visual Studio 18 Roslyn .NET 8 dotnet Universal.exe
[3] .NET Framework 4.0 csc.exe .NET Fx 4.0 Native Universal.exe

Example Output

[Build-Time Information]
Compiler:        Roslyn: 10.0.1 (System.Private.CoreLib), CoreLib: 10.0.1, Image: v4.0.30319
Ref Assemblies:  net10.0

[Run-Time Information]
CLR Version:     10.0.1
Runtime:         .NET 10.0.1
Timestamp:       2026-01-08 16:03:28
OS:              Microsoft Windows NT 10.0.26200.0
64-bit Process:  True

🧠 Lessons Learned (The Hard Way)

1. The Three Players: Compiler, Reference Assemblies, and Runtime

There are three distinct components involved in building and running a .NET application:

Component Role Example
Compiler (csc.exe) Translates C# to IL bytecode dotnet\sdk\10.0.101\Roslyn\bincore\csc.exe
Reference Assemblies Metadata-only DLLs for compilation dotnet\packs\Microsoft.NETCore.App.Ref\10.0.1\ref\net10.0\
Runtime (dotnet host) Executes the IL bytecode dotnet\shared\Microsoft.NETCore.App\10.0.1\

Key Insight: Environment.Version reports the runtime version, not the compiler or reference assemblies used at build time. To determine what you compiled against, you must use preprocessor defines (/define:) since that information is not retained in the IL.

2. "Dumb" Mode vs. "Smart" Mode Compilers

The legacy .NET Framework compiler (v4.0.30319\csc.exe) operates in "smart" modeβ€”it implicitly knows where mscorlib.dll lives and references it automatically.

Modern Roslyn compilers (in the SDK or VS) are implementation-agnostic. They operate in "dumb" mode and do not assume anything about the target runtime.

The Fix: You must explicitly tell the compiler where fundamental types live:

csc.exe /noconfig /nostdlib /r:Path\To\System.Runtime.dll ...

The /nostdlib flag disables implicit mscorlib references, and /noconfig ignores the default csc.rsp response file.

3. Reference vs. Implementation Assemblies

We discovered a critical distinction when compiling against Visual Studio's internal folders:

Assembly Type Location Characteristics
Reference Assemblies dotnet\packs\...\ref\ Metadata-only; System.Runtime.dll defines System.Object
Implementation Assemblies dotnet\shared\... or VS runtime folders Executable code; System.Runtime.dll is a Type Forwarder

The Pitfall: Implementation assemblies' System.Runtime.dll forwards types to System.Private.CoreLib.dll. If you compile against implementation assemblies without explicitly referencing System.Private.CoreLib.dll, the build fails:

error CS0518: Predefined type 'System.Object' is not defined or imported

The Fix: When using implementation assemblies, add:

/r:Path\To\System.Private.CoreLib.dll

4. The runtimeconfig.json Necessity

A .exe produced by csc.exe targeting .NET Core/.NET 5+ is not a native Windows executableβ€”it's a managed assembly that requires the dotnet host to execute.

The Fix: Generate a <AssemblyName>.runtimeconfig.json alongside the executable:

{
  "runtimeOptions": {
    "tfm": "net10.0",
    "framework": {
      "name": "Microsoft.NETCore.App",
      "version": "10.0.0"
    }
  }
}

Then execute via: dotnet Universal.exe

Note: The runtime version in runtimeconfig.json determines which CLR loads, regardless of which reference assemblies you compiled against. The dotnet host may "roll forward" to a newer runtime if the exact version isn't installed.

5. The Frozen ImageRuntimeVersion (v4.0.30319)

You might expect Assembly.ImageRuntimeVersion to differ between .NET Framework 4, .NET 8, and .NET 10 binaries. Surprisingly, they all report v4.0.30319.

Explanation: The CLR metadata format has been frozen since .NET Framework 4.0 (2010). Microsoft chose not to increment this version for .NET Core and beyond because:

  • The IL bytecode format hasn't fundamentally changed
  • It maintains compatibility with existing tooling (debuggers, decompilers, etc.)
  • Actual runtime version is communicated through other mechanisms

6. Extracting Compiler Version via Reflection

To determine the actual compiler/runtime version at execution time, inspect:

  • typeof(object).Assembly β€” Points to mscorlib (.NET Framework) or System.Private.CoreLib (.NET Core+)
  • AssemblyInformationalVersionAttribute β€” Contains version string with Git commit hash (e.g., 8.0.22+a2266c72...)
  • RuntimeInformation.FrameworkDescription β€” Human-readable runtime description
// Get detailed runtime info
var coreAsm = typeof(object).Assembly;
var infoVersion = coreAsm.GetCustomAttribute<AssemblyInformationalVersionAttribute>()
                         ?.InformationalVersion;
// Returns: "10.0.1+abcdef123..." for .NET 10

7. Batch Scripting Pitfalls

7.1 Comments Inside if Blocks

The :: comment syntax is actually a label (::), not a true comment. Inside parenthetical if blocks, it causes parse errors:

if "%x%"=="1" (
    :: This breaks the parser!
    set "VAR=value"
)

The Fix: Use REM inside blocks:

if "%x%"=="1" (
    REM This works correctly
    set "VAR=value"
)

7.2 Delayed Expansion for Variables

Variables set inside if blocks aren't accessible with %VAR% syntax because CMD expands variables at parse time, not execution time.

The Fix: Enable delayed expansion and use !VAR! syntax:

setlocal enabledelayedexpansion

if "%choice%"=="1" (
    set "REF_PATH=C:\some\path"
    REM Use !REF_PATH! not %REF_PATH%
    echo Path is: !REF_PATH!
)

8. Minimal Reference Assembly Set

For a basic console application, you need at minimum:

Assembly Purpose
System.Runtime.dll Core types (Object, String, Int32, etc.)
System.Console.dll Console.WriteLine, Console.ReadKey
System.Runtime.Extensions.dll Environment.Version, DateTime
System.Runtime.InteropServices.RuntimeInformation.dll RuntimeInformation.FrameworkDescription

Gotcha: System.Collections.Generic.List<T> lives in System.Collections.dllβ€”if you use it, you must add another /r: reference. To keep dependencies minimal, use arrays instead.

πŸ“‚ File Structure

β”œβ”€β”€ Universal.cs                    # Self-inspecting C# source file
β”œβ”€β”€ Build.cmd                       # Hybrid batch script for compilation
β”œβ”€β”€ Universal.exe                   # Output binary
└── README.md                       # This file

Universal.cs Features

  • Uses reflection to inspect its own runtime environment
  • Reports CLR version, assembly metadata, and compiler-embedded attributes
  • Uses preprocessor directives (#if REF_NET10) for compile-time information
  • Avoids System.Collections.dll dependency by using arrays
  • Compatible with .NET Framework 4.0 through .NET 10

Build.cmd Features

  • Interactive menu for compiler selection
  • Automatic runtimeconfig.json generation
  • Proper delayed expansion for variable handling
  • Displays compiler path, target TFM, and reference assembly path
  • Error handling with ERRORLEVEL checks

πŸ”§ Prerequisites

  • Windows 10/11
  • .NET SDK 10.x (for option 1)
  • Visual Studio 2025 "18" (for option 2)
  • .NET Framework 4.x (pre-installed on Windows)

πŸ“š Further Reading

βš–οΈ License

This work is licensed under a Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International License.

CC BY-NC-SA 4.0

About

No description, website, or topics provided.

Resources

Stars

3 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages