Have you ever written any Delphi code like the following snippet? That is, have you ever raised a Delphi exception in a Win32 callback? Or even just failed to handle potential exceptions in a callback?
function MyWndEnumFunc(hwnd: HWND; lParam: LPARAM): BOOL; stdcall; begin if hwnd = TForm4(lParam).Handle then raise Exception.Create('Raise an exception just to demonstrate the issue'); Result := True; end; procedure TForm4.FormCreate(Sender: TObject); begin try EnumWindows(@MyWndEnumFunc, NativeInt(Self)); except on E:Exception do ShowMessage('Well, we unwound that exception. But does Win32 agree?'); end; end;
Raymond Chen has posted a great blog today about why that approach will eventually end in tears. You may get away with it for a while, or you may end up with horrific stack or heap corruption. The moral of the story is, any time you have a Win32 callback function, you need to make sure no exceptions leak. Like this:
function MyWndEnumFunc(hwnd: HWND; lParam: LPARAM): BOOL; stdcall; begin try if hwnd = TForm4(lParam).Handle then raise Exception.Create('Raise an exception just to demonstrate the issue'); Result := True; except // Handle the exception, perhaps pass it to Application.HandleException, // or log it, or abort your app. Just don’t let it bubble through any // Win32 code. You need to write the HandleAllExceptions function! HandleAllExceptions; Result := False; end; end;
If you use Delphi’s AllocateHwnd procedure, remember that it also does not handle exceptions for you (I’ve just reported this in QualityCentral as QC108653 as this caveat should at least be documented). So you need to do it:
procedure TForm4.MyAllocatedWindowProc(var Message: TMessage); begin try if Message.Msg = WM_USER then raise Exception.Create('Go wild!'); except Application.HandleException(Self); // Or whatever, just don’t let it leak end; with Message do Result := DefWindowProc(FMyHandle, Msg, wParam, lParam); end; procedure TForm4.FormCreate(Sender: TObject); begin FMyHandle := AllocateHwnd(MyAllocatedWindowProc); SendMessage(FMyHandle, WM_USER, 0, 0); end;