├── .gitignore ├── README.md ├── efi-no-runtime ├── README.md ├── build.cmd ├── efinoruntime.csproj └── zerosharp.cs ├── no-runtime ├── build.cmd ├── noruntime.csproj └── zerosharp.cs └── with-runtime ├── Test.CoreLib.dll ├── Test.CoreLib.pdb ├── withruntime.csproj └── zerosharp.cs /.gitignore: -------------------------------------------------------------------------------- 1 | .vs/ 2 | bin/ 3 | obj/ 4 | *.exe 5 | *.ilexe 6 | *.pdb 7 | *.obj 8 | *.map 9 | 10 | !Test.CoreLib.pdb -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # C# for systems programming 2 | 3 | These samples show how to compile C# to native code using the .NET Native AOT technology ([NativeAOT](https://github.com/dotnet/runtimelab/tree/feature/NativeAOT), also known as CoreRT previously). 4 | 5 | The samples are for people who would like to use C#, but don't want to be bound by the choices of the base class libraries that normally come with C# (in the form that it's bundled in .NET). If you just want to native compile your .NET apps, go to the [NativeAOT](https://github.com/dotnet/runtimelab/tree/feature/NativeAOT) repo/branch instead. Nothing to see for you in this repo. 6 | 7 | `no-runtime` is a rather pointless sample that demonstrates how to write code in C# that is directly runnable without a runtime. C# has value types and you can p/invoke into an unmanaged memory allocator, so you can do things with this, but you're so severily limited it's rather pointless. But Hello world ends up being about 8 kB native EXE with no dependencies, so that's rather cool. 8 | 9 | `with-runtime` is something that can be actually useful. This includes the full managed and unmanaged runtime - GC, exception handling, and interface dispatch all work. Test.CoreLib used as the class library here is the same Test.CoreLib that you can find in the NativeAOT repo. Don't look for things like `Object.ToString()` because being compatible with .NET is not the point. This sample comes down to about 400 kB, most of which is the C runtime library. 10 | 11 | `efi-no-runtime` is an EFI boot application that lets you run C# on bare metal, without an OS. Similar restrictions to the `no-runtime` sample apply. Making a version of this sample with a runtime would require some porting work on the runtime side. 12 | 13 | ## Building the samples 14 | 15 | [.NET 7 SDK](https://dotnet.microsoft.com/download) is a prerequisite for building these on all platforms. 16 | 17 | In addition to the .NET 7 SDK, these are needed: 18 | * On Windows: Visual Studio 2022 **with** C++ development support and a Windows SDK 19 | * On Linux: clang 20 | * On macOS (untested): XCode 21 | 22 | Once you made sure you have the prerequisites, enter the appropriate sample directory and type: 23 | 24 | ```bash 25 | $ dotnet publish -c Release 26 | ``` 27 | 28 | Some samples also come with a shell script (*.cmd) that pieces together all the tools and avoid MSBuild or dotnet. You need to make sure you have environment set up before running the script. Look at the script for details. The script is redundant with the *.csproj project files. 29 | -------------------------------------------------------------------------------- /efi-no-runtime/README.md: -------------------------------------------------------------------------------- 1 | # EFI boot application in C# 2 | 3 | This sample is a EFI boot application written in C# that displays Hello World. It runs without an OS, on x64 bare metal hardware. 4 | 5 | 6 | 7 | ## Building the program 8 | 9 | Refer to the general instructions at the root of the repo. 10 | 11 | ## Booting the generated program 12 | 13 | Note: producing VHDX requires running from an elevated command prompt. It will not work without elevation. 14 | 15 | Running `build.cmd` should produce a BOOTX64.EFI file in the current directory. There are multiple ways to run this. QEMU with an EFI firmware should work. I use Hyper-V. 16 | 17 | Running `build.cmd vhd` will produce a VHDX file for you that you can run on Hyper-V directly. You need to create a new Gen 2 virtual machine in Hyper-V and attach the generated disk. Make sure to turn off Secure boot in the virtual machine: the EFI image is not signed. 18 | 19 | Similarly, adding `-p:BuildVHDX=true` to the `dotnet publish` line (when using the `*.csproj` project) will produce a bootable VHDX. 20 | 21 | ### Booting in VirtualBox 22 | 23 | To boot it in VirtualBox, we need to get a `vdi` file. First, create the vhdx like explained above (for example, `build.cmd vhd`). Then, you can use VB's built-in tool to convert the `vhdx` file into a `vdi` one: 24 | ``` 25 | VBoxManage.exe clonemedium disk zerosharp.vhdx zerosharp.vdi 26 | ``` 27 | 28 | Now, go to VirtualBox, create an empty OS, load the `vdi` file as a hard drive, and it should work. 29 | -------------------------------------------------------------------------------- /efi-no-runtime/build.cmd: -------------------------------------------------------------------------------- 1 | :: 2 | :: "Manual" build script that bypasses MSBuild and directly invokes the necessary tools. 3 | :: Good to show how things get hooked up together, but redundant with the project file. 4 | :: 5 | :: The tools are: 6 | :: 7 | :: * CSC, the C# compiler 8 | :: Opening a "x64 Native Tools Command Prompt for VS 2019" will place csc.exe on your PATH. 9 | :: * ILC, the Native AOT compiler 10 | :: If you use the project file to build this sample at least once, you can find ILC 11 | :: in your NuGet cache. It will be somewhere like 12 | :: C:\Users\username\.nuget\packages\runtime.win-x64.microsoft.dotnet.ilcompiler\7.0.0-alpha.1.21430.2 13 | :: * Linker 14 | :: This is the platform linker. "x64 Native Tools Command Prompt for VS 2019" will place 15 | :: the linker on your PATH. 16 | :: 17 | 18 | @set ILCPATH=%DROPPATH%\tools 19 | @if not exist %ILCPATH%\ilc.exe ( 20 | echo The DROPPATH environment variable not set. 21 | exit /B 22 | ) 23 | @where csc >nul 2>&1 24 | @if ERRORLEVEL 1 ( 25 | echo CSC not on the PATH. 26 | exit /B 27 | ) 28 | 29 | @set VHD=%CD%\zerosharp.vhdx 30 | @set VHD_SCRIPT=%CD%\diskpart.txt 31 | @del %VHD% >nul 2>&1 32 | @del %VHD_SCRIPT% >nul 2>&1 33 | @del zerosharp.ilexe >nul 2>&1 34 | @del zerosharp.obj >nul 2>&1 35 | @del zerosharp.map >nul 2>&1 36 | @del zerosharp.pdb >nul 2>&1 37 | @del BOOTX64.EFI >nul 2>&1 38 | 39 | @if "%1" == "clean" exit /B 40 | 41 | csc /nologo /debug:embedded /noconfig /nostdlib /runtimemetadataversion:v4.0.30319 zerosharp.cs /out:zerosharp.ilexe /langversion:latest /unsafe 42 | %ILCPATH%\ilc zerosharp.ilexe -o zerosharp.obj --systemmodule zerosharp --map zerosharp.map -O 43 | link /nologo /subsystem:EFI_APPLICATION zerosharp.obj /entry:EfiMain /incremental:no /out:BOOTX64.EFI 44 | 45 | @rem Build a VHD if requested 46 | 47 | @if not "%1" == "vhd" exit /B 48 | 49 | @( 50 | echo create vdisk file=%VHD% maximum=500 51 | echo select vdisk file=%VHD% 52 | echo attach vdisk 53 | echo convert gpt 54 | echo create partition efi size=100 55 | echo format quick fs=fat32 label="System" 56 | echo assign letter="X" 57 | echo exit 58 | )>%VHD_SCRIPT% 59 | 60 | diskpart /s %VHD_SCRIPT% 61 | 62 | xcopy BOOTX64.EFI X:\EFI\BOOT\ 63 | 64 | @( 65 | echo select vdisk file=%VHD% 66 | echo select partition 2 67 | echo remove letter=X 68 | echo detach vdisk 69 | echo exit 70 | )>%VHD_SCRIPT% 71 | 72 | diskpart /s %VHD_SCRIPT% 73 | -------------------------------------------------------------------------------- /efi-no-runtime/efinoruntime.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Exe 5 | net7.0 6 | true 7 | 8 | true 9 | true 10 | v4.0.30319 11 | false 12 | false 13 | 14 | efinoruntime 15 | EfiMain 16 | EFI_APPLICATION 17 | 18 | true 19 | 20 | 21 | 25 | 26 | 27 | 28 | 29 | 34 | 35 | $([System.IO.Path]::GetFullPath('$(PublishDir)'))zerosharp.vhdx 36 | 37 | 38 | create vdisk file=$(VHDFilePath) maximum=500 39 | select vdisk file=$(VHDFilePath) 40 | attach vdisk 41 | convert gpt 42 | create partition efi size=100 43 | format quick fs=fat32 label="System" 44 | assign letter=X 45 | 46 | 47 | select vdisk file=$(VHDFilePath) 48 | select partition 2 49 | remove letter=X 50 | detach vdisk 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | -------------------------------------------------------------------------------- /efi-no-runtime/zerosharp.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Runtime.InteropServices; 3 | 4 | #region A couple very basic things 5 | namespace System 6 | { 7 | public class Object 8 | { 9 | #pragma warning disable 169 10 | // The layout of object is a contract with the compiler. 11 | private IntPtr m_pMethodTable; 12 | #pragma warning restore 169 13 | } 14 | public struct Void { } 15 | 16 | // The layout of primitive types is special cased because it would be recursive. 17 | // These really don't need any fields to work. 18 | public struct Boolean { } 19 | public struct Char { } 20 | public struct SByte { } 21 | public struct Byte { } 22 | public struct Int16 { } 23 | public struct UInt16 { } 24 | public struct Int32 { } 25 | public struct UInt32 { } 26 | public struct Int64 { } 27 | public struct UInt64 { } 28 | public struct IntPtr { } 29 | public struct UIntPtr { } 30 | public struct Single { } 31 | public struct Double { } 32 | 33 | public abstract class ValueType { } 34 | public abstract class Enum : ValueType { } 35 | 36 | public struct Nullable where T : struct { } 37 | 38 | public sealed class String { public readonly int Length; } 39 | public abstract class Array { } 40 | public abstract class Delegate { } 41 | public abstract class MulticastDelegate : Delegate { } 42 | 43 | public struct RuntimeTypeHandle { } 44 | public struct RuntimeMethodHandle { } 45 | public struct RuntimeFieldHandle { } 46 | 47 | public class Attribute { } 48 | 49 | public enum AttributeTargets { } 50 | 51 | public sealed class AttributeUsageAttribute : Attribute 52 | { 53 | public AttributeUsageAttribute(AttributeTargets validOn) { } 54 | public bool AllowMultiple { get; set; } 55 | public bool Inherited { get; set; } 56 | } 57 | 58 | public class AppContext 59 | { 60 | public static void SetData(string s, object o) { } 61 | } 62 | 63 | namespace Runtime.CompilerServices 64 | { 65 | public class RuntimeHelpers 66 | { 67 | public static unsafe int OffsetToStringData => sizeof(IntPtr) + sizeof(int); 68 | } 69 | 70 | public static class RuntimeFeature 71 | { 72 | public const string UnmanagedSignatureCallingConvention = nameof(UnmanagedSignatureCallingConvention); 73 | } 74 | } 75 | } 76 | 77 | namespace System.Runtime.InteropServices 78 | { 79 | public class UnmanagedType { } 80 | 81 | sealed class StructLayoutAttribute : Attribute 82 | { 83 | public StructLayoutAttribute(LayoutKind layoutKind) 84 | { 85 | } 86 | } 87 | 88 | internal enum LayoutKind 89 | { 90 | Sequential = 0, // 0x00000008, 91 | Explicit = 2, // 0x00000010, 92 | Auto = 3, // 0x00000000, 93 | } 94 | 95 | internal enum CharSet 96 | { 97 | None = 1, // User didn't specify how to marshal strings. 98 | Ansi = 2, // Strings should be marshalled as ANSI 1 byte chars. 99 | Unicode = 3, // Strings should be marshalled as Unicode 2 byte chars. 100 | Auto = 4, // Marshal Strings in the right way for the target system. 101 | } 102 | } 103 | #endregion 104 | 105 | #region Things needed by ILC 106 | namespace System 107 | { 108 | namespace Runtime 109 | { 110 | internal sealed class RuntimeExportAttribute : Attribute 111 | { 112 | public RuntimeExportAttribute(string entry) { } 113 | } 114 | } 115 | 116 | class Array : Array { } 117 | } 118 | 119 | namespace Internal.Runtime.CompilerHelpers 120 | { 121 | using System.Runtime; 122 | 123 | // A class that the compiler looks for that has helpers to initialize the 124 | // process. The compiler can gracefully handle the helpers not being present, 125 | // but the class itself being absent is unhandled. Let's add an empty class. 126 | class StartupCodeHelpers 127 | { 128 | // A couple symbols the generated code will need we park them in this class 129 | // for no particular reason. These aid in transitioning to/from managed code. 130 | // Since we don't have a GC, the transition is a no-op. 131 | [RuntimeExport("RhpReversePInvoke")] 132 | static void RhpReversePInvoke(IntPtr frame) { } 133 | [RuntimeExport("RhpReversePInvokeReturn")] 134 | static void RhpReversePInvokeReturn(IntPtr frame) { } 135 | [RuntimeExport("RhpPInvoke")] 136 | static void RhpPInvoke(IntPtr frame) { } 137 | [RuntimeExport("RhpPInvokeReturn")] 138 | static void RhpPInvokeReturn(IntPtr frame) { } 139 | 140 | [RuntimeExport("RhpFallbackFailFast")] 141 | static void RhpFallbackFailFast() { while (true) ; } 142 | } 143 | } 144 | #endregion 145 | 146 | [StructLayout(LayoutKind.Sequential)] 147 | struct EFI_HANDLE 148 | { 149 | private IntPtr _handle; 150 | } 151 | 152 | [StructLayout(LayoutKind.Sequential)] 153 | unsafe readonly struct EFI_SIMPLE_TEXT_OUTPUT_PROTOCOL 154 | { 155 | private readonly IntPtr _pad; 156 | 157 | public readonly delegate* unmanaged OutputString; 158 | } 159 | 160 | [StructLayout(LayoutKind.Sequential)] 161 | readonly struct EFI_TABLE_HEADER 162 | { 163 | public readonly ulong Signature; 164 | public readonly uint Revision; 165 | public readonly uint HeaderSize; 166 | public readonly uint Crc32; 167 | public readonly uint Reserved; 168 | } 169 | 170 | [StructLayout(LayoutKind.Sequential)] 171 | unsafe readonly struct EFI_SYSTEM_TABLE 172 | { 173 | public readonly EFI_TABLE_HEADER Hdr; 174 | public readonly char* FirmwareVendor; 175 | public readonly uint FirmwareRevision; 176 | public readonly EFI_HANDLE ConsoleInHandle; 177 | public readonly void* ConIn; 178 | public readonly EFI_HANDLE ConsoleOutHandle; 179 | public readonly EFI_SIMPLE_TEXT_OUTPUT_PROTOCOL* ConOut; 180 | } 181 | 182 | unsafe class Program 183 | { 184 | static void Main() { } 185 | 186 | [System.Runtime.RuntimeExport("EfiMain")] 187 | static long EfiMain(IntPtr imageHandle, EFI_SYSTEM_TABLE* systemTable) 188 | { 189 | string hello = "Hello world!"; 190 | fixed (char* pHello = hello) 191 | { 192 | systemTable->ConOut->OutputString(systemTable->ConOut, pHello); 193 | } 194 | 195 | while (true) ; 196 | } 197 | } 198 | -------------------------------------------------------------------------------- /no-runtime/build.cmd: -------------------------------------------------------------------------------- 1 | :: 2 | :: "Manual" build script that bypasses MSBuild and directly invokes the necessary tools. 3 | :: Good to show how things get hooked up together, but redundant with the project file. 4 | :: 5 | :: The tools are: 6 | :: 7 | :: * CSC, the C# compiler 8 | :: Opening a "x64 Native Tools Command Prompt for VS 2019" will place csc.exe on your PATH. 9 | :: * ILC, the Native AOT compiler 10 | :: If you use the project file to build this sample at least once, you can find ILC 11 | :: in your NuGet cache. It will be somewhere like 12 | :: C:\Users\username\.nuget\packages\runtime.win-x64.microsoft.dotnet.ilcompiler\7.0.0-alpha.1.21430.2 13 | :: * Linker 14 | :: This is the platform linker. "x64 Native Tools Command Prompt for VS 2019" will place 15 | :: the linker on your PATH. 16 | :: 17 | 18 | @set ILCPATH=%DROPPATH%\tools 19 | @if not exist %ILCPATH%\ilc.exe ( 20 | echo The DROPPATH environment variable not set. 21 | exit /B 22 | ) 23 | @where csc >nul 2>&1 24 | @if ERRORLEVEL 1 ( 25 | echo CSC not on the PATH. 26 | exit /B 27 | ) 28 | 29 | @del zerosharp.ilexe >nul 2>&1 30 | @del zerosharp.obj >nul 2>&1 31 | @del zerosharp.exe >nul 2>&1 32 | @del zerosharp.map >nul 2>&1 33 | @del zerosharp.pdb >nul 2>&1 34 | 35 | @if "%1" == "clean" exit /B 36 | 37 | csc /define:WINDOWS /debug:embedded /noconfig /nostdlib /runtimemetadataversion:v4.0.30319 zerosharp.cs /out:zerosharp.ilexe /langversion:latest /unsafe || goto Error 38 | %ILCPATH%\ilc zerosharp.ilexe -g -o zerosharp.obj --systemmodule zerosharp --map zerosharp.map -O --directpinvoke:kernel32 || goto Error 39 | link /debug /subsystem:console zerosharp.obj /entry:__managed__Main kernel32.lib /incremental:no || goto Error 40 | 41 | @goto :EOF 42 | 43 | :Error 44 | @echo Tool failed. 45 | exit /B 1 46 | -------------------------------------------------------------------------------- /no-runtime/noruntime.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Exe 5 | net7.0 6 | true 7 | 8 | true 9 | true 10 | v4.0.30319 11 | false 12 | false 13 | 14 | noruntime 15 | __managed__Main 16 | 17 | true 18 | 19 | $(DefineConstants);WINDOWS 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | -------------------------------------------------------------------------------- /no-runtime/zerosharp.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Runtime; 3 | using System.Runtime.InteropServices; 4 | 5 | #region A couple very basic things 6 | namespace System 7 | { 8 | public class Object 9 | { 10 | #pragma warning disable 169 11 | // The layout of object is a contract with the compiler. 12 | private IntPtr m_pMethodTable; 13 | #pragma warning restore 169 14 | } 15 | public struct Void { } 16 | 17 | // The layout of primitive types is special cased because it would be recursive. 18 | // These really don't need any fields to work. 19 | public struct Boolean { } 20 | public struct Char { } 21 | public struct SByte { } 22 | public struct Byte { } 23 | public struct Int16 { } 24 | public struct UInt16 { } 25 | public struct Int32 { } 26 | public struct UInt32 { } 27 | public struct Int64 { } 28 | public struct UInt64 { } 29 | public struct IntPtr { } 30 | public struct UIntPtr { } 31 | public struct Single { } 32 | public struct Double { } 33 | 34 | public abstract class ValueType { } 35 | public abstract class Enum : ValueType { } 36 | 37 | public struct Nullable where T : struct { } 38 | 39 | public sealed class String { public readonly int Length; } 40 | public abstract class Array { } 41 | public abstract class Delegate { } 42 | public abstract class MulticastDelegate : Delegate { } 43 | 44 | public struct RuntimeTypeHandle { } 45 | public struct RuntimeMethodHandle { } 46 | public struct RuntimeFieldHandle { } 47 | 48 | public class Attribute { } 49 | 50 | public enum AttributeTargets { } 51 | 52 | public sealed class AttributeUsageAttribute : Attribute 53 | { 54 | public AttributeUsageAttribute(AttributeTargets validOn) { } 55 | public bool AllowMultiple { get; set; } 56 | public bool Inherited { get; set; } 57 | } 58 | 59 | public class AppContext 60 | { 61 | public static void SetData(string s, object o) { } 62 | } 63 | 64 | namespace Runtime.CompilerServices 65 | { 66 | public class RuntimeHelpers 67 | { 68 | public static unsafe int OffsetToStringData => sizeof(IntPtr) + sizeof(int); 69 | } 70 | } 71 | } 72 | namespace System.Runtime.InteropServices 73 | { 74 | public sealed class DllImportAttribute : Attribute 75 | { 76 | public DllImportAttribute(string dllName) { } 77 | } 78 | } 79 | #endregion 80 | 81 | #region Things needed by ILC 82 | namespace System 83 | { 84 | namespace Runtime 85 | { 86 | internal sealed class RuntimeExportAttribute : Attribute 87 | { 88 | public RuntimeExportAttribute(string entry) { } 89 | } 90 | } 91 | 92 | class Array : Array { } 93 | } 94 | 95 | namespace Internal.Runtime.CompilerHelpers 96 | { 97 | // A class that the compiler looks for that has helpers to initialize the 98 | // process. The compiler can gracefully handle the helpers not being present, 99 | // but the class itself being absent is unhandled. Let's add an empty class. 100 | class StartupCodeHelpers 101 | { 102 | // A couple symbols the generated code will need we park them in this class 103 | // for no particular reason. These aid in transitioning to/from managed code. 104 | // Since we don't have a GC, the transition is a no-op. 105 | [RuntimeExport("RhpReversePInvoke")] 106 | static void RhpReversePInvoke(IntPtr frame) { } 107 | [RuntimeExport("RhpReversePInvokeReturn")] 108 | static void RhpReversePInvokeReturn(IntPtr frame) { } 109 | [RuntimeExport("RhpPInvoke")] 110 | static void RhpPInvoke(IntPtr frame) { } 111 | [RuntimeExport("RhpPInvokeReturn")] 112 | static void RhpPInvokeReturn(IntPtr frame) { } 113 | 114 | [RuntimeExport("RhpFallbackFailFast")] 115 | static void RhpFallbackFailFast() { while (true) ; } 116 | } 117 | } 118 | #endregion 119 | 120 | unsafe class Program 121 | { 122 | [DllImport("libc")] 123 | static extern int printf(byte* fmt); 124 | 125 | [DllImport("kernel32")] 126 | static extern IntPtr GetStdHandle(int nStdHandle); 127 | 128 | [DllImport("kernel32")] 129 | static extern IntPtr WriteConsoleW(IntPtr hConsole, void* lpBuffer, int charsToWrite, out int charsWritten, void* reserved); 130 | 131 | #if !WINDOWS 132 | // Export this as "main" so that we can link with the C runtime library properly. 133 | // If the C runtime library is not initialized we can't even printf. 134 | // This is not needed on Windows because we don't call the C runtime. 135 | [RuntimeExport("main")] 136 | #endif 137 | static int Main() 138 | { 139 | string hello = "Hello world!\n"; 140 | fixed (char* pHello = hello) 141 | { 142 | #if WINDOWS 143 | WriteConsoleW(GetStdHandle(-11), pHello, hello.Length, out int _, null); 144 | #else 145 | // Once C# has support for UTF-8 string literals, this can be simplified. 146 | // https://github.com/dotnet/csharplang/issues/2911 147 | // Since we don't have that, convert from UTF-16 to ASCII. 148 | byte* pHelloASCII = stackalloc byte[hello.Length + 1]; 149 | for (int i = 0; i < hello.Length; i++) 150 | pHelloASCII[i] = (byte)pHello[i]; 151 | 152 | printf(pHelloASCII); 153 | #endif 154 | } 155 | 156 | return 42; 157 | } 158 | } 159 | -------------------------------------------------------------------------------- /with-runtime/Test.CoreLib.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MichalStrehovsky/zerosharp/33cb5b6a663da1918767a2c624afcd0b16e1da43/with-runtime/Test.CoreLib.dll -------------------------------------------------------------------------------- /with-runtime/Test.CoreLib.pdb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MichalStrehovsky/zerosharp/33cb5b6a663da1918767a2c624afcd0b16e1da43/with-runtime/Test.CoreLib.pdb -------------------------------------------------------------------------------- /with-runtime/withruntime.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Exe 5 | net7.0 6 | true 7 | 8 | true 9 | true 10 | v4.0.30319 11 | false 12 | false 13 | 14 | Test.CoreLib 15 | 16 | true 17 | 18 | 10 19 | 20 | 21 | 22 | 23 | Test.CoreLib.dll 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | -------------------------------------------------------------------------------- /with-runtime/zerosharp.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Runtime.InteropServices; 3 | 4 | static unsafe class Console 5 | { 6 | [DllImport("kernel32")] 7 | static extern IntPtr GetStdHandle(int nStdHandle); 8 | 9 | [DllImport("kernel32")] 10 | static extern IntPtr WriteConsoleW(IntPtr hConsole, void* lpBuffer, int charsToWrite, out int charsWritten, void* reserved); 11 | 12 | public static void WriteLine(string s) 13 | { 14 | IntPtr stdInputHandle = GetStdHandle(-11); 15 | int charsWritten; 16 | 17 | fixed (char* c = s) 18 | { 19 | WriteConsoleW(stdInputHandle, c, s.Length, out charsWritten, null); 20 | } 21 | 22 | char newLine = '\n'; 23 | WriteConsoleW(stdInputHandle, &newLine, 1, out charsWritten, null); 24 | } 25 | } 26 | 27 | class MyException : Exception { } 28 | 29 | interface IFooer 30 | { 31 | void Foo(); 32 | } 33 | 34 | struct Fooer : IFooer 35 | { 36 | public void Foo() => Console.WriteLine("Foo"); 37 | } 38 | 39 | class Program 40 | { 41 | static int Main() 42 | { 43 | try 44 | { 45 | throw new MyException(); 46 | Console.WriteLine("Exception not thrown!"); 47 | } 48 | catch 49 | { 50 | Console.WriteLine("Exception caught"); 51 | } 52 | 53 | IFooer fooer = (IFooer)new Fooer(); 54 | fooer.Foo(); 55 | 56 | return 42; 57 | } 58 | } 59 | --------------------------------------------------------------------------------