Tcanvas.Handle/Device Context is deleted before OpenGL rendering context can be cleaned up. (Happens when GUI control is deleted from From in Form Designer ?! producing error in ntdll.dll ?!))

Problem can be seen here.

I wrote this TOpenGLControl (GUI control) for Delphi/Windows/GDI which uses TCanvas and OpenGL Rendering Contexts and such.

(TOpenGLControl inherits from TCustomControl)

When I delete an OpenGLControl on the Form Designer delphi produces an error as follows:

“Access violation at address 76F19E33 in module ‘ntdll.dll’. Write of address 00000014.”

The following error details (below) are available, from the looks of it the canvas/device context is deleted before my OpenGLControl is deleted.

This is probably a problem. The OpenGL Rendering context should first be deleted and it needs this device context/TCanvas.Handle for it ?!

Since now the handle/device context is already deleted it creates this error.

How to best fix this problem ? (The OpenGLControl should be capable of being placed on forms and page controls and such)

[76F19E33]{ntdll.dll } Unknown function at RtlEncodePointer + $1EC
[50C2B2E6]{vcl250.bpl } Vcl.Forms.TCustomForm.WMEraseBkgnd (Line 6235, “Vcl.Forms.pas” + 1) + $14
[50AC03DA]{vcl250.bpl } Vcl.Graphics.TCustomCanvas.Lock (Line 3644, “Vcl.Graphics.pas” + 7) + $4
[50AC0497]{vcl250.bpl } Vcl.Graphics.TCustomCanvas.TryLock (Line 3676, “Vcl.Graphics.pas” + 7) + $8
[50AE15D1]{vcl250.bpl } Vcl.Controls.FreeDeviceContexts (Line 5014, “Vcl.Controls.pas” + 5) + $3
[50AE8E20]{vcl250.bpl } Vcl.Controls.TWinControl.MainWndProc (Line 9910, “Vcl.Controls.pas” + 5) + $0
[5016EBCC]{rtl250.bpl } System.Classes.StdWndProc (Line 17408, “System.Classes.pas” + 11) + $2
[50AE87C0]{vcl250.bpl } Vcl.Controls.TWinControl.DestroyWindowHandle (Line 9668, “Vcl.Controls.pas” + 5) + $A
[50AE69F7]{vcl250.bpl } Vcl.Controls.TWinControl.Destroy (Line 8500, “Vcl.Controls.pas” + 19) + $4
[50060016]{rtl250.bpl } System.@ClassDestroy (Line 18298, “System.pas” + 0) + $2
[50AE165F]{vcl250.bpl } Vcl.Controls.TControlCanvas.Destroy (Line 5066, “Vcl.Controls.pas” + 3) + $6
[50AEFC2C]{vcl250.bpl } Vcl.Controls.TCustomControl.Destroy (Line 14134, “Vcl.Controls.pas” + 2) + $6
[2B39DF16]{DelphiUnitsGUIControlsTOpenGLControlVersion005.bpl} unit_TOpenGLControl_version_005.TOpenGLControl.Destroy (Line 167, “unit_TOpenGLControl_version_005.pas” + 11) + $9
[5005F8A8]{rtl250.bpl } System.TObject.Free (Line 16994, “System.pas” + 1) + $4
[21200F32]{designide250.bpl} ComponentDesigner.TComponentRoot.DeleteSelection (Line 5139, “ComponentDesigner.pas” + 44) + $2
[2121C04B]{designide250.bpl} Surface.TDesignSurface.DeleteSelection (Line 208, “Surface.pas” + 0) + $7
[211BF793]{designide250.bpl} Designer.TDesigner.DeleteSelection (Line 789, “Designer.pas” + 2) + $5
[211C1F9F]{designide250.bpl} Designer.TDesigner.Key (Line 1607, “Designer.pas” + 4) + $3
[525E0098]{vcldesigner250.bpl} VCLSurface.KeyEvent (Line 3349, “VCLSurface.pas” + 8) + $1E
[525E09B2]{vcldesigner250.bpl} VCLSurface.TVclDesignSurface.IsDesignMsg (Line 3531, “VCLSurface.pas” + 61) + $6
[5005FBA2]{rtl250.bpl } System.@IsClass (Line 17430, “System.pas” + 1) + $8
[50AE49E3]{vcl250.bpl } Vcl.Controls.TControl.WndProc (Line 7251, “Vcl.Controls.pas” + 4) + $21
[50AE9843]{vcl250.bpl } Vcl.Controls.TWinControl.WndProc (Line 10209, “Vcl.Controls.pas” + 166) + $6
[50C28469]{vcl250.bpl } Vcl.Forms.TCustomForm.WndProc (Line 4572, “Vcl.Forms.pas” + 209) + $5
[21A24ED7]{vclactnband250.bpl} Vcl.ActnMenus.CallWindowHook (Line 750, “Vcl.ActnMenus.pas” + 20) + $F
[50060884]{rtl250.bpl } System.TMonitor.TryEnter (Line 18935, “System.pas” + 10) + $0
[500603D8]{rtl250.bpl } System.TMonitor.Enter (Line 18596, “System.pas” + 4) + $2
[525E9C1F]{vcldesigner250.bpl} VCLFormContainer.TControlSizer.ControlWndProc (Line 345, “VCLFormContainer.pas” + 33) + $C
[50060258]{rtl250.bpl } System.TMonitor.CheckOwningThread (Line 18510, “System.pas” + 2) + $0
[50060566]{rtl250.bpl } System.TMonitor.Exit (Line 18700, “System.pas” + 1) + $2
[500605B7]{rtl250.bpl } System.TMonitor.Exit (Line 18722, “System.pas” + 2) + $7
[50AC64AF]{vcl250.bpl } Vcl.Graphics.FreeMemoryContexts (Line 7129, “Vcl.Graphics.pas” + 12) + $8
[50AE8E10]{vcl250.bpl } Vcl.Controls.TWinControl.MainWndProc (Line 9908, “Vcl.Controls.pas” + 3) + $6
[50AE8E25]{vcl250.bpl } Vcl.Controls.TWinControl.MainWndProc (Line 9911, “Vcl.Controls.pas” + 6) + $0
[5016EBCC]{rtl250.bpl } System.Classes.StdWndProc (Line 17408, “System.Classes.pas” + 11) + $2
[50AE9843]{vcl250.bpl } Vcl.Controls.TWinControl.WndProc (Line 10209, “Vcl.Controls.pas” + 166) + $6
[50C28469]{vcl250.bpl } Vcl.Forms.TCustomForm.WndProc (Line 4572, “Vcl.Forms.pas” + 209) + $5
[525E9C1F]{vcldesigner250.bpl} VCLFormContainer.TControlSizer.ControlWndProc (Line 345, “VCLFormContainer.pas” + 33) + $C
[50AE8E10]{vcl250.bpl } Vcl.Controls.TWinControl.MainWndProc (Line 9908, “Vcl.Controls.pas” + 3) + $6
[5016EBCC]{rtl250.bpl } System.Classes.StdWndProc (Line 17408, “System.Classes.pas” + 11) + $2
[50C31E1F]{vcl250.bpl } Vcl.Forms.TApplication.ProcessMessage (Line 10641, “Vcl.Forms.pas” + 23) + $1
[50C31E62]{vcl250.bpl } Vcl.Forms.TApplication.HandleMessage (Line 10671, “Vcl.Forms.pas” + 1) + $4
[50C32195]{vcl250.bpl } Vcl.Forms.TApplication.Run (Line 10809, “Vcl.Forms.pas” + 26) + $3
[0050BAB2]{bds.exe } bds.bds (Line 214, “” + 7) + $7

Currently the TOpenGLControl tries to clean up as follows:

procedure TOpenGLControl.WMDestroy(var aMessage: TWMDestroy);
begin
if pointer(mRenderingContext) <> nil then
begin

	// make it the calling thread's current rendering context
	// perhaps clean up code depends on it so better make it current
	if wglMakeCurrent( Canvas.Handle, mRenderingContext ) then
	begin
		if Assigned(mOnDestroy) then
		begin
			mOnDestroy( Self );
		end;
	end;

	if wglGetCurrentContext <> 0 then
	begin

// ShowMessage(‘wglGetCurrentContext <> 0’);

		// make the rendering context not current
		if wglMakeCurrent( HDC(nil), HGLRC(nil) ) then
		begin
			// delete rendering context
			if wglDeleteContext( mRenderingContext ) then
			begin

			end else
			begin
				ShowMessage('mOpenGLAPI.wglDeleteContext( mRenderingContext ) failed.');
				ShowMessage('GetLastError: ' + IntToStr(GetLastError) );
			end;
		end else
		begin
			ShowMessage('mOpenGLAPI.wglMakeCurrent( nil, nil ) failed.');
			ShowMessage('GetLastError: ' + IntToStr(GetLastError) );
		end;
	end else
	begin
		// delete rendering context
		if wglDeleteContext( mRenderingContext ) then
		begin

		end else
		begin
			ShowMessage('mOpenGLAPI.wglDeleteContext( mRenderingContext ) failed.');
			ShowMessage('GetLastError: ' + IntToStr(GetLastError) );
		end;
	end;
end;

end;

Perhaps checking for nil on TCanvas handle might help somewhat.

I will try and fix problem myself, but any advise would be appreciated.

Bye for now,
Skybuck.

(I probably miss-interpretted the list above, it seems up/side down… down lines are done first, up lines last, so maybe it’s called in correct order).

For now the solution I implemented was to avoid any opengl calls during design time:

if not (csDesigning in ComponentState) then
begin
          // do opengl stuff.
    end;

Also in the paint event I do something like:

if not (csDesigning in ComponentState) then
begin
       openglstuff
    end else
    begin
	Canvas.Pen.Color := 0;
	Canvas.Brush.Color := 0;
	Canvas.FillRect(ClientRect);

    end;

This is sufficient for now to mimic same behaviour and to show component on form.

Bye,
Skybuck.

Oh in my TransferVisualizer for UDP File Transfer which can also use OpenGL I already solved this problem apperently, don’t know how.

This code should be commented in the destroy method, kinda makes sense assuming canvas.handle is zero or something, maybe it’s not though, could also be garbage data, anyway, commenting this code no longer produces the error.

However when switching page control/tab sheets in udp file transfer I did notice occasional crashes of application, could be laptop/driver specific or not, not sure yet ;) Haven’t seen it crash on my main computer though.

(*
// make it the calling thread’s current rendering context
// perhaps clean up code depends on it so better make it current
if wglMakeCurrent( Canvas.Handle, mRenderingContext ) then
begin
if Assigned(mOnDestroy) then
begin
mOnDestroy( Self );
end;
end;
*)

Sometimes I might want to experiment with graphics while the real opengl is used… so I am going to undue previous fix, but I’ll leave it in commented just in case I ever need it again ;)

Bye,
Skybuck.

Oh I just noticed one big drawback of this disabling…

The OnDestroy is not available to the end user anymore. Hmmm…

Not sure if I will need it.

Not even sure if it’s safe to use…

Will see in feature, will document this… and might apply in design time fix in future maybe.

Hmm this is getting a little bit akward =D lol.

Bye for now,
Skybuck.

Microsoft seems to have a fix for this. Perhaps this was written because I reported this problem in the past not sure, probably not, but ya never know ! ;)

From 2018:

https://docs.microsoft.com/en-us/windows/win32/opengl/deleting-a-rendering-context

// a window is about to be destroyed
case WM_DESTROY:
{
// local variables
HGLRC hglrc;
HDC hdc ;

// if the thread has a current rendering context ...  
if(hglrc = wglGetCurrentContext()) { 

    // obtain its associated device context  
    hdc = wglGetCurrentDC() ; 

    // make the rendering context not current  
    wglMakeCurrent(NULL, NULL) ; 

    // release the device context  
    ReleaseDC (hwnd, hdc) ; 

    // delete the rendering context  
    wglDeleteContext(hglrc); 

    }

I tried this and it does work, perhaps OnDestroy might then be usuable as well, but if I remember correctly, maybe I already tried this approach and the handle is actually zero and unusuable, however this code is old, so maybe I didn’t try this fix yet…

This isn’t making much sense to me.

TCanvas.Handle is same as the handle returned from this method… might just be a fluke of luck…

Don’t know yet.

Or windows is internally doing something special to fix this.

procedure TOpenGLControl.WMDestroy(var aMessage: TWMDestroy);
var
vDeviceContextHandle : HDC;
begin
// if not (csDesigning in ComponentState) then
begin
if (pointer(mRenderingContext) <> nil) then
begin

		// this produces a crash when it's deleted from form designer, so don't call it:
		// *** disabled ***

		vDeviceContextHandle := wglGetCurrentDC();

		// make it the calling thread's current rendering context
		// perhaps clean up code depends on it so better make it current

// if wglMakeCurrent( Canvas.Handle, mRenderingContext ) then
if wglMakeCurrent( vDeviceContextHandle, mRenderingContext ) then
begin
if Assigned(mOnDestroy) then
begin
mOnDestroy( Self );
end;
end;

		if wglGetCurrentContext <> 0 then
		begin
//			ShowMessage('wglGetCurrentContext <> 0');

			// make the rendering context not current
			if wglMakeCurrent( HDC(nil), HGLRC(nil) ) then
			begin
				// delete rendering context
				if wglDeleteContext( mRenderingContext ) then
				begin

				end else
				begin
					ShowMessage('mOpenGLAPI.wglDeleteContext( mRenderingContext ) failed.');
					ShowMessage('GetLastError: ' + IntToStr(GetLastError) );
				end;
			end else
			begin
				ShowMessage('mOpenGLAPI.wglMakeCurrent( nil, nil ) failed.');
				ShowMessage('GetLastError: ' + IntToStr(GetLastError) );
			end;
		end else
		begin
			// delete rendering context
			if wglDeleteContext( mRenderingContext ) then
			begin

			end else
			begin
				ShowMessage('mOpenGLAPI.wglDeleteContext( mRenderingContext ) failed.');
				ShowMessage('GetLastError: ' + IntToStr(GetLastError) );
			end;
		end;
	end;
end;

end;

^ This is how it looks like for now.

Bye,
Skybuck.

Could be that TCanvas.Handle is different during design time. I can vaglue remember something about that.

Also calling ShowMessage during designtime might not be safe to do ?!

Not sure.

Bye,
Skybuck.

Calling ShowMessage during designtime is safe so that is not the cause of it. Hmmm ;)

Bye,
Skybuck.