wailsapp/wails · error

Cannot create canvas from invalid HDC.

Error message

Cannot create canvas from invalid HDC.

What it means

NewCanvasFromHDC is a thin wrapper that rejects a NULL HDC argument with a panic; it performs no Win32 call at all. The error is purely a caller bug: an external HDC (typically obtained from WM_PAINT's BeginPaint or a parent's GetDC) was 0/invalid before being passed in. Because doNotDispose is set, winc never releases this DC, so the caller keeps ownership.

Source

Thrown at v2/internal/frontend/desktop/windows/winc/canvas.go:35

	hwnd         w32.HWND
	hdc          w32.HDC
	doNotDispose bool
}

var nullBrush = NewNullBrush()

func NewCanvasFromHwnd(hwnd w32.HWND) *Canvas {
	hdc := w32.GetDC(hwnd)
	if hdc == 0 {
		panic(fmt.Sprintf("Create canvas from %v failed.", hwnd))
	}

	return &Canvas{hwnd: hwnd, hdc: hdc, doNotDispose: false}
}

func NewCanvasFromHDC(hdc w32.HDC) *Canvas {
	if hdc == 0 {
		panic("Cannot create canvas from invalid HDC.")
	}

	return &Canvas{hdc: hdc, doNotDispose: true}
}

func (ca *Canvas) Dispose() {
	if !ca.doNotDispose && ca.hdc != 0 {
		if ca.hwnd == 0 {
			w32.DeleteDC(ca.hdc)
		} else {
			w32.ReleaseDC(ca.hwnd, ca.hdc)
		}

		ca.hdc = 0
	}
}

func (ca *Canvas) DrawBitmap(bmp *Bitmap, x, y int) {

View on GitHub (pinned to 0e754b1b40)

Solutions

  1. Check the HDC for 0 before constructing the canvas (see validation code) and skip/handle the paint
  2. Fix the upstream call that produced the NULL HDC: verify BeginPaint/GetDC/GetWindowDC succeeded and that the target HWND is valid
  3. Never cache the HDC across messages — get it fresh each WM_PAINT and wrap it immediately

Example fix

// before
hdc := w32.BeginPaint(hwnd, &ps)
ca := winc.NewCanvasFromHDC(hdc) // panics when BeginPaint returned 0

// after
hdc := w32.BeginPaint(hwnd, &ps)
if hdc == 0 { return 0 }
ca := winc.NewCanvasFromHDC(hdc)
Defensive patterns

Strategy: validation

Validate before calling

if hdc != 0 {
    ca := winc.NewCanvasFromHDC(hdc)
    defer func() { /* canvas will not release it; owner releases via EndPaint/ReleaseDC */ }()
    _ = ca
}

Prevention

When it happens

Trigger: Passing an HDC that a prior API returned NULL for — e.g. w32.BeginPaint failed, GetDC(hwnd) returned 0, or a bit-blit helper handed back 0 on failure; storing an HDC after its owning DC was deleted and reusing it.

Common situations: Custom WM_PAINT handling in winc controls where the developer extracts hdc from w32.PAINTSTRUCT without checking BeginPaint's return; interop code receiving an HDC over FFI/channel that can legitimately be 0.

Related errors


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