wailsapp/wails · critical

failed to load gdi32.dll: %v

Error message

failed to load gdi32.dll: %v

What it means

The same icon.go init that loads user32.dll also loads gdi32.dll and panics with the wrapped error if it fails. gdi32.dll is a permanent system DLL used for all drawing (icon bitmaps, DCs, brushes), so a load failure is environmental, not a code bug: blocked by security software, missing in stripped-down Windows containers/server-core-without-GUI, or an emulation layer without a matching-bitness gdi32.

Source

Thrown at v2/internal/frontend/desktop/windows/winc/icon.go:40

var (
	user32                 = syscall.NewLazyDLL("user32.dll")
	gdi32                  = syscall.NewLazyDLL("gdi32.dll")
	procGetIconInfo        = user32.NewProc("GetIconInfo")
	procDeleteObject       = gdi32.NewProc("DeleteObject")
	procGetObject          = gdi32.NewProc("GetObjectW")
	procGetDIBits          = gdi32.NewProc("GetDIBits")
	procCreateCompatibleDC = gdi32.NewProc("CreateCompatibleDC")
	procSelectObject       = gdi32.NewProc("SelectObject")
	procDeleteDC           = gdi32.NewProc("DeleteDC")
)

func init() {
	// Validate DLL loads at initialization time to surface missing APIs early
	if err := user32.Load(); err != nil {
		panic(fmt.Sprintf("failed to load user32.dll: %v", err))
	}
	if err := gdi32.Load(); err != nil {
		panic(fmt.Sprintf("failed to load gdi32.dll: %v", err))
	}
}

// ICONINFO mirrors the Win32 ICONINFO struct
type ICONINFO struct {
	FIcon    int32
	XHotspot uint32
	YHotspot uint32
	HbmMask  uintptr
	HbmColor uintptr
}

// http://msdn.microsoft.com/en-us/library/windows/desktop/dd183376.aspx
type BITMAPINFOHEADER struct {
	BiSize          uint32
	BiWidth         int32
	BiHeight        int32
	BiPlanes        uint16

View on GitHub (pinned to 0e754b1b40)

Solutions

  1. Keep GUI dependencies (winc imports) out of headless services/containers — split them behind a build tag or separate package so init never runs there
  2. Read the wrapped %v error: access-denied -> policy/AV; module-not-found -> missing DLL on that SKU
  3. On stripped SKUs, switch to a full desktop or servercore image that includes gdi32.dll
  4. Verify with `dumpbin /dependents` (or Dependency Walker) that the exe's DLL expectations match the host

Example fix

// before: headless service transitively imports winc -> init panics without gdi32
import _ "myapp/guipanel"

// after: gate GUI code behind a build tag
//go:build windows && gui
package guipanel
Defensive patterns

Strategy: fallback

Validate before calling

// As with user32: pre-flight the environment outside the process, or gate imports.
//go:build windows && gui
// package gui // imports winc icon package only in GUI builds

Prevention

When it happens

Trigger: Importing the winc icon package (any transitively importing package triggers init) on headless Windows containers; 64-bit process under emulation with only 32-bit gdi32; DLL load blocked by AppLocker/EDR; corrupted system DLL search path.

Common situations: Docker 'windows' container images based on servercore:nanoserver missing GUI DLLs when a GUI dependency sneaks into a service build; CI agents without interactive desktops when tests import winc.

Related errors


AI-assisted analysis of wailsapp/wails@0e754b1b40 (2026-08-15). Data as JSON: /api/errors/12c322ebec785c00. Report an issue: GitHub.