I am trying to write a simple block containing the TMainWindow class to improve my knowledge of the native Windows API.
I would like to use this class as follows:
var MainWindow: TMainWindow; begin MainWindow := TMainWindow.Create; try MainWindow.ShowModal; finally MainWindow.Free; end; end.
I have an almost working prototype, but I can not find the problem, here is the code that I have written so far:
unit NT.Window; interface uses Windows, Messages, Classes, SysUtils; type PObject = ^TObject; TMainWindow = class(TObject) private FChild : HWND; { Optional child window } FHandle : HWND; procedure WMCreate (var Msg: TWMCreate); message WM_CREATE; procedure WMDestroy (var Msg: TWMDestroy); message WM_DESTROY; procedure WMNcCreate (var Msg: TWMNCCreate); message WM_NCCREATE; procedure WMPaint (var Msg: TWMPaint); message WM_PAINT; procedure WMPrintClient (var Msg: TWMPrintClient); message WM_PRINTCLIENT; procedure WMSize (var Msg: TWMSize); message WM_SIZE; procedure PaintContent(const APaintStruct: TPaintStruct); function HandleMessage(var Msg: TMessage): Integer; public constructor Create; procedure DefaultHandler(var Message); override; function ShowModal: Boolean; end; implementation var WindowByHwnd: TStringList; function PointerToStr(APointer: Pointer): string; begin Result := IntToStr(NativeInt(APointer)); end; function StrToPointerDef(AString: string; ADefault: Pointer): Pointer; begin Result := Pointer(StrToIntDef(AString, Integer(ADefault))); end; function GetWindowByHwnd(hwnd: HWND): TMainWindow; begin Result := TMainWindow(StrToPointerDef(WindowByHwnd.Values[IntToStr(hwnd)], nil)); end; procedure StoreWindowByHwnd(hwnd: HWND; AWindow: TMainWindow); begin AWindow.FHandle := hwnd; WindowByHwnd.Add(IntToStr(hwnd) + '=' + PointerToStr(Pointer(AWindow))); end; function WndProc(hwnd: HWND; uiMsg: UINT; wParam: WPARAM; lParam: LPARAM): LRESULT; stdcall; var Msg : TMessage; Window : TMainWindow; begin Msg.Msg := uiMsg; Msg.WParam := wParam; Msg.LParam := lParam; Msg.Result := 0; if uiMsg = WM_NCCREATE then begin StoreWindowByHwnd(hwnd, TMainWindow(TWMNCCreate(Msg).CreateStruct.lpCreateParams)) end; Window := GetWindowByHwnd(hwnd); if Window = nil then begin Result := DefWindowProc(hwnd, Msg.Msg, Msg.WParam, Msg.LParam); end else begin Result := Window.HandleMessage(Msg); end; end; { TMainWindow } constructor TMainWindow.Create; var wc: WNDCLASS; begin inherited Create; wc.style := 0; wc.lpfnWndProc := @WndProc; wc.cbClsExtra := 0; wc.cbWndExtra := 0; wc.hInstance := HInstance; wc.hIcon := 0; wc.hCursor := LoadCursor(0, IDC_ARROW); wc.hbrBackground := HBRUSH(COLOR_WINDOW + 1); wc.lpszMenuName := nil; wc.lpszClassName := 'Scratch'; if Windows.RegisterClass(wc) = 0 then begin raise Exception.Create('RegisterClass failed: ' + SysErrorMessage(GetLastError)); end; if CreateWindow( 'Scratch', { Class Name } 'Scratch', { Title } WS_OVERLAPPEDWINDOW, { Style } Integer(CW_USEDEFAULT), Integer(CW_USEDEFAULT), { Position } Integer(CW_USEDEFAULT), Integer(CW_USEDEFAULT), { Size } 0, { Parent } 0, { No menu } HInstance, { Instance } @Self { No special parameters } ) = 0 then begin raise Exception.Create('CreateWindow failed: ' + SysErrorMessage(GetLastError)); end; end; procedure TMainWindow.DefaultHandler(var Message); var Msg: TMessage; begin Msg := TMessage(Message); Msg.Result := DefWindowProc(FHandle, Msg.Msg, Msg.WParam, Msg.LParam); end; function TMainWindow.HandleMessage(var Msg: TMessage): Integer; begin // Dispatch(Msg); case Msg.Msg of WM_CREATE : WMCreate( TWMCreate(Msg)); WM_DESTROY : WMDestroy( TWMDestroy(Msg)); WM_NCCREATE : WMNcCreate( TWMNCCreate(Msg)); WM_PAINT : WMPaint( TWMPaint(Msg)); WM_PRINTCLIENT : WMPrintClient(TWMPrintClient(Msg)); WM_SIZE : WMSize( TWMSize(Msg)); else // DefaultHandler(Msg); Msg.Result := DefWindowProc(FHandle, Msg.Msg, Msg.WParam, Msg.LParam); end; Result := Msg.Result; end; procedure TMainWindow.PaintContent(const APaintStruct: TPaintStruct); begin end; function TMainWindow.ShowModal: Boolean; var msg_ : MSG; begin ShowWindow(FHandle, CmdShow); while GetMessage(msg_, 0, 0, 0) do begin TranslateMessage(msg_); DispatchMessage(msg_); end; Result := True; end; procedure TMainWindow.WMCreate(var Msg: TWMCreate); begin Msg.Result := 0; end; procedure TMainWindow.WMDestroy(var Msg: TWMDestroy); begin PostQuitMessage(0); end; procedure TMainWindow.WMNcCreate(var Msg: TWMNCCreate); begin Msg.Result := Ord(True); end; procedure TMainWindow.WMPaint(var Msg: TWMPaint); var ps: PAINTSTRUCT; begin BeginPaint(FHandle, ps); PaintContent(ps); EndPaint(FHandle, ps); end; procedure TMainWindow.WMPrintClient(var Msg: TWMPrintClient); var ps: PAINTSTRUCT; begin ps.hdc := Msg.DC; GetClientRect(FHandle, ps.rcPaint); PaintContent(ps); end; procedure TMainWindow.WMSize(var Msg: TWMSize); begin if FChild <> 0 then begin MoveWindow(FChild, 0, 0, Msg.Width, Msg.Height, True); end; end; initialization WindowByHwnd := TStringList.Create; finalization WindowByHwnd.Free; end.
The code is partially based on the scratch program by Raymond Chen: http://blogs.msdn.com/b/oldnewthing/archive/2003/07/23/54576.aspx
I use TStringList to search for an instance of TMainWindow in the WndProc function, which is pretty inefficient but should work.
The program crashes as is, and also crashes when using Dispatch in HandleMessage .
Why does it crash immediately after exiting the constructor or a modified version in a Dispatch call?