Skip to content

Instantly share code, notes, and snippets.

@0xF6
Last active September 10, 2022 11:11
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save 0xF6/d1f21ce173da491e4a681eded5537cf7 to your computer and use it in GitHub Desktop.
Save 0xF6/d1f21ce173da491e4a681eded5537cf7 to your computer and use it in GitHub Desktop.
native splash screen when using native aot without wpf or win forms
using System.ComponentModel;
using static NativeApi;
public class SplashScreen : IDisposable
{
private ushort _wndClass;
private IntPtr _hwnd;
private const string CLASS_NAME = "SplashScreen";
private Bitmap _bitmap;
private static HandleRef nullHandle = new HandleRef(null, IntPtr.Zero);
private WndProc? proc;
public SplashScreen(FileInfo bitmapFile)
{
if (!bitmapFile.Exists)
throw new FileNotFoundException($"File '{bitmapFile.FullName}' is not found");
_bitmap = (Bitmap)Bitmap.FromFile(bitmapFile.FullName);
}
public void Show(bool topmost)
{
if (isDisposed)
throw new ObjectDisposedException("");
_hwnd = CreateWindow(_bitmap.GetHbitmap(), _bitmap.Width, _bitmap.Height, topmost);
}
public void Hide()
=> Dispose();
private nint CreateWindow(nint hBitmap, int width, int height, bool topMost)
{
proc = new WndProc(DefWindowProc);
var wndClass = new WNDCLASSEX();
wndClass.cbSize = 80;
wndClass.style = 3; /* CS_HREDRAW | CS_VREDRAW */
wndClass.hInstance = IntPtr.Zero;
wndClass.hCursor = IntPtr.Zero;
wndClass.lpszClassName = CLASS_NAME;
wndClass.lpszMenuName = string.Empty;
wndClass.lpfnWndProc = proc;
_wndClass = IntRegisterClassEx(wndClass);
var screenWidth = GetSystemMetrics(SystemMetric.SM_CXSCREEN);
var screenHeight = GetSystemMetrics(SystemMetric.SM_CYSCREEN);
var x = (screenWidth - width) / 2;
var y = (screenHeight - height) / 2;
var windowCreateFlags =
WS_EX_WINDOWEDGE |
WS_EX_TOOLWINDOW |
WS_EX_LAYERED |
(topMost ? WS_EX_TOPMOST : 0);
var hWnd = CreateWindowEx(
windowCreateFlags,
CLASS_NAME, "",
unchecked((int)( WindowStyles.WS_POPUP | WindowStyles.WS_VISIBLE)),
x, y, width, height,
nullHandle, nullHandle, /*new HandleRef(null, _hInstance)*/ nullHandle, IntPtr.Zero);
var hScreenDC = GetDC(new HandleRef());
var memDC = CreateCompatibleDC(new HandleRef(null, hScreenDC));
var hOldBitmap = SelectObject(new HandleRef(null, memDC), hBitmap);
var newSize = new NativeApi.Size(width, height);
var newLocation = new NativeApi.Point(x, y);
var sourceLocation = new NativeApi.Point(0, 0);
var _blendFunc = new BLENDFUNCTION();
_blendFunc.BlendOp = AC_SRC_OVER;
_blendFunc.BlendFlags = 0;
_blendFunc.SourceConstantAlpha = 255;
_blendFunc.AlphaFormat = 1; /*AC_SRC_ALPHA*/
var result = UpdateLayeredWindow(hWnd, hScreenDC, ref newLocation, ref newSize,
memDC, ref sourceLocation, 0, ref _blendFunc, ULW_ALPHA);
SelectObject(new HandleRef(null, memDC), hOldBitmap);
ReleaseDC(new HandleRef(), new HandleRef(null, memDC));
ReleaseDC(new HandleRef(), new HandleRef(null, hScreenDC));
if (result == Bool.False)
throw new Win32Exception(Marshal.GetHRForLastWin32Error());
return hWnd;
}
private void ReleaseUnmanagedResources()
{
SetActiveWindow(new HandleRef(null, _hwnd));
IntDestroyWindow(new HandleRef(null, _hwnd));
IntUnregisterClass(new IntPtr(_wndClass), IntPtr.Zero);
}
private void Dispose(bool disposing)
{
ReleaseUnmanagedResources();
if (disposing) _bitmap.Dispose();
isDisposed = true;
}
private bool isDisposed = false;
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
~SplashScreen()
=> Dispose(false);
}
public class NativeApi
{
public enum Bool: int
{
@False = 0,
@True = 1
}
public struct Point
{
public int x;
public int y;
public Point(int x, int y)
{
this.x = x;
this.y = y;
}
}
public struct Size
{
public int cx;
public int cy;
public Size(int cx, int cy)
{
this.cx = cx;
this.cy = cy;
}
}
public enum Protection
{
PAGE_NOACCESS = 0x01,
PAGE_READONLY = 0x02,
PAGE_READWRITE = 0x04,
PAGE_WRITECOPY = 0x08,
PAGE_EXECUTE = 0x10,
PAGE_EXECUTE_READ = 0x20,
PAGE_EXECUTE_READWRITE = 0x40,
PAGE_EXECUTE_WRITECOPY = 0x80,
PAGE_GUARD = 0x100,
PAGE_NOCACHE = 0x200,
PAGE_WRITECOMBINE = 0x400
}
public struct BLENDFUNCTION
{
public byte BlendOp;
public byte BlendFlags;
public byte SourceConstantAlpha;
public byte AlphaFormat;
}
public const int AC_SRC_OVER = 0x00000000;
public const int ULW_COLORKEY = 0x00000001;
public const int ULW_ALPHA = 0x00000002;
public const int ULW_OPAQUE = 0x00000004;
public const byte AC_SRC_ALPHA = 1;
public const int WS_POPUP = unchecked((int)0x80000000);
public const int WS_EX_LAYERED = 0x00080000,
WS_EX_WINDOWEDGE = 0x00000100,
WS_EX_TOOLWINDOW = 0x00000080,
WS_EX_TOPMOST = 0x00000008;
[DllImport("kernel32.dll")]
internal static extern bool FlushInstructionCache(IntPtr hProcess, IntPtr lpBaseAddress,
UIntPtr dwSize);
[DllImport("kernel32", CharSet = CharSet.Ansi, ExactSpelling = true, SetLastError = true)]
internal static extern IntPtr GetProcAddress(IntPtr hModule, string procName);
[DllImport("kernel32", SetLastError = true, CharSet = CharSet.Ansi)]
internal static extern IntPtr LoadLibrary([MarshalAs(UnmanagedType.LPStr)]string lpFileName);
[DllImport("kernel32.dll")]
internal static extern void Sleep(uint dwMilliseconds);
[DllImport("kernel32.dll", SetLastError = true)]
internal static extern bool VirtualProtect(IntPtr lpAddress, uint dwSize,
Protection flNewProtect, out Protection lpflOldProtect);
[DllImport("user32.dll", CharSet = CharSet.Unicode, EntryPoint = "DefWindowProcW")]
public static extern IntPtr DefWindowProc(IntPtr hWnd, Int32 Msg, IntPtr wParam, IntPtr lParam);
public delegate IntPtr WndProc(IntPtr hWnd, Int32 msg, IntPtr wParam, IntPtr lParam);
[DllImport("user32.dll", EntryPoint="RegisterClassEx", CharSet=CharSet.Unicode, SetLastError=true, BestFitMapping=false)]
internal static extern UInt16 IntRegisterClassEx(WNDCLASSEX wc_d);
[DllImport("user32.dll", SetLastError = true)]
public static extern IntPtr SetActiveWindow(HandleRef hWnd);
[DllImport("user32.dll", SetLastError = true, EntryPoint="DestroyWindow", CharSet=CharSet.Auto)]
public static extern bool IntDestroyWindow(HandleRef hWnd);
[DllImport("user32.dll", EntryPoint="UnregisterClass",CharSet = CharSet.Auto, SetLastError = true, BestFitMapping=false)]
internal static extern int IntUnregisterClass(IntPtr atomString /*lpClassName*/ , IntPtr hInstance);
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)]
public struct WNDCLASSEX
{
[MarshalAs(UnmanagedType.U4)]
public int cbSize = 80;
[MarshalAs(UnmanagedType.U4)]
public int style;
public WndProc lpfnWndProc; // not WndProc
public int cbClsExtra;
public int cbWndExtra;
public IntPtr hInstance;
public IntPtr hIcon;
public IntPtr hCursor;
public IntPtr hbrBackground;
public string lpszMenuName;
public string lpszClassName;
public IntPtr hIconSm;
public WNDCLASSEX()
{
}
}
[DllImport("user32.dll")]
[return: MarshalAs(UnmanagedType.U2)]
static extern short RegisterClassEx([In] ref WNDCLASSEX lpwcx);
[DllImport("user32.dll")]
public extern static Bool UpdateLayeredWindow(IntPtr handle, IntPtr hdcDst, ref Point pptDst, ref Size psize, IntPtr hdcSrc, ref Point pprSrc, int crKey, ref BLENDFUNCTION pblend, int dwFlags);
[DllImport("user32.dll")]
public extern static IntPtr GetDC(HandleRef handle);
[DllImport("user32.dll", ExactSpelling=true)]
public extern static int ReleaseDC(HandleRef handle, HandleRef hDC);
[DllImport("gdi32.dll")]
public extern static IntPtr CreateCompatibleDC(HandleRef hDC);
[DllImport("gdi32.dll")]
public extern static Bool DeleteDC(HandleRef hdc);
[DllImport("gdi32.dll")]
public extern static IntPtr SelectObject(HandleRef hDC, IntPtr hObject);
[DllImport("gdi32.dll")]
public extern static Bool DeleteObject(HandleRef hObject);
[DllImport("user32.dll", EntryPoint = "CreateWindowEx", CharSet = CharSet.Unicode)]
internal static extern IntPtr CreateWindowEx(int dwExStyle,
string lpszClassName,
string lpszWindowName,
int style,
int x, int y,
int width, int height,
HandleRef hwndParent,
HandleRef hMenu,
HandleRef hInst,
[MarshalAs(UnmanagedType.AsAny)] object pvParam);
[DllImport("user32.dll")]
public static extern int GetSystemMetrics(SystemMetric smIndex);
public enum SystemMetric
{
SM_CXSCREEN = 0, // 0x00
SM_CYSCREEN = 1, // 0x01
SM_CXVSCROLL = 2, // 0x02
SM_CYHSCROLL = 3, // 0x03
SM_CYCAPTION = 4, // 0x04
SM_CXBORDER = 5, // 0x05
SM_CYBORDER = 6, // 0x06
SM_CXDLGFRAME = 7, // 0x07
SM_CXFIXEDFRAME = 7, // 0x07
SM_CYDLGFRAME = 8, // 0x08
SM_CYFIXEDFRAME = 8, // 0x08
SM_CYVTHUMB = 9, // 0x09
SM_CXHTHUMB = 10, // 0x0A
SM_CXICON = 11, // 0x0B
SM_CYICON = 12, // 0x0C
SM_CXCURSOR = 13, // 0x0D
SM_CYCURSOR = 14, // 0x0E
SM_CYMENU = 15, // 0x0F
SM_CXFULLSCREEN = 16, // 0x10
SM_CYFULLSCREEN = 17, // 0x11
SM_CYKANJIWINDOW = 18, // 0x12
SM_MOUSEPRESENT = 19, // 0x13
SM_CYVSCROLL = 20, // 0x14
SM_CXHSCROLL = 21, // 0x15
SM_DEBUG = 22, // 0x16
SM_SWAPBUTTON = 23, // 0x17
SM_CXMIN = 28, // 0x1C
SM_CYMIN = 29, // 0x1D
SM_CXSIZE = 30, // 0x1E
SM_CYSIZE = 31, // 0x1F
SM_CXSIZEFRAME = 32, // 0x20
SM_CXFRAME = 32, // 0x20
SM_CYSIZEFRAME = 33, // 0x21
SM_CYFRAME = 33, // 0x21
SM_CXMINTRACK = 34, // 0x22
SM_CYMINTRACK = 35, // 0x23
SM_CXDOUBLECLK = 36, // 0x24
SM_CYDOUBLECLK = 37, // 0x25
SM_CXICONSPACING = 38, // 0x26
SM_CYICONSPACING = 39, // 0x27
SM_MENUDROPALIGNMENT = 40, // 0x28
SM_PENWINDOWS = 41, // 0x29
SM_DBCSENABLED = 42, // 0x2A
SM_CMOUSEBUTTONS = 43, // 0x2B
SM_SECURE = 44, // 0x2C
SM_CXEDGE = 45, // 0x2D
SM_CYEDGE = 46, // 0x2E
SM_CXMINSPACING = 47, // 0x2F
SM_CYMINSPACING = 48, // 0x30
SM_CXSMICON = 49, // 0x31
SM_CYSMICON = 50, // 0x32
SM_CYSMCAPTION = 51, // 0x33
SM_CXSMSIZE = 52, // 0x34
SM_CYSMSIZE = 53, // 0x35
SM_CXMENUSIZE = 54, // 0x36
SM_CYMENUSIZE = 55, // 0x37
SM_ARRANGE = 56, // 0x38
SM_CXMINIMIZED = 57, // 0x39
SM_CYMINIMIZED = 58, // 0x3A
SM_CXMAXTRACK = 59, // 0x3B
SM_CYMAXTRACK = 60, // 0x3C
SM_CXMAXIMIZED = 61, // 0x3D
SM_CYMAXIMIZED = 62, // 0x3E
SM_NETWORK = 63, // 0x3F
SM_CLEANBOOT = 67, // 0x43
SM_CXDRAG = 68, // 0x44
SM_CYDRAG = 69, // 0x45
SM_SHOWSOUNDS = 70, // 0x46
SM_CXMENUCHECK = 71, // 0x47
SM_CYMENUCHECK = 72, // 0x48
SM_SLOWMACHINE = 73, // 0x49
SM_MIDEASTENABLED = 74, // 0x4A
SM_MOUSEWHEELPRESENT = 75, // 0x4B
SM_XVIRTUALSCREEN = 76, // 0x4C
SM_YVIRTUALSCREEN = 77, // 0x4D
SM_CXVIRTUALSCREEN = 78, // 0x4E
SM_CYVIRTUALSCREEN = 79, // 0x4F
SM_CMONITORS = 80, // 0x50
SM_SAMEDISPLAYFORMAT = 81, // 0x51
SM_IMMENABLED = 82, // 0x52
SM_CXFOCUSBORDER = 83, // 0x53
SM_CYFOCUSBORDER = 84, // 0x54
SM_TABLETPC = 86, // 0x56
SM_MEDIACENTER = 87, // 0x57
SM_STARTER = 88, // 0x58
SM_SERVERR2 = 89, // 0x59
SM_MOUSEHORIZONTALWHEELPRESENT = 91, // 0x5B
SM_CXPADDEDBORDER = 92, // 0x5C
SM_DIGITIZER = 94, // 0x5E
SM_MAXIMUMTOUCHES = 95, // 0x5F
SM_REMOTESESSION = 0x1000, // 0x1000
SM_SHUTTINGDOWN = 0x2000, // 0x2000
SM_REMOTECONTROL = 0x2001, // 0x2001
SM_CONVERTIBLESLATEMODE = 0x2003,
SM_SYSTEMDOCKED = 0x2004,
}
[Flags]
public enum WindowStyles : uint
{
WS_BORDER = 0x800000,
WS_CAPTION = 0xc00000,
WS_CHILD = 0x40000000,
WS_CLIPCHILDREN = 0x2000000,
WS_CLIPSIBLINGS = 0x4000000,
WS_DISABLED = 0x8000000,
WS_DLGFRAME = 0x400000,
WS_GROUP = 0x20000,
WS_HSCROLL = 0x100000,
WS_MAXIMIZE = 0x1000000,
WS_MAXIMIZEBOX = 0x10000,
WS_MINIMIZE = 0x20000000,
WS_MINIMIZEBOX = 0x20000,
WS_OVERLAPPED = 0x0,
WS_OVERLAPPEDWINDOW = WS_OVERLAPPED | WS_CAPTION | WS_SYSMENU | WS_SIZEFRAME | WS_MINIMIZEBOX | WS_MAXIMIZEBOX,
WS_POPUP = 0x80000000u,
WS_POPUPWINDOW = WS_POPUP | WS_BORDER | WS_SYSMENU,
WS_SIZEFRAME = 0x40000,
WS_SYSMENU = 0x80000,
WS_TABSTOP = 0x10000,
WS_VISIBLE = 0x10000000,
WS_VSCROLL = 0x200000
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment