{"record":{"id":"01d405e30b4c974a","repo":"wailsapp/wails","slug":"cannot-create-canvas-from-invalid-hdc","errorCode":null,"errorMessage":"Cannot create canvas from invalid HDC.","messagePattern":"Cannot create canvas from invalid HDC\\.","errorType":"panic","errorClass":null,"httpStatus":null,"severity":"error","filePath":"v2/internal/frontend/desktop/windows/winc/canvas.go","lineNumber":35,"sourceCode":"\thwnd         w32.HWND\n\thdc          w32.HDC\n\tdoNotDispose bool\n}\n\nvar nullBrush = NewNullBrush()\n\nfunc NewCanvasFromHwnd(hwnd w32.HWND) *Canvas {\n\thdc := w32.GetDC(hwnd)\n\tif hdc == 0 {\n\t\tpanic(fmt.Sprintf(\"Create canvas from %v failed.\", hwnd))\n\t}\n\n\treturn &Canvas{hwnd: hwnd, hdc: hdc, doNotDispose: false}\n}\n\nfunc NewCanvasFromHDC(hdc w32.HDC) *Canvas {\n\tif hdc == 0 {\n\t\tpanic(\"Cannot create canvas from invalid HDC.\")\n\t}\n\n\treturn &Canvas{hdc: hdc, doNotDispose: true}\n}\n\nfunc (ca *Canvas) Dispose() {\n\tif !ca.doNotDispose && ca.hdc != 0 {\n\t\tif ca.hwnd == 0 {\n\t\t\tw32.DeleteDC(ca.hdc)\n\t\t} else {\n\t\t\tw32.ReleaseDC(ca.hwnd, ca.hdc)\n\t\t}\n\n\t\tca.hdc = 0\n\t}\n}\n\nfunc (ca *Canvas) DrawBitmap(bmp *Bitmap, x, y int) {","sourceCodeStart":17,"sourceCodeEnd":53,"githubUrl":"https://github.com/wailsapp/wails/blob/0e754b1b40ba9044c2a1460e23b7c3de20fc5cf3/v2/internal/frontend/desktop/windows/winc/canvas.go#L17-L53","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","solutions":["Check the HDC for 0 before constructing the canvas (see validation code) and skip/handle the paint","Fix the upstream call that produced the NULL HDC: verify BeginPaint/GetDC/GetWindowDC succeeded and that the target HWND is valid","Never cache the HDC across messages — get it fresh each WM_PAINT and wrap it immediately"],"exampleFix":"// before\nhdc := w32.BeginPaint(hwnd, &ps)\nca := winc.NewCanvasFromHDC(hdc) // panics when BeginPaint returned 0\n\n// after\nhdc := w32.BeginPaint(hwnd, &ps)\nif hdc == 0 { return 0 }\nca := winc.NewCanvasFromHDC(hdc)","handlingStrategy":"validation","validationCode":"if hdc != 0 {\n    ca := winc.NewCanvasFromHDC(hdc)\n    defer func() { /* canvas will not release it; owner releases via EndPaint/ReleaseDC */ }()\n    _ = ca\n}","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Never construct the canvas before verifying the HDC source call succeeded","Remember doNotDispose=true: the caller owns release (EndPaint/ReleaseDC)"],"tags":["windows","gdi","device-context","winc","panic","invalid-argument"],"backgroundTag":null,"analyzedSha":"0e754b1b40ba9044c2a1460e23b7c3de20fc5cf3","analyzedAt":"2026-08-15T14:17:36.034Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}